Support @ModelAttribute(binding=false) with WebFlux
Prior to this commit, @ModelAttribute(binding=false) was honored with Spring Web MVC but not with WebFlux. This commit adds support for disabling binding via @ModelAttribute with WebFlux by adding a check to resolveArgument(...) in ModelAttributeMethodArgumentResolver. Closes gh-26856
This commit is contained in:
@@ -60,6 +60,7 @@ import org.springframework.web.server.ServerWebExchange;
|
|||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
|
* @author Sam Brannen
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
*/
|
*/
|
||||||
public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentResolverSupport {
|
public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentResolverSupport {
|
||||||
@@ -117,7 +118,7 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
|
|||||||
|
|
||||||
return valueMono.flatMap(value -> {
|
return valueMono.flatMap(value -> {
|
||||||
WebExchangeDataBinder binder = context.createDataBinder(exchange, value, name);
|
WebExchangeDataBinder binder = context.createDataBinder(exchange, value, name);
|
||||||
return bindRequestParameters(binder, exchange)
|
return (bindingDisabled(parameter) ? Mono.empty() : bindRequestParameters(binder, exchange))
|
||||||
.doOnError(bindingResultSink::tryEmitError)
|
.doOnError(bindingResultSink::tryEmitError)
|
||||||
.doOnSuccess(aVoid -> {
|
.doOnSuccess(aVoid -> {
|
||||||
validateIfApplicable(binder, parameter);
|
validateIfApplicable(binder, parameter);
|
||||||
@@ -143,6 +144,16 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if binding should be disabled for the supplied {@link MethodParameter},
|
||||||
|
* based on the {@link ModelAttribute#binding} annotation attribute.
|
||||||
|
* @since 5.3.7
|
||||||
|
*/
|
||||||
|
private boolean bindingDisabled(MethodParameter parameter) {
|
||||||
|
ModelAttribute modelAttribute = parameter.getParameterAnnotation(ModelAttribute.class);
|
||||||
|
return (modelAttribute != null && !modelAttribute.binding());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extension point to bind the request to the target object.
|
* Extension point to bind the request to the target object.
|
||||||
* @param binder the data binder instance to use for the binding
|
* @param binder the data binder instance to use for the binding
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2020 the original author or authors.
|
* Copyright 2002-2021 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -21,6 +21,8 @@ import java.time.Duration;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
|
||||||
import io.reactivex.rxjava3.core.Single;
|
import io.reactivex.rxjava3.core.Single;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -49,16 +51,17 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
|
* @author Sam Brannen
|
||||||
*/
|
*/
|
||||||
public class ModelAttributeMethodArgumentResolverTests {
|
class ModelAttributeMethodArgumentResolverTests {
|
||||||
|
|
||||||
|
private final ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handle").build();
|
||||||
|
|
||||||
private BindingContext bindContext;
|
private BindingContext bindContext;
|
||||||
|
|
||||||
private ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handle").build();
|
|
||||||
|
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setup() throws Exception {
|
void setup() {
|
||||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||||
validator.afterPropertiesSet();
|
validator.afterPropertiesSet();
|
||||||
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
|
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
|
||||||
@@ -68,32 +71,38 @@ public class ModelAttributeMethodArgumentResolverTests {
|
|||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void supports() throws Exception {
|
void supports() {
|
||||||
ModelAttributeMethodArgumentResolver resolver =
|
ModelAttributeMethodArgumentResolver resolver =
|
||||||
new ModelAttributeMethodArgumentResolver(ReactiveAdapterRegistry.getSharedInstance(), false);
|
new ModelAttributeMethodArgumentResolver(ReactiveAdapterRegistry.getSharedInstance(), false);
|
||||||
|
|
||||||
MethodParameter param = this.testMethod.annotPresent(ModelAttribute.class).arg(Foo.class);
|
MethodParameter param = this.testMethod.annotPresent(ModelAttribute.class).arg(Pojo.class);
|
||||||
assertThat(resolver.supportsParameter(param)).isTrue();
|
assertThat(resolver.supportsParameter(param)).isTrue();
|
||||||
|
|
||||||
param = this.testMethod.annotPresent(ModelAttribute.class).arg(Mono.class, Foo.class);
|
param = this.testMethod.annotPresent(ModelAttribute.class).arg(NonBindingPojo.class);
|
||||||
assertThat(resolver.supportsParameter(param)).isTrue();
|
assertThat(resolver.supportsParameter(param)).isTrue();
|
||||||
|
|
||||||
param = this.testMethod.annotNotPresent(ModelAttribute.class).arg(Foo.class);
|
param = this.testMethod.annotPresent(ModelAttribute.class).arg(Mono.class, Pojo.class);
|
||||||
|
assertThat(resolver.supportsParameter(param)).isTrue();
|
||||||
|
|
||||||
|
param = this.testMethod.annotPresent(ModelAttribute.class).arg(Mono.class, NonBindingPojo.class);
|
||||||
|
assertThat(resolver.supportsParameter(param)).isTrue();
|
||||||
|
|
||||||
|
param = this.testMethod.annotNotPresent(ModelAttribute.class).arg(Pojo.class);
|
||||||
assertThat(resolver.supportsParameter(param)).isFalse();
|
assertThat(resolver.supportsParameter(param)).isFalse();
|
||||||
|
|
||||||
param = this.testMethod.annotNotPresent(ModelAttribute.class).arg(Mono.class, Foo.class);
|
param = this.testMethod.annotNotPresent(ModelAttribute.class).arg(Mono.class, Pojo.class);
|
||||||
assertThat(resolver.supportsParameter(param)).isFalse();
|
assertThat(resolver.supportsParameter(param)).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void supportsWithDefaultResolution() throws Exception {
|
void supportsWithDefaultResolution() {
|
||||||
ModelAttributeMethodArgumentResolver resolver =
|
ModelAttributeMethodArgumentResolver resolver =
|
||||||
new ModelAttributeMethodArgumentResolver(ReactiveAdapterRegistry.getSharedInstance(), true);
|
new ModelAttributeMethodArgumentResolver(ReactiveAdapterRegistry.getSharedInstance(), true);
|
||||||
|
|
||||||
MethodParameter param = this.testMethod.annotNotPresent(ModelAttribute.class).arg(Foo.class);
|
MethodParameter param = this.testMethod.annotNotPresent(ModelAttribute.class).arg(Pojo.class);
|
||||||
assertThat(resolver.supportsParameter(param)).isTrue();
|
assertThat(resolver.supportsParameter(param)).isTrue();
|
||||||
|
|
||||||
param = this.testMethod.annotNotPresent(ModelAttribute.class).arg(Mono.class, Foo.class);
|
param = this.testMethod.annotNotPresent(ModelAttribute.class).arg(Mono.class, Pojo.class);
|
||||||
assertThat(resolver.supportsParameter(param)).isTrue();
|
assertThat(resolver.supportsParameter(param)).isTrue();
|
||||||
|
|
||||||
param = this.testMethod.annotNotPresent(ModelAttribute.class).arg(String.class);
|
param = this.testMethod.annotNotPresent(ModelAttribute.class).arg(String.class);
|
||||||
@@ -104,204 +113,286 @@ public class ModelAttributeMethodArgumentResolverTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createAndBind() throws Exception {
|
void createAndBind() throws Exception {
|
||||||
testBindFoo("foo", this.testMethod.annotPresent(ModelAttribute.class).arg(Foo.class), value -> {
|
testBindPojo("pojo", this.testMethod.annotPresent(ModelAttribute.class).arg(Pojo.class), value -> {
|
||||||
assertThat(value.getClass()).isEqualTo(Foo.class);
|
assertThat(value.getClass()).isEqualTo(Pojo.class);
|
||||||
return (Foo) value;
|
return (Pojo) value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createAndBindToMono() throws Exception {
|
void createAndBindToMono() throws Exception {
|
||||||
MethodParameter parameter = this.testMethod
|
MethodParameter parameter = this.testMethod
|
||||||
.annotNotPresent(ModelAttribute.class).arg(Mono.class, Foo.class);
|
.annotNotPresent(ModelAttribute.class).arg(Mono.class, Pojo.class);
|
||||||
|
|
||||||
testBindFoo("fooMono", parameter, mono -> {
|
testBindPojo("pojoMono", parameter, mono -> {
|
||||||
boolean condition = mono instanceof Mono;
|
assertThat(mono).isInstanceOf(Mono.class);
|
||||||
assertThat(condition).as(mono.getClass().getName()).isTrue();
|
|
||||||
Object value = ((Mono<?>) mono).block(Duration.ofSeconds(5));
|
Object value = ((Mono<?>) mono).block(Duration.ofSeconds(5));
|
||||||
assertThat(value.getClass()).isEqualTo(Foo.class);
|
assertThat(value.getClass()).isEqualTo(Pojo.class);
|
||||||
return (Foo) value;
|
return (Pojo) value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createAndBindToSingle() throws Exception {
|
void createAndBindToSingle() throws Exception {
|
||||||
MethodParameter parameter = this.testMethod
|
MethodParameter parameter = this.testMethod
|
||||||
.annotPresent(ModelAttribute.class).arg(Single.class, Foo.class);
|
.annotPresent(ModelAttribute.class).arg(Single.class, Pojo.class);
|
||||||
|
|
||||||
testBindFoo("fooSingle", parameter, single -> {
|
testBindPojo("pojoSingle", parameter, single -> {
|
||||||
boolean condition = single instanceof Single;
|
assertThat(single).isInstanceOf(Single.class);
|
||||||
assertThat(condition).as(single.getClass().getName()).isTrue();
|
|
||||||
Object value = ((Single<?>) single).blockingGet();
|
Object value = ((Single<?>) single).blockingGet();
|
||||||
assertThat(value.getClass()).isEqualTo(Foo.class);
|
assertThat(value.getClass()).isEqualTo(Pojo.class);
|
||||||
return (Foo) value;
|
return (Pojo) value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindExisting() throws Exception {
|
void createButDoNotBind() throws Exception {
|
||||||
Foo foo = new Foo();
|
MethodParameter parameter =
|
||||||
foo.setName("Jim");
|
this.testMethod.annotPresent(ModelAttribute.class).arg(NonBindingPojo.class);
|
||||||
this.bindContext.getModel().addAttribute(foo);
|
|
||||||
|
|
||||||
MethodParameter parameter = this.testMethod.annotNotPresent(ModelAttribute.class).arg(Foo.class);
|
createButDoNotBindToPojo("nonBindingPojo", parameter, value -> {
|
||||||
testBindFoo("foo", parameter, value -> {
|
assertThat(value).isInstanceOf(NonBindingPojo.class);
|
||||||
assertThat(value.getClass()).isEqualTo(Foo.class);
|
return (NonBindingPojo) value;
|
||||||
return (Foo) value;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
assertThat(this.bindContext.getModel().asMap().get("foo")).isSameAs(foo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindExistingMono() throws Exception {
|
void createButDoNotBindToMono() throws Exception {
|
||||||
Foo foo = new Foo();
|
MethodParameter parameter =
|
||||||
foo.setName("Jim");
|
this.testMethod.annotPresent(ModelAttribute.class).arg(Mono.class, NonBindingPojo.class);
|
||||||
this.bindContext.getModel().addAttribute("fooMono", Mono.just(foo));
|
|
||||||
|
|
||||||
MethodParameter parameter = this.testMethod.annotNotPresent(ModelAttribute.class).arg(Foo.class);
|
createButDoNotBindToPojo("nonBindingPojoMono", parameter, value -> {
|
||||||
testBindFoo("foo", parameter, value -> {
|
assertThat(value).isInstanceOf(Mono.class);
|
||||||
assertThat(value.getClass()).isEqualTo(Foo.class);
|
Object extractedValue = ((Mono<?>) value).block(Duration.ofSeconds(5));
|
||||||
return (Foo) value;
|
assertThat(extractedValue).isInstanceOf(NonBindingPojo.class);
|
||||||
|
return (NonBindingPojo) extractedValue;
|
||||||
});
|
});
|
||||||
|
|
||||||
assertThat(this.bindContext.getModel().asMap().get("foo")).isSameAs(foo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindExistingSingle() throws Exception {
|
void createButDoNotBindToSingle() throws Exception {
|
||||||
Foo foo = new Foo();
|
MethodParameter parameter =
|
||||||
foo.setName("Jim");
|
this.testMethod.annotPresent(ModelAttribute.class).arg(Single.class, NonBindingPojo.class);
|
||||||
this.bindContext.getModel().addAttribute("fooSingle", Single.just(foo));
|
|
||||||
|
|
||||||
MethodParameter parameter = this.testMethod.annotNotPresent(ModelAttribute.class).arg(Foo.class);
|
createButDoNotBindToPojo("nonBindingPojoSingle", parameter, value -> {
|
||||||
testBindFoo("foo", parameter, value -> {
|
assertThat(value).isInstanceOf(Single.class);
|
||||||
assertThat(value.getClass()).isEqualTo(Foo.class);
|
Object extractedValue = ((Single<?>) value).blockingGet();
|
||||||
return (Foo) value;
|
assertThat(extractedValue).isInstanceOf(NonBindingPojo.class);
|
||||||
|
return (NonBindingPojo) extractedValue;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
assertThat(this.bindContext.getModel().asMap().get("foo")).isSameAs(foo);
|
private void createButDoNotBindToPojo(String modelKey, MethodParameter methodParameter,
|
||||||
|
Function<Object, NonBindingPojo> valueExtractor) throws Exception {
|
||||||
|
|
||||||
|
Object value = createResolver()
|
||||||
|
.resolveArgument(methodParameter, this.bindContext, postForm("name=Enigma"))
|
||||||
|
.block(Duration.ZERO);
|
||||||
|
|
||||||
|
NonBindingPojo nonBindingPojo = valueExtractor.apply(value);
|
||||||
|
assertThat(nonBindingPojo).isNotNull();
|
||||||
|
assertThat(nonBindingPojo.getName()).isNull();
|
||||||
|
|
||||||
|
String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + modelKey;
|
||||||
|
|
||||||
|
Map<String, Object> model = bindContext.getModel().asMap();
|
||||||
|
assertThat(model).hasSize(2);
|
||||||
|
assertThat(model.get(modelKey)).isSameAs(nonBindingPojo);
|
||||||
|
assertThat(model.get(bindingResultKey)).isInstanceOf(BindingResult.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindExistingMonoToMono() throws Exception {
|
void bindExisting() throws Exception {
|
||||||
Foo foo = new Foo();
|
Pojo pojo = new Pojo();
|
||||||
foo.setName("Jim");
|
pojo.setName("Jim");
|
||||||
String modelKey = "fooMono";
|
this.bindContext.getModel().addAttribute(pojo);
|
||||||
this.bindContext.getModel().addAttribute(modelKey, Mono.just(foo));
|
|
||||||
|
MethodParameter parameter = this.testMethod.annotNotPresent(ModelAttribute.class).arg(Pojo.class);
|
||||||
|
testBindPojo("pojo", parameter, value -> {
|
||||||
|
assertThat(value.getClass()).isEqualTo(Pojo.class);
|
||||||
|
return (Pojo) value;
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThat(this.bindContext.getModel().asMap().get("pojo")).isSameAs(pojo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void bindExistingMono() throws Exception {
|
||||||
|
Pojo pojo = new Pojo();
|
||||||
|
pojo.setName("Jim");
|
||||||
|
this.bindContext.getModel().addAttribute("pojoMono", Mono.just(pojo));
|
||||||
|
|
||||||
|
MethodParameter parameter = this.testMethod.annotNotPresent(ModelAttribute.class).arg(Pojo.class);
|
||||||
|
testBindPojo("pojo", parameter, value -> {
|
||||||
|
assertThat(value.getClass()).isEqualTo(Pojo.class);
|
||||||
|
return (Pojo) value;
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThat(this.bindContext.getModel().asMap().get("pojo")).isSameAs(pojo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void bindExistingSingle() throws Exception {
|
||||||
|
Pojo pojo = new Pojo();
|
||||||
|
pojo.setName("Jim");
|
||||||
|
this.bindContext.getModel().addAttribute("pojoSingle", Single.just(pojo));
|
||||||
|
|
||||||
|
MethodParameter parameter = this.testMethod.annotNotPresent(ModelAttribute.class).arg(Pojo.class);
|
||||||
|
testBindPojo("pojo", parameter, value -> {
|
||||||
|
assertThat(value.getClass()).isEqualTo(Pojo.class);
|
||||||
|
return (Pojo) value;
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThat(this.bindContext.getModel().asMap().get("pojo")).isSameAs(pojo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void bindExistingMonoToMono() throws Exception {
|
||||||
|
Pojo pojo = new Pojo();
|
||||||
|
pojo.setName("Jim");
|
||||||
|
String modelKey = "pojoMono";
|
||||||
|
this.bindContext.getModel().addAttribute(modelKey, Mono.just(pojo));
|
||||||
|
|
||||||
MethodParameter parameter = this.testMethod
|
MethodParameter parameter = this.testMethod
|
||||||
.annotNotPresent(ModelAttribute.class).arg(Mono.class, Foo.class);
|
.annotNotPresent(ModelAttribute.class).arg(Mono.class, Pojo.class);
|
||||||
|
|
||||||
testBindFoo(modelKey, parameter, mono -> {
|
testBindPojo(modelKey, parameter, mono -> {
|
||||||
boolean condition = mono instanceof Mono;
|
assertThat(mono).isInstanceOf(Mono.class);
|
||||||
assertThat(condition).as(mono.getClass().getName()).isTrue();
|
|
||||||
Object value = ((Mono<?>) mono).block(Duration.ofSeconds(5));
|
Object value = ((Mono<?>) mono).block(Duration.ofSeconds(5));
|
||||||
assertThat(value.getClass()).isEqualTo(Foo.class);
|
assertThat(value.getClass()).isEqualTo(Pojo.class);
|
||||||
return (Foo) value;
|
return (Pojo) value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testBindFoo(String modelKey, MethodParameter param, Function<Object, Foo> valueExtractor)
|
private void testBindPojo(String modelKey, MethodParameter param, Function<Object, Pojo> valueExtractor)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
|
||||||
Object value = createResolver()
|
Object value = createResolver()
|
||||||
.resolveArgument(param, this.bindContext, postForm("name=Robert&age=25"))
|
.resolveArgument(param, this.bindContext, postForm("name=Robert&age=25"))
|
||||||
.block(Duration.ZERO);
|
.block(Duration.ZERO);
|
||||||
|
|
||||||
Foo foo = valueExtractor.apply(value);
|
Pojo pojo = valueExtractor.apply(value);
|
||||||
assertThat(foo.getName()).isEqualTo("Robert");
|
assertThat(pojo.getName()).isEqualTo("Robert");
|
||||||
assertThat(foo.getAge()).isEqualTo(25);
|
assertThat(pojo.getAge()).isEqualTo(25);
|
||||||
|
|
||||||
String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + modelKey;
|
String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + modelKey;
|
||||||
|
|
||||||
Map<String, Object> map = bindContext.getModel().asMap();
|
Map<String, Object> model = bindContext.getModel().asMap();
|
||||||
assertThat(map.size()).as(map.toString()).isEqualTo(2);
|
assertThat(model).hasSize(2);
|
||||||
assertThat(map.get(modelKey)).isSameAs(foo);
|
assertThat(model.get(modelKey)).isSameAs(pojo);
|
||||||
assertThat(map.get(bindingResultKey)).isNotNull();
|
assertThat(model.get(bindingResultKey)).isInstanceOf(BindingResult.class);
|
||||||
boolean condition = map.get(bindingResultKey) instanceof BindingResult;
|
|
||||||
assertThat(condition).isTrue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void validationError() throws Exception {
|
void validationErrorForPojo() throws Exception {
|
||||||
MethodParameter parameter = this.testMethod.annotNotPresent(ModelAttribute.class).arg(Foo.class);
|
MethodParameter parameter = this.testMethod.annotNotPresent(ModelAttribute.class).arg(Pojo.class);
|
||||||
testValidationError(parameter, Function.identity());
|
testValidationError(parameter, Function.identity());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void validationErrorToMono() throws Exception {
|
void validationErrorForMono() throws Exception {
|
||||||
MethodParameter parameter = this.testMethod
|
MethodParameter parameter = this.testMethod
|
||||||
.annotNotPresent(ModelAttribute.class).arg(Mono.class, Foo.class);
|
.annotNotPresent(ModelAttribute.class).arg(Mono.class, Pojo.class);
|
||||||
|
|
||||||
testValidationError(parameter,
|
testValidationError(parameter,
|
||||||
resolvedArgumentMono -> {
|
resolvedArgumentMono -> {
|
||||||
Object value = resolvedArgumentMono.block(Duration.ofSeconds(5));
|
Object value = resolvedArgumentMono.block(Duration.ofSeconds(5));
|
||||||
assertThat(value).isNotNull();
|
assertThat(value).isInstanceOf(Mono.class);
|
||||||
boolean condition = value instanceof Mono;
|
|
||||||
assertThat(condition).isTrue();
|
|
||||||
return (Mono<?>) value;
|
return (Mono<?>) value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void validationErrorToSingle() throws Exception {
|
void validationErrorForSingle() throws Exception {
|
||||||
MethodParameter parameter = this.testMethod
|
MethodParameter parameter = this.testMethod
|
||||||
.annotPresent(ModelAttribute.class).arg(Single.class, Foo.class);
|
.annotPresent(ModelAttribute.class).arg(Single.class, Pojo.class);
|
||||||
|
|
||||||
testValidationError(parameter,
|
testValidationError(parameter,
|
||||||
resolvedArgumentMono -> {
|
resolvedArgumentMono -> {
|
||||||
Object value = resolvedArgumentMono.block(Duration.ofSeconds(5));
|
Object value = resolvedArgumentMono.block(Duration.ofSeconds(5));
|
||||||
assertThat(value).isNotNull();
|
assertThat(value).isInstanceOf(Single.class);
|
||||||
boolean condition = value instanceof Single;
|
|
||||||
assertThat(condition).isTrue();
|
|
||||||
return Mono.from(((Single<?>) value).toFlowable());
|
return Mono.from(((Single<?>) value).toFlowable());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testValidationError(MethodParameter param, Function<Mono<?>, Mono<?>> valueMonoExtractor)
|
@Test
|
||||||
|
void validationErrorWithoutBindingForPojo() throws Exception {
|
||||||
|
MethodParameter parameter = this.testMethod.annotPresent(ModelAttribute.class).arg(ValidatedPojo.class);
|
||||||
|
testValidationErrorWithoutBinding(parameter, Function.identity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validationErrorWithoutBindingForMono() throws Exception {
|
||||||
|
MethodParameter parameter = this.testMethod.annotPresent(ModelAttribute.class).arg(Mono.class, ValidatedPojo.class);
|
||||||
|
|
||||||
|
testValidationErrorWithoutBinding(parameter, resolvedArgumentMono -> {
|
||||||
|
Object value = resolvedArgumentMono.block(Duration.ofSeconds(5));
|
||||||
|
assertThat(value).isInstanceOf(Mono.class);
|
||||||
|
return (Mono<?>) value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validationErrorWithoutBindingForSingle() throws Exception {
|
||||||
|
MethodParameter parameter = this.testMethod.annotPresent(ModelAttribute.class).arg(Single.class, ValidatedPojo.class);
|
||||||
|
|
||||||
|
testValidationErrorWithoutBinding(parameter, resolvedArgumentMono -> {
|
||||||
|
Object value = resolvedArgumentMono.block(Duration.ofSeconds(5));
|
||||||
|
assertThat(value).isInstanceOf(Single.class);
|
||||||
|
return Mono.from(((Single<?>) value).toFlowable());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testValidationError(MethodParameter parameter, Function<Mono<?>, Mono<?>> valueMonoExtractor)
|
||||||
throws URISyntaxException {
|
throws URISyntaxException {
|
||||||
|
|
||||||
ServerWebExchange exchange = postForm("age=invalid");
|
testValidationError(parameter, valueMonoExtractor, "age=invalid", "age", "invalid");
|
||||||
Mono<?> mono = createResolver().resolveArgument(param, this.bindContext, exchange);
|
}
|
||||||
|
|
||||||
|
private void testValidationErrorWithoutBinding(MethodParameter parameter, Function<Mono<?>, Mono<?>> valueMonoExtractor)
|
||||||
|
throws URISyntaxException {
|
||||||
|
|
||||||
|
testValidationError(parameter, valueMonoExtractor, "name=Enigma", "name", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testValidationError(MethodParameter param, Function<Mono<?>, Mono<?>> valueMonoExtractor,
|
||||||
|
String formData, String field, String rejectedValue) throws URISyntaxException {
|
||||||
|
|
||||||
|
Mono<?> mono = createResolver().resolveArgument(param, this.bindContext, postForm(formData));
|
||||||
mono = valueMonoExtractor.apply(mono);
|
mono = valueMonoExtractor.apply(mono);
|
||||||
|
|
||||||
StepVerifier.create(mono)
|
StepVerifier.create(mono)
|
||||||
.consumeErrorWith(ex -> {
|
.consumeErrorWith(ex -> {
|
||||||
boolean condition = ex instanceof WebExchangeBindException;
|
assertThat(ex).isInstanceOf(WebExchangeBindException.class);
|
||||||
assertThat(condition).isTrue();
|
|
||||||
WebExchangeBindException bindException = (WebExchangeBindException) ex;
|
WebExchangeBindException bindException = (WebExchangeBindException) ex;
|
||||||
assertThat(bindException.getErrorCount()).isEqualTo(1);
|
assertThat(bindException.getErrorCount()).isEqualTo(1);
|
||||||
assertThat(bindException.hasFieldErrors("age")).isTrue();
|
assertThat(bindException.hasFieldErrors(field)).isTrue();
|
||||||
|
assertThat(bindException.getFieldError(field).getRejectedValue()).isEqualTo(rejectedValue);
|
||||||
})
|
})
|
||||||
.verify();
|
.verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindDataClass() throws Exception {
|
void bindDataClass() throws Exception {
|
||||||
testBindBar(this.testMethod.annotNotPresent(ModelAttribute.class).arg(Bar.class));
|
MethodParameter parameter = this.testMethod.annotNotPresent(ModelAttribute.class).arg(DataClass.class);
|
||||||
}
|
|
||||||
|
|
||||||
private void testBindBar(MethodParameter param) throws Exception {
|
|
||||||
Object value = createResolver()
|
Object value = createResolver()
|
||||||
.resolveArgument(param, this.bindContext, postForm("name=Robert&age=25&count=1"))
|
.resolveArgument(parameter, this.bindContext, postForm("name=Robert&age=25&count=1"))
|
||||||
.block(Duration.ZERO);
|
.block(Duration.ZERO);
|
||||||
|
|
||||||
Bar bar = (Bar) value;
|
DataClass dataClass = (DataClass) value;
|
||||||
assertThat(bar.getName()).isEqualTo("Robert");
|
assertThat(dataClass.getName()).isEqualTo("Robert");
|
||||||
assertThat(bar.getAge()).isEqualTo(25);
|
assertThat(dataClass.getAge()).isEqualTo(25);
|
||||||
assertThat(bar.getCount()).isEqualTo(1);
|
assertThat(dataClass.getCount()).isEqualTo(1);
|
||||||
|
|
||||||
String key = "bar";
|
String modelKey = "dataClass";
|
||||||
String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + key;
|
String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + modelKey;
|
||||||
|
|
||||||
Map<String, Object> map = bindContext.getModel().asMap();
|
Map<String, Object> model = bindContext.getModel().asMap();
|
||||||
assertThat(map.size()).as(map.toString()).isEqualTo(2);
|
assertThat(model).hasSize(2);
|
||||||
assertThat(map.get(key)).isSameAs(bar);
|
assertThat(model.get(modelKey)).isSameAs(dataClass);
|
||||||
assertThat(map.get(bindingResultKey)).isNotNull();
|
assertThat(model.get(bindingResultKey)).isInstanceOf(BindingResult.class);
|
||||||
boolean condition = map.get(bindingResultKey) instanceof BindingResult;
|
|
||||||
assertThat(condition).isTrue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: SPR-15871, SPR-15542
|
// TODO: SPR-15871, SPR-15542
|
||||||
@@ -320,31 +411,30 @@ public class ModelAttributeMethodArgumentResolverTests {
|
|||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
void handle(
|
void handle(
|
||||||
@ModelAttribute @Validated Foo foo,
|
@ModelAttribute @Validated Pojo pojo,
|
||||||
@ModelAttribute @Validated Mono<Foo> mono,
|
@ModelAttribute @Validated Mono<Pojo> mono,
|
||||||
@ModelAttribute @Validated Single<Foo> single,
|
@ModelAttribute @Validated Single<Pojo> single,
|
||||||
Foo fooNotAnnotated,
|
@ModelAttribute(binding = false) NonBindingPojo nonBindingPojo,
|
||||||
|
@ModelAttribute(binding = false) Mono<NonBindingPojo> monoNonBindingPojo,
|
||||||
|
@ModelAttribute(binding = false) Single<NonBindingPojo> singleNonBindingPojo,
|
||||||
|
@ModelAttribute(binding = false) @Validated ValidatedPojo validatedPojo,
|
||||||
|
@ModelAttribute(binding = false) @Validated Mono<ValidatedPojo> monoValidatedPojo,
|
||||||
|
@ModelAttribute(binding = false) @Validated Single<ValidatedPojo> singleValidatedPojo,
|
||||||
|
Pojo pojoNotAnnotated,
|
||||||
String stringNotAnnotated,
|
String stringNotAnnotated,
|
||||||
Mono<Foo> monoNotAnnotated,
|
Mono<Pojo> monoNotAnnotated,
|
||||||
Mono<String> monoStringNotAnnotated,
|
Mono<String> monoStringNotAnnotated,
|
||||||
Bar barNotAnnotated) {
|
DataClass dataClassNotAnnotated) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static class Foo {
|
private static class Pojo {
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
private int age;
|
private int age;
|
||||||
|
|
||||||
public Foo() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Foo(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
@@ -364,7 +454,48 @@ public class ModelAttributeMethodArgumentResolverTests {
|
|||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static class Bar {
|
private static class NonBindingPojo {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "NonBindingPojo [name=" + name + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static class ValidatedPojo {
|
||||||
|
|
||||||
|
@NotEmpty
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ValidatedPojo [name=" + name + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static class DataClass {
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
@@ -372,7 +503,7 @@ public class ModelAttributeMethodArgumentResolverTests {
|
|||||||
|
|
||||||
private int count;
|
private int count;
|
||||||
|
|
||||||
public Bar(String name, int age) {
|
public DataClass(String name, int age) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.age = age;
|
this.age = age;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user