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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user