Adjust WebFlux behavior for @RequestPart List<T>

List<T> support was added relatively late, incorrectly decoding each
part to T which means no way to decode a single part to List<T> and
thatis the most common case (vs multipart parts with the same name).
This behavior was further misaligned with Spring MVC as well as with
the behavior for T[].

Closes gh-22973
This commit is contained in:
Rossen Stoyanchev
2020-06-20 08:33:26 +01:00
parent 01fa923e96
commit ff9daa9377
2 changed files with 94 additions and 84 deletions

View File

@@ -17,6 +17,7 @@
package org.springframework.web.reactive.result.method.annotation;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -61,17 +62,17 @@ import static org.springframework.web.testfixture.method.MvcAnnotationPredicates
* @author Rossen Stoyanchev
* @author Ilya Lukyanovich
*/
public class RequestPartMethodArgumentResolverTests {
class RequestPartMethodArgumentResolverTests {
private RequestPartMethodArgumentResolver resolver;
private ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handle").build();
private final ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handle").build();
private MultipartHttpMessageWriter writer;
@BeforeEach
public void setup() throws Exception {
void setup() {
List<HttpMessageReader<?>> readers = ServerCodecConfigurer.create().getReaders();
ReactiveAdapterRegistry registry = ReactiveAdapterRegistry.getSharedInstance();
this.resolver = new RequestPartMethodArgumentResolver(readers, registry);
@@ -82,7 +83,7 @@ public class RequestPartMethodArgumentResolverTests {
@Test
public void supportsParameter() {
void supportsParameter() {
MethodParameter param;
@@ -110,7 +111,7 @@ public class RequestPartMethodArgumentResolverTests {
@Test
public void person() {
void person() {
MethodParameter param = this.testMethod.annot(requestPart()).arg(Person.class);
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("name", new Person("Jones"));
@@ -120,11 +121,10 @@ public class RequestPartMethodArgumentResolverTests {
}
@Test
public void listPerson() {
void listPerson() {
MethodParameter param = this.testMethod.annot(requestPart()).arg(List.class, Person.class);
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("name", new Person("Jones"));
bodyBuilder.part("name", new Person("James"));
bodyBuilder.part("name", Arrays.asList(new Person("Jones"), new Person("James")));
List<Person> actual = resolveArgument(param, bodyBuilder);
assertThat(actual.get(0).getName()).isEqualTo("Jones");
@@ -132,7 +132,7 @@ public class RequestPartMethodArgumentResolverTests {
}
@Test // gh-23060
public void listPersonNotRequired() {
void listPersonNotRequired() {
MethodParameter param = this.testMethod.annot(requestPart().notRequired()).arg(List.class, Person.class);
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
List<Person> actual = resolveArgument(param, bodyBuilder);
@@ -141,7 +141,7 @@ public class RequestPartMethodArgumentResolverTests {
}
@Test
public void monoPerson() {
void monoPerson() {
MethodParameter param = this.testMethod.annot(requestPart()).arg(Mono.class, Person.class);
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("name", new Person("Jones"));
@@ -151,7 +151,7 @@ public class RequestPartMethodArgumentResolverTests {
}
@Test // gh-23060
public void monoPersonNotRequired() {
void monoPersonNotRequired() {
MethodParameter param = this.testMethod.annot(requestPart().notRequired()).arg(Mono.class, Person.class);
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
Mono<Person> actual = resolveArgument(param, bodyBuilder);
@@ -160,7 +160,7 @@ public class RequestPartMethodArgumentResolverTests {
}
@Test
public void fluxPerson() {
void fluxPerson() {
MethodParameter param = this.testMethod.annot(requestPart()).arg(Flux.class, Person.class);
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("name", new Person("Jones"));
@@ -173,7 +173,7 @@ public class RequestPartMethodArgumentResolverTests {
}
@Test // gh-23060
public void fluxPersonNotRequired() {
void fluxPersonNotRequired() {
MethodParameter param = this.testMethod.annot(requestPart().notRequired()).arg(Flux.class, Person.class);
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
Flux<Person> actual = resolveArgument(param, bodyBuilder);
@@ -182,7 +182,7 @@ public class RequestPartMethodArgumentResolverTests {
}
@Test
public void part() {
void part() {
MethodParameter param = this.testMethod.annot(requestPart()).arg(Part.class);
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("name", new Person("Jones"));
@@ -193,7 +193,7 @@ public class RequestPartMethodArgumentResolverTests {
}
@Test
public void listPart() {
void listPart() {
MethodParameter param = this.testMethod.annot(requestPart()).arg(List.class, Part.class);
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("name", new Person("Jones"));
@@ -205,7 +205,7 @@ public class RequestPartMethodArgumentResolverTests {
}
@Test // gh-23060
public void listPartNotRequired() {
void listPartNotRequired() {
MethodParameter param = this.testMethod.annot(requestPart().notRequired()).arg(List.class, Part.class);
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
List<Part> actual = resolveArgument(param, bodyBuilder);
@@ -214,7 +214,7 @@ public class RequestPartMethodArgumentResolverTests {
}
@Test
public void monoPart() {
void monoPart() {
MethodParameter param = this.testMethod.annot(requestPart()).arg(Mono.class, Part.class);
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("name", new Person("Jones"));
@@ -225,7 +225,7 @@ public class RequestPartMethodArgumentResolverTests {
}
@Test // gh-23060
public void monoPartNotRequired() {
void monoPartNotRequired() {
MethodParameter param = this.testMethod.annot(requestPart().notRequired()).arg(Mono.class, Part.class);
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
Mono<Part> actual = resolveArgument(param, bodyBuilder);
@@ -234,7 +234,7 @@ public class RequestPartMethodArgumentResolverTests {
}
@Test
public void fluxPart() {
void fluxPart() {
MethodParameter param = this.testMethod.annot(requestPart()).arg(Flux.class, Part.class);
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("name", new Person("Jones"));
@@ -247,7 +247,7 @@ public class RequestPartMethodArgumentResolverTests {
}
@Test // gh-23060
public void fluxPartNotRequired() {
void fluxPartNotRequired() {
MethodParameter param = this.testMethod.annot(requestPart().notRequired()).arg(Flux.class, Part.class);
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
Flux<Part> actual = resolveArgument(param, bodyBuilder);
@@ -256,7 +256,7 @@ public class RequestPartMethodArgumentResolverTests {
}
@Test
public void personRequired() {
void personRequired() {
MethodParameter param = this.testMethod.annot(requestPart()).arg(Person.class);
ServerWebExchange exchange = createExchange(new MultipartBodyBuilder());
Mono<Object> result = this.resolver.resolveArgument(param, new BindingContext(), exchange);
@@ -265,7 +265,7 @@ public class RequestPartMethodArgumentResolverTests {
}
@Test
public void personNotRequired() {
void personNotRequired() {
MethodParameter param = this.testMethod.annot(requestPart().notRequired()).arg(Person.class);
ServerWebExchange exchange = createExchange(new MultipartBodyBuilder());
Mono<Object> result = this.resolver.resolveArgument(param, new BindingContext(), exchange);
@@ -274,7 +274,7 @@ public class RequestPartMethodArgumentResolverTests {
}
@Test
public void partRequired() {
void partRequired() {
MethodParameter param = this.testMethod.annot(requestPart()).arg(Part.class);
ServerWebExchange exchange = createExchange(new MultipartBodyBuilder());
Mono<Object> result = this.resolver.resolveArgument(param, new BindingContext(), exchange);
@@ -283,7 +283,7 @@ public class RequestPartMethodArgumentResolverTests {
}
@Test
public void partNotRequired() {
void partNotRequired() {
MethodParameter param = this.testMethod.annot(requestPart().notRequired()).arg(Part.class);
ServerWebExchange exchange = createExchange(new MultipartBodyBuilder());
Mono<Object> result = this.resolver.resolveArgument(param, new BindingContext(), exchange);
@@ -308,16 +308,15 @@ public class RequestPartMethodArgumentResolverTests {
this.writer.write(Mono.just(builder.build()), forClass(MultiValueMap.class),
MediaType.MULTIPART_FORM_DATA, clientRequest, Collections.emptyMap()).block();
MockServerHttpRequest serverRequest = MockServerHttpRequest.post("/")
.contentType(clientRequest.getHeaders().getContentType())
.body(clientRequest.getBody());
MediaType contentType = clientRequest.getHeaders().getContentType();
Flux<DataBuffer> body = clientRequest.getBody();
MockServerHttpRequest serverRequest = MockServerHttpRequest.post("/").contentType(contentType).body(body);
return MockServerWebExchange.from(serverRequest);
}
private String partToUtf8String(Part part) {
DataBuffer buffer = DataBufferUtils.join(part.content()).block();
return buffer.toString(UTF_8);
return DataBufferUtils.join(part.content()).block().toString(UTF_8);
}
@@ -344,7 +343,7 @@ public class RequestPartMethodArgumentResolverTests {
private static class Person {
private String name;
private final String name;
@JsonCreator
public Person(@JsonProperty("name") String name) {