Allow to specify hints with the functional web API
The most common use case is specifying JSON views. ServerResponse.BodyBuilder#hint(String, Object) allows to specify response body serialization hints. ServerRequest#body(BodyExtractor, Map) allows to specify request body extraction hints. Issue: SPR-15030
This commit is contained in:
parent
f51fe5fd39
commit
fbf88d19da
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.springframework.web.reactive.function;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
@ -52,6 +53,11 @@ public interface BodyExtractor<T, M extends ReactiveHttpInputMessage> {
|
|||
* @return the stream of message readers
|
||||
*/
|
||||
Supplier<Stream<HttpMessageReader<?>>> messageReaders();
|
||||
|
||||
/**
|
||||
* Return the map of hints to use to customize body extraction.
|
||||
*/
|
||||
Map<String, Object> hints();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ public abstract class BodyExtractors {
|
|||
Assert.notNull(elementType, "'elementType' must not be null");
|
||||
return (request, context) -> readWithMessageReaders(request, context,
|
||||
elementType,
|
||||
reader -> reader.readMono(elementType, request, Collections.emptyMap()),
|
||||
reader -> reader.readMono(elementType, request, context.hints()),
|
||||
Mono::error);
|
||||
}
|
||||
|
||||
|
@ -90,7 +90,7 @@ public abstract class BodyExtractors {
|
|||
Assert.notNull(elementType, "'elementType' must not be null");
|
||||
return (inputMessage, context) -> readWithMessageReaders(inputMessage, context,
|
||||
elementType,
|
||||
reader -> reader.read(elementType, inputMessage, Collections.emptyMap()),
|
||||
reader -> reader.read(elementType, inputMessage, context.hints()),
|
||||
Flux::error);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.springframework.web.reactive.function;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
@ -54,6 +55,11 @@ public interface BodyInserter<T, M extends ReactiveHttpOutputMessage> {
|
|||
*/
|
||||
Supplier<Stream<HttpMessageWriter<?>>> messageWriters();
|
||||
|
||||
/**
|
||||
* Return the map of hints to use for response body conversion.
|
||||
*/
|
||||
Map<String, Object> hints();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package org.springframework.web.reactive.function;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -119,7 +118,7 @@ public abstract class BodyInserters {
|
|||
return (response, context) -> {
|
||||
HttpMessageWriter<Resource> messageWriter = resourceHttpMessageWriter(context);
|
||||
return messageWriter.write(Mono.just(resource), RESOURCE_TYPE, null,
|
||||
response, Collections.emptyMap());
|
||||
response, context.hints());
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -146,7 +145,7 @@ public abstract class BodyInserters {
|
|||
return (response, context) -> {
|
||||
HttpMessageWriter<ServerSentEvent<T>> messageWriter = sseMessageWriter(context);
|
||||
return messageWriter.write(eventsPublisher, SERVER_SIDE_EVENT_TYPE,
|
||||
MediaType.TEXT_EVENT_STREAM, response, Collections.emptyMap());
|
||||
MediaType.TEXT_EVENT_STREAM, response, context.hints());
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -186,7 +185,7 @@ public abstract class BodyInserters {
|
|||
return (outputMessage, context) -> {
|
||||
HttpMessageWriter<T> messageWriter = sseMessageWriter(context);
|
||||
return messageWriter.write(eventsPublisher, eventType,
|
||||
MediaType.TEXT_EVENT_STREAM, outputMessage, Collections.emptyMap());
|
||||
MediaType.TEXT_EVENT_STREAM, outputMessage, context.hints());
|
||||
|
||||
};
|
||||
}
|
||||
|
@ -227,8 +226,7 @@ public abstract class BodyInserters {
|
|||
.findFirst()
|
||||
.map(BodyInserters::cast)
|
||||
.map(messageWriter -> messageWriter
|
||||
.write(body, bodyType, contentType, m, Collections
|
||||
.emptyMap()))
|
||||
.write(body, bodyType, contentType, m, context.hints()))
|
||||
.orElseGet(() -> {
|
||||
List<MediaType> supportedMediaTypes = messageWriters.get()
|
||||
.flatMap(reader -> reader.getWritableMediaTypes().stream())
|
||||
|
|
|
@ -22,6 +22,8 @@ import java.time.ZoneId;
|
|||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
@ -220,6 +222,11 @@ class DefaultClientRequestBuilder implements ClientRequest.BodyBuilder {
|
|||
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
|
||||
return strategies.messageWriters();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> hints() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.springframework.web.reactive.function.client;
|
|||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalLong;
|
||||
import java.util.function.Function;
|
||||
|
@ -74,6 +75,10 @@ class DefaultClientResponse implements ClientResponse {
|
|||
public Supplier<Stream<HttpMessageReader<?>>> messageReaders() {
|
||||
return strategies.messageReaders();
|
||||
}
|
||||
@Override
|
||||
public Map<String, Object> hints() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -78,6 +78,11 @@ class DefaultServerRequest implements ServerRequest {
|
|||
|
||||
@Override
|
||||
public <T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor) {
|
||||
return body(extractor, Collections.emptyMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor, Map<String, Object> hints) {
|
||||
Assert.notNull(extractor, "'extractor' must not be null");
|
||||
return extractor.extract(request(),
|
||||
new BodyExtractor.Context() {
|
||||
|
@ -85,6 +90,10 @@ class DefaultServerRequest implements ServerRequest {
|
|||
public Supplier<Stream<HttpMessageReader<?>>> messageReaders() {
|
||||
return DefaultServerRequest.this.strategies.messageReaders();
|
||||
}
|
||||
@Override
|
||||
public Map<String, Object> hints() {
|
||||
return hints;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.time.ZonedDateTime;
|
|||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Locale;
|
||||
|
@ -62,6 +63,8 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
|
|||
|
||||
private final HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
private final Map<String, Object> hints = new HashMap<>();
|
||||
|
||||
|
||||
public DefaultServerResponseBuilder(HttpStatus statusCode) {
|
||||
this.statusCode = statusCode;
|
||||
|
@ -122,6 +125,12 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerResponse.BodyBuilder hint(String key, Object value) {
|
||||
this.hints.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerResponse.BodyBuilder lastModified(ZonedDateTime lastModified) {
|
||||
ZonedDateTime gmt = lastModified.withZoneSameInstant(ZoneId.of("GMT"));
|
||||
|
@ -182,7 +191,7 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
|
|||
public <T> Mono<ServerResponse> body(BodyInserter<T, ? super ServerHttpResponse> inserter) {
|
||||
Assert.notNull(inserter, "'inserter' must not be null");
|
||||
return Mono
|
||||
.just(new BodyInserterServerResponse<T>(this.statusCode, this.headers, inserter));
|
||||
.just(new BodyInserterServerResponse<T>(this.statusCode, this.headers, inserter, this.hints));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -276,11 +285,14 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
|
|||
|
||||
private final BodyInserter<T, ? super ServerHttpResponse> inserter;
|
||||
|
||||
private final Map<String, Object> hints;
|
||||
|
||||
public BodyInserterServerResponse(HttpStatus statusCode, HttpHeaders headers,
|
||||
BodyInserter<T, ? super ServerHttpResponse> inserter) {
|
||||
BodyInserter<T, ? super ServerHttpResponse> inserter, Map<String, Object> hints) {
|
||||
|
||||
super(statusCode, headers);
|
||||
this.inserter = inserter;
|
||||
this.hints = hints;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -292,6 +304,10 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
|
|||
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
|
||||
return strategies.messageWriters();
|
||||
}
|
||||
@Override
|
||||
public Map<String, Object> hints() {
|
||||
return hints;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -323,6 +323,11 @@ public abstract class RequestPredicates {
|
|||
return this.request.body(extractor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor, Map<String, Object> hints) {
|
||||
return this.request.body(extractor, hints);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<T> bodyToMono(Class<? extends T> elementClass) {
|
||||
return this.request.bodyToMono(elementClass);
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.springframework.http.HttpHeaders;
|
|||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRange;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.json.AbstractJackson2Codec;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.web.reactive.function.BodyExtractor;
|
||||
|
||||
|
@ -40,6 +41,7 @@ import org.springframework.web.reactive.function.BodyExtractor;
|
|||
* {@link #body(BodyExtractor)} respectively.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.0
|
||||
*/
|
||||
public interface ServerRequest {
|
||||
|
@ -71,9 +73,20 @@ public interface ServerRequest {
|
|||
* @param extractor the {@code BodyExtractor} that reads from the request
|
||||
* @param <T> the type of the body returned
|
||||
* @return the extracted body
|
||||
* @see #body(BodyExtractor, Map)
|
||||
*/
|
||||
<T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor);
|
||||
|
||||
/**
|
||||
* Extract the body with the given {@code BodyExtractor} and hints.
|
||||
* @param extractor the {@code BodyExtractor} that reads from the request
|
||||
* @param hints the map of hints like {@link AbstractJackson2Codec#JSON_VIEW_HINT}
|
||||
* to use to customize body extraction
|
||||
* @param <T> the type of the body returned
|
||||
* @return the extracted body
|
||||
*/
|
||||
<T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor, Map<String, Object> hints);
|
||||
|
||||
/**
|
||||
* Extract the body to a {@code Mono}.
|
||||
* @param elementClass the class of element in the {@code Mono}
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.springframework.http.HttpHeaders;
|
|||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.json.AbstractJackson2Codec;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.function.BodyInserter;
|
||||
|
@ -42,6 +43,7 @@ import org.springframework.web.server.ServerWebExchange;
|
|||
* {@linkplain HandlerFunction handler function} or {@linkplain HandlerFilterFunction filter function}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.0
|
||||
*/
|
||||
public interface ServerResponse {
|
||||
|
@ -312,6 +314,12 @@ public interface ServerResponse {
|
|||
*/
|
||||
BodyBuilder contentType(MediaType contentType);
|
||||
|
||||
/**
|
||||
* Add a serialization hint like {@link AbstractJackson2Codec#JSON_VIEW_HINT} to
|
||||
* customize how the body will be serialized.
|
||||
*/
|
||||
BodyBuilder hint(String key, Object value);
|
||||
|
||||
/**
|
||||
* Set the body of the response to the given {@code Publisher} and return it. This
|
||||
* convenience method combines {@link #body(BodyInserter)} and
|
||||
|
|
|
@ -91,6 +91,11 @@ public class ServerRequestWrapper implements ServerRequest {
|
|||
return this.request.body(extractor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor, Map<String, Object> hints) {
|
||||
return this.request.body(extractor, hints);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<T> bodyToMono(Class<? extends T> elementClass) {
|
||||
return this.request.bodyToMono(elementClass);
|
||||
|
|
|
@ -19,10 +19,14 @@ package org.springframework.web.reactive.function;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
@ -43,13 +47,20 @@ import org.springframework.http.codec.json.Jackson2JsonDecoder;
|
|||
import org.springframework.http.codec.xml.Jaxb2XmlDecoder;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
|
||||
import static org.springframework.http.codec.json.AbstractJackson2Codec.JSON_VIEW_HINT;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class BodyExtractorsTests {
|
||||
|
||||
private BodyExtractor.Context context;
|
||||
|
||||
private Map<String, Object> hints;
|
||||
|
||||
@Before
|
||||
public void createContext() {
|
||||
final List<HttpMessageReader<?>> messageReaders = new ArrayList<>();
|
||||
|
@ -63,8 +74,12 @@ public class BodyExtractorsTests {
|
|||
public Supplier<Stream<HttpMessageReader<?>>> messageReaders() {
|
||||
return messageReaders::stream;
|
||||
}
|
||||
@Override
|
||||
public Map<String, Object> hints() {
|
||||
return hints;
|
||||
}
|
||||
};
|
||||
|
||||
this.hints = new HashMap();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -87,6 +102,31 @@ public class BodyExtractorsTests {
|
|||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toMonoWithHints() throws Exception {
|
||||
BodyExtractor<Mono<User>, ReactiveHttpInputMessage> extractor = BodyExtractors.toMono(User.class);
|
||||
this.hints.put(JSON_VIEW_HINT, SafeToDeserialize.class);
|
||||
|
||||
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
|
||||
DefaultDataBuffer dataBuffer =
|
||||
factory.wrap(ByteBuffer.wrap("{\"username\":\"foo\",\"password\":\"bar\"}".getBytes(StandardCharsets.UTF_8)));
|
||||
Flux<DataBuffer> body = Flux.just(dataBuffer);
|
||||
|
||||
MockServerHttpRequest request = new MockServerHttpRequest();
|
||||
request.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
request.setBody(body);
|
||||
|
||||
Mono<User> result = extractor.extract(request, this.context);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.consumeNextWith(user -> {
|
||||
assertEquals("foo", user.getUsername());
|
||||
assertNull(user.getPassword());
|
||||
})
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toFlux() throws Exception {
|
||||
BodyExtractor<Flux<String>, ReactiveHttpInputMessage> extractor = BodyExtractors.toFlux(String.class);
|
||||
|
@ -107,6 +147,35 @@ public class BodyExtractorsTests {
|
|||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toFluxWithHints() throws Exception {
|
||||
BodyExtractor<Flux<User>, ReactiveHttpInputMessage> extractor = BodyExtractors.toFlux(User.class);
|
||||
this.hints.put(JSON_VIEW_HINT, SafeToDeserialize.class);
|
||||
|
||||
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
|
||||
DefaultDataBuffer dataBuffer =
|
||||
factory.wrap(ByteBuffer.wrap("[{\"username\":\"foo\",\"password\":\"bar\"},{\"username\":\"bar\",\"password\":\"baz\"}]".getBytes(StandardCharsets.UTF_8)));
|
||||
Flux<DataBuffer> body = Flux.just(dataBuffer);
|
||||
|
||||
MockServerHttpRequest request = new MockServerHttpRequest();
|
||||
request.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
request.setBody(body);
|
||||
|
||||
Flux<User> result = extractor.extract(request, this.context);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.consumeNextWith(user -> {
|
||||
assertEquals("foo", user.getUsername());
|
||||
assertNull(user.getPassword());
|
||||
})
|
||||
.consumeNextWith(user -> {
|
||||
assertEquals("bar", user.getUsername());
|
||||
assertNull(user.getPassword());
|
||||
})
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toFluxUnacceptable() throws Exception {
|
||||
BodyExtractor<Flux<String>, ReactiveHttpInputMessage> extractor = BodyExtractors.toFlux(String.class);
|
||||
|
@ -125,6 +194,10 @@ public class BodyExtractorsTests {
|
|||
public Supplier<Stream<HttpMessageReader<?>>> messageReaders() {
|
||||
return Stream::empty;
|
||||
}
|
||||
@Override
|
||||
public Map<String, Object> hints() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
};
|
||||
|
||||
Flux<String> result = extractor.extract(request, emptyContext);
|
||||
|
@ -153,4 +226,39 @@ public class BodyExtractorsTests {
|
|||
.verify();
|
||||
}
|
||||
|
||||
|
||||
interface SafeToDeserialize {}
|
||||
|
||||
private static class User {
|
||||
|
||||
@JsonView(SafeToDeserialize.class)
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
|
||||
public User() {
|
||||
}
|
||||
|
||||
public User(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -21,10 +21,13 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
@ -51,14 +54,18 @@ import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse
|
|||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.springframework.http.codec.json.AbstractJackson2Codec.JSON_VIEW_HINT;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class BodyInsertersTests {
|
||||
|
||||
private BodyInserter.Context context;
|
||||
|
||||
private Map<String, Object> hints;
|
||||
|
||||
@Before
|
||||
public void createContext() {
|
||||
final List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
|
||||
|
@ -71,19 +78,21 @@ public class BodyInsertersTests {
|
|||
messageWriters
|
||||
.add(new ServerSentEventHttpMessageWriter(Collections.singletonList(jsonEncoder)));
|
||||
|
||||
|
||||
this.context = new BodyInserter.Context() {
|
||||
@Override
|
||||
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
|
||||
return messageWriters::stream;
|
||||
}
|
||||
@Override
|
||||
public Map<String, Object> hints() {
|
||||
return hints;
|
||||
}
|
||||
};
|
||||
|
||||
this.hints = new HashMap();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void ofObject() throws Exception {
|
||||
public void ofString() throws Exception {
|
||||
String body = "foo";
|
||||
BodyInserter<String, ReactiveHttpOutputMessage> inserter = BodyInserters.fromObject(body);
|
||||
|
||||
|
@ -99,6 +108,35 @@ public class BodyInsertersTests {
|
|||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ofObject() throws Exception {
|
||||
User body = new User("foo", "bar");
|
||||
BodyInserter<User, ReactiveHttpOutputMessage> inserter = BodyInserters.fromObject(body);
|
||||
MockServerHttpResponse response = new MockServerHttpResponse();
|
||||
Mono<Void> result = inserter.insert(response, this.context);
|
||||
StepVerifier.create(result).expectComplete().verify();
|
||||
|
||||
StepVerifier.create(response.getBodyAsString())
|
||||
.expectNext("{\"username\":\"foo\",\"password\":\"bar\"}")
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ofObjectWithHints() throws Exception {
|
||||
User body = new User("foo", "bar");
|
||||
BodyInserter<User, ReactiveHttpOutputMessage> inserter = BodyInserters.fromObject(body);
|
||||
this.hints.put(JSON_VIEW_HINT, SafeToSerialize.class);
|
||||
MockServerHttpResponse response = new MockServerHttpResponse();
|
||||
Mono<Void> result = inserter.insert(response, this.context);
|
||||
StepVerifier.create(result).expectComplete().verify();
|
||||
|
||||
StepVerifier.create(response.getBodyAsString())
|
||||
.expectNext("{\"username\":\"foo\"}")
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ofPublisher() throws Exception {
|
||||
Flux<String> body = Flux.just("foo");
|
||||
|
@ -180,4 +218,39 @@ public class BodyInsertersTests {
|
|||
}
|
||||
|
||||
|
||||
interface SafeToSerialize {}
|
||||
|
||||
private static class User {
|
||||
|
||||
@JsonView(SafeToSerialize.class)
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
|
||||
public User() {
|
||||
}
|
||||
|
||||
public User(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -99,6 +99,11 @@ public class MockServerRequest implements ServerRequest {
|
|||
return (S) this.body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> S body(BodyExtractor<S, ? super ServerHttpRequest> extractor, Map<String, Object> hints) {
|
||||
return (S) this.body;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <S> Mono<S> bodyToMono(Class<? extends S> elementClass) {
|
||||
|
|
|
@ -50,7 +50,7 @@ import org.springframework.util.MimeTypeUtils;
|
|||
public class ServerSentEventHttpMessageWriter implements HttpMessageWriter<Object> {
|
||||
|
||||
/**
|
||||
* Server-Sent Events hint expecting a {@link Boolean} value which when set to true
|
||||
* Server-Sent Events hint key expecting a {@link Boolean} value which when set to true
|
||||
* will adapt the content in order to comply with Server-Sent Events recommendation.
|
||||
* For example, it will append "data:" after each line break with data encoders
|
||||
* supporting it.
|
||||
|
|
|
@ -38,6 +38,11 @@ import org.springframework.util.MimeType;
|
|||
*/
|
||||
public class AbstractJackson2Codec {
|
||||
|
||||
/**
|
||||
* Hint key to use with a {@link Class} value specifying the JSON View to use to serialize
|
||||
* or deserialize an object.
|
||||
* @see <a href="http://wiki.fasterxml.com/JacksonJsonViews">Jackson JSON Views</a>
|
||||
*/
|
||||
public static final String JSON_VIEW_HINT = AbstractJackson2Codec.class.getName() + ".jsonView";
|
||||
|
||||
protected static final List<MimeType> JSON_MIME_TYPES = Arrays.asList(
|
||||
|
|
Loading…
Reference in New Issue