Skip to content

Commit e779fb0

Browse files
committed
Validate constructor bound config props that implement Validator
Closes gh-33669
1 parent 836d88c commit e779fb0

File tree

2 files changed

+74
-2
lines changed

2 files changed

+74
-2
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.springframework.core.annotation.MergedAnnotations;
5252
import org.springframework.core.convert.ConversionService;
5353
import org.springframework.core.env.PropertySources;
54+
import org.springframework.validation.Errors;
5455
import org.springframework.validation.Validator;
5556
import org.springframework.validation.annotation.Validated;
5657

@@ -136,6 +137,7 @@ private IgnoreTopLevelConverterNotFoundBindHandler getHandler() {
136137
: new IgnoreTopLevelConverterNotFoundBindHandler();
137138
}
138139

140+
@SuppressWarnings("unchecked")
139141
private List<Validator> getValidators(Bindable<?> target) {
140142
List<Validator> validators = new ArrayList<>(3);
141143
if (this.configurationPropertiesValidator != null) {
@@ -144,8 +146,13 @@ private List<Validator> getValidators(Bindable<?> target) {
144146
if (this.jsr303Present && target.getAnnotation(Validated.class) != null) {
145147
validators.add(getJsr303Validator());
146148
}
147-
if (target.getValue() != null && target.getValue().get() instanceof Validator) {
148-
validators.add((Validator) target.getValue().get());
149+
if (target.getValue() != null) {
150+
if (target.getValue().get() instanceof Validator) {
151+
validators.add((Validator) target.getValue().get());
152+
}
153+
}
154+
else if (Validator.class.isAssignableFrom(target.getType().resolve())) {
155+
validators.add(new SelfValidatingConstructorBoundBindableValidator((Bindable<? extends Validator>) target));
149156
}
150157
return validators;
151158
}
@@ -258,4 +265,28 @@ private boolean isConfigurationProperties(Class<?> target) {
258265

259266
}
260267

268+
/**
269+
* A {@code Validator} for a constructor-bound {@code Bindable} where the type being
270+
* bound is itself a {@code Validator} implementation.
271+
*/
272+
static class SelfValidatingConstructorBoundBindableValidator implements Validator {
273+
274+
private final Bindable<? extends Validator> bindable;
275+
276+
SelfValidatingConstructorBoundBindableValidator(Bindable<? extends Validator> bindable) {
277+
this.bindable = bindable;
278+
}
279+
280+
@Override
281+
public boolean supports(Class<?> clazz) {
282+
return clazz.isAssignableFrom(this.bindable.getType().resolve());
283+
}
284+
285+
@Override
286+
public void validate(Object target, Errors errors) {
287+
((Validator) target).validate(target, errors);
288+
}
289+
290+
}
291+
261292
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,16 @@ void loadWhenConfigurationPropertiesIsAlsoValidatorShouldApplyValidator() {
731731
});
732732
}
733733

734+
@Test
735+
void loadWhenConstructorBoundConfigurationPropertiesIsAlsoValidatorShouldApplyValidator() {
736+
assertThatExceptionOfType(Exception.class)
737+
.isThrownBy(() -> load(ValidatorConstructorBoundPropertiesConfiguration.class))
738+
.satisfies((ex) -> {
739+
assertThat(ex).hasCauseInstanceOf(BindException.class);
740+
assertThat(ex.getCause()).hasCauseExactlyInstanceOf(BindValidationException.class);
741+
});
742+
}
743+
734744
@Test
735745
void loadWhenConfigurationPropertiesWithValidDefaultValuesShouldNotFail() {
736746
AnnotationConfigApplicationContext context = load(ValidatorPropertiesWithDefaultValues.class);
@@ -2025,6 +2035,37 @@ void setFoo(String foo) {
20252035

20262036
}
20272037

2038+
@EnableConfigurationProperties(ValidatorConstructorBoundProperties.class)
2039+
static class ValidatorConstructorBoundPropertiesConfiguration {
2040+
2041+
}
2042+
2043+
@ConstructorBinding
2044+
@ConfigurationProperties
2045+
static class ValidatorConstructorBoundProperties implements Validator {
2046+
2047+
private final String foo;
2048+
2049+
ValidatorConstructorBoundProperties(String foo) {
2050+
this.foo = foo;
2051+
}
2052+
2053+
@Override
2054+
public boolean supports(Class<?> type) {
2055+
return type == ValidatorConstructorBoundProperties.class;
2056+
}
2057+
2058+
@Override
2059+
public void validate(Object target, Errors errors) {
2060+
ValidationUtils.rejectIfEmpty(errors, "foo", "TEST1");
2061+
}
2062+
2063+
String getFoo() {
2064+
return this.foo;
2065+
}
2066+
2067+
}
2068+
20282069
@EnableConfigurationProperties
20292070
@ConfigurationProperties(prefix = "test")
20302071
static class WithSetterThatThrowsValidationExceptionProperties {

0 commit comments

Comments
 (0)