Align cascading of config prop validation with bean validation spec

Closes gh-40345
This commit is contained in:
Andy Wilkinson 2024-06-20 12:14:41 +01:00
parent d9e9a46189
commit 7701201bc3
4 changed files with 16 additions and 18 deletions

View File

@ -1178,7 +1178,7 @@ include-code::MyProperties[]
TIP: You can also trigger validation by annotating the `@Bean` method that creates the configuration properties with `@Validated`.
To ensure that validation is always triggered for nested properties, even when no properties are found, the associated field must be annotated with `@Valid`.
To cascade validation to nested properties the associated field must be annotated with `@Valid`.
The following example builds on the preceding `MyProperties` example:
include-code::nested/MyProperties[]

View File

@ -76,8 +76,6 @@ class ConfigurationPropertiesBinder {
private final boolean jsr303Present;
private volatile Validator jsr303Validator;
private volatile Binder binder;
ConfigurationPropertiesBinder(ApplicationContext applicationContext) {
@ -141,7 +139,7 @@ class ConfigurationPropertiesBinder {
validators.add(this.configurationPropertiesValidator);
}
if (this.jsr303Present && target.getAnnotation(Validated.class) != null) {
validators.add(getJsr303Validator());
validators.add(getJsr303Validator(target.getType().resolve()));
}
Validator selfValidator = getSelfValidator(target);
if (selfValidator != null) {
@ -162,11 +160,8 @@ class ConfigurationPropertiesBinder {
return null;
}
private Validator getJsr303Validator() {
if (this.jsr303Validator == null) {
this.jsr303Validator = new ConfigurationPropertiesJsr303Validator(this.applicationContext);
}
return this.jsr303Validator;
private Validator getJsr303Validator(Class<?> type) {
return new ConfigurationPropertiesJsr303Validator(this.applicationContext, type);
}
private List<ConfigurationPropertiesBindHandlerAdvisor> getBindHandlerAdvisors() {

View File

@ -37,13 +37,16 @@ final class ConfigurationPropertiesJsr303Validator implements Validator {
private final Delegate delegate;
ConfigurationPropertiesJsr303Validator(ApplicationContext applicationContext) {
private final Class<?> validatedType;
ConfigurationPropertiesJsr303Validator(ApplicationContext applicationContext, Class<?> validatedType) {
this.delegate = new Delegate(applicationContext);
this.validatedType = validatedType;
}
@Override
public boolean supports(Class<?> type) {
return this.delegate.supports(type);
return this.validatedType.equals(type) && this.delegate.supports(type);
}
@Override

View File

@ -106,6 +106,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatException;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.entry;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
@ -525,10 +526,9 @@ class ConfigurationPropertiesTests {
}
@Test
void loadWhenJsr303ConstraintDoesNotMatchOnNestedThatIsNotDirectlyAnnotatedShouldFail() {
assertThatExceptionOfType(ConfigurationPropertiesBindException.class)
.isThrownBy(() -> load(ValidatedNestedJsr303Properties.class, "properties.description="))
.withCauseInstanceOf(BindException.class);
void loadWhenJsr303ConstraintDoesNotMatchOnNestedThatIsNotAnnotatedWithValidShouldNotFail() {
assertThatNoException()
.isThrownBy(() -> load(ValidatedNestedJsr303Properties.class, "properties.description="));
}
@Test
@ -1837,7 +1837,7 @@ class ConfigurationPropertiesTests {
@Validated
static class ValidatedNestedJsr303Properties {
private Jsr303Properties properties;
private final Jsr303Properties properties = new Jsr303Properties();
Jsr303Properties getProperties() {
return this.properties;
@ -1851,9 +1851,9 @@ class ConfigurationPropertiesTests {
static class ValidatedValidNestedJsr303Properties {
@Valid
private final List<Jsr303Properties> properties = Collections.singletonList(new Jsr303Properties());
private final Jsr303Properties properties = new Jsr303Properties();
List<Jsr303Properties> getProperties() {
Jsr303Properties getProperties() {
return this.properties;
}