Skip to content

Commit 5f98afc

Browse files
makingpoutsma
authored andcommitted
Introduce functional factory methods in Validator
This commit introduces `of` method in `Validator` to provide a way to create a validator for the specific type `<T>` using `BiConsumer<T, Errors>` and define the validator in a functional way. This also eliminates the boilerplate for implementing the `supports` method.
1 parent 7492c0e commit 5f98afc

File tree

3 files changed

+65
-72
lines changed

3 files changed

+65
-72
lines changed

spring-context/src/main/java/org/springframework/validation/Validator.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,10 @@
1616

1717
package org.springframework.validation;
1818

19+
import java.util.function.BiConsumer;
20+
21+
import org.springframework.util.Assert;
22+
1923
/**
2024
* A validator for application-specific objects.
2125
*
@@ -59,6 +63,7 @@
5963
* application.
6064
*
6165
* @author Rod Johnson
66+
* @author Toshiaki Maki
6267
* @see SmartValidator
6368
* @see Errors
6469
* @see ValidationUtils
@@ -92,4 +97,38 @@ public interface Validator {
9297
*/
9398
void validate(Object target, Errors errors);
9499

100+
/**
101+
* Takes the {@link BiConsumer} containing the validation logic for the specific type
102+
* <code>&lt;T&gt;</code> and returns the {@link Validator} instance.<br>
103+
* This validator implements the <i>typical</i> {@link #supports(Class)} method
104+
* for the given <code>&lt;T&gt;</code>.<br>
105+
*
106+
* By using this method, a {@link Validator} can be implemented as follows:
107+
*
108+
* <pre class="code">Validator passwordEqualsValidator = Validator.of(PasswordResetForm.class, (form, errors) -> {
109+
* if (!Objects.equals(form.getPassword(), form.getConfirmPassword())) {
110+
* errors.rejectValue("confirmPassword",
111+
* "PasswordEqualsValidator.passwordResetForm.password",
112+
* "password and confirm password must be same.");
113+
* }
114+
* });</pre>
115+
* @param targetClass the class of the object that is to be validated
116+
* @param delegate the validation logic to delegate for the specific type <code>&lt;T&gt;</code>
117+
* @param <T> the type of the object that is to be validated
118+
* @return the {@link Validator} instance
119+
*/
120+
static <T> Validator of(Class<T> targetClass, BiConsumer<T, Errors> delegate) {
121+
Assert.notNull(targetClass, "'targetClass' must not be null.");
122+
return new Validator() {
123+
@Override
124+
public boolean supports(Class<?> clazz) {
125+
return targetClass.isAssignableFrom(clazz);
126+
}
127+
128+
@Override
129+
public void validate(Object target, Errors errors) {
130+
delegate.accept(targetClass.cast(target), errors);
131+
}
132+
};
133+
}
95134
}

spring-context/src/test/java/org/springframework/validation/DataBinderTests.java

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,16 @@
8282
*/
8383
class DataBinderTests {
8484

85+
Validator spouseValidator = Validator.of(TestBean.class, (tb, errors) -> {
86+
if (tb == null || "XXX".equals(tb.getName())) {
87+
errors.rejectValue("", "SPOUSE_NOT_AVAILABLE");
88+
return;
89+
}
90+
if (tb.getAge() < 32) {
91+
errors.rejectValue("age", "TOO_YOUNG", "simply too young");
92+
}
93+
});
94+
8595
@Test
8696
void bindingNoErrors() throws BindException {
8797
TestBean rod = new TestBean();
@@ -1144,7 +1154,6 @@ void validatorNoErrors() throws Exception {
11441154
errors.setNestedPath("spouse");
11451155
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
11461156
assertThat(errors.getFieldValue("age")).isEqualTo("argh");
1147-
Validator spouseValidator = new SpouseValidator();
11481157
spouseValidator.validate(tb.getSpouse(), errors);
11491158

11501159
errors.setNestedPath("");
@@ -1195,7 +1204,6 @@ void validatorWithErrors() {
11951204

11961205
errors.setNestedPath("spouse.");
11971206
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
1198-
Validator spouseValidator = new SpouseValidator();
11991207
spouseValidator.validate(tb.getSpouse(), errors);
12001208

12011209
errors.setNestedPath("");
@@ -1267,7 +1275,6 @@ void validatorWithErrorsAndCodesPrefix() {
12671275

12681276
errors.setNestedPath("spouse.");
12691277
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
1270-
Validator spouseValidator = new SpouseValidator();
12711278
spouseValidator.validate(tb.getSpouse(), errors);
12721279

12731280
errors.setNestedPath("");
@@ -1332,7 +1339,6 @@ void validatorWithNestedObjectNull() {
13321339
testValidator.validate(tb, errors);
13331340
errors.setNestedPath("spouse.");
13341341
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
1335-
Validator spouseValidator = new SpouseValidator();
13361342
spouseValidator.validate(tb.getSpouse(), errors);
13371343
errors.setNestedPath("");
13381344

@@ -1348,7 +1354,6 @@ void nestedValidatorWithoutNestedPath() {
13481354
TestBean tb = new TestBean();
13491355
tb.setName("XXX");
13501356
Errors errors = new BeanPropertyBindingResult(tb, "tb");
1351-
Validator spouseValidator = new SpouseValidator();
13521357
spouseValidator.validate(tb, errors);
13531358

13541359
assertThat(errors.hasGlobalErrors()).isTrue();
@@ -2160,28 +2165,6 @@ public void validate(@Nullable Object obj, Errors errors) {
21602165
}
21612166
}
21622167

2163-
2164-
private static class SpouseValidator implements Validator {
2165-
2166-
@Override
2167-
public boolean supports(Class<?> clazz) {
2168-
return TestBean.class.isAssignableFrom(clazz);
2169-
}
2170-
2171-
@Override
2172-
public void validate(@Nullable Object obj, Errors errors) {
2173-
TestBean tb = (TestBean) obj;
2174-
if (tb == null || "XXX".equals(tb.getName())) {
2175-
errors.rejectValue("", "SPOUSE_NOT_AVAILABLE");
2176-
return;
2177-
}
2178-
if (tb.getAge() < 32) {
2179-
errors.rejectValue("age", "TOO_YOUNG", "simply too young");
2180-
}
2181-
}
2182-
}
2183-
2184-
21852168
@SuppressWarnings("unused")
21862169
private static class GrowingList<E> extends AbstractList<E> {
21872170

spring-context/src/test/java/org/springframework/validation/ValidationUtilsTests.java

Lines changed: 15 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@
1919
import org.junit.jupiter.api.Test;
2020

2121
import org.springframework.beans.testfixture.beans.TestBean;
22-
import org.springframework.lang.Nullable;
2322

2423
import static org.assertj.core.api.Assertions.assertThat;
2524
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -34,6 +33,10 @@
3433
*/
3534
public class ValidationUtilsTests {
3635

36+
Validator emptyValidator = Validator.of(TestBean.class, (testBean, errors) -> ValidationUtils.rejectIfEmpty(errors, "name", "EMPTY", "You must enter a name!"));
37+
38+
Validator emptyOrWhitespaceValidator = Validator.of(TestBean.class, (testBean, errors) -> ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "EMPTY_OR_WHITESPACE", "You must enter a name!"));
39+
3740
@Test
3841
public void testInvokeValidatorWithNullValidator() throws Exception {
3942
TestBean tb = new TestBean();
@@ -46,14 +49,14 @@ public void testInvokeValidatorWithNullValidator() throws Exception {
4649
public void testInvokeValidatorWithNullErrors() throws Exception {
4750
TestBean tb = new TestBean();
4851
assertThatIllegalArgumentException().isThrownBy(() ->
49-
ValidationUtils.invokeValidator(new EmptyValidator(), tb, null));
52+
ValidationUtils.invokeValidator(emptyValidator, tb, null));
5053
}
5154

5255
@Test
5356
public void testInvokeValidatorSunnyDay() throws Exception {
5457
TestBean tb = new TestBean();
5558
Errors errors = new BeanPropertyBindingResult(tb, "tb");
56-
ValidationUtils.invokeValidator(new EmptyValidator(), tb, errors);
59+
ValidationUtils.invokeValidator(emptyValidator, tb, errors);
5760
assertThat(errors.hasFieldErrors("name")).isTrue();
5861
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY");
5962
}
@@ -62,24 +65,22 @@ public void testInvokeValidatorSunnyDay() throws Exception {
6265
public void testValidationUtilsSunnyDay() throws Exception {
6366
TestBean tb = new TestBean("");
6467

65-
Validator testValidator = new EmptyValidator();
6668
tb.setName(" ");
6769
Errors errors = new BeanPropertyBindingResult(tb, "tb");
68-
testValidator.validate(tb, errors);
70+
emptyValidator.validate(tb, errors);
6971
assertThat(errors.hasFieldErrors("name")).isFalse();
7072

7173
tb.setName("Roddy");
7274
errors = new BeanPropertyBindingResult(tb, "tb");
73-
testValidator.validate(tb, errors);
75+
emptyValidator.validate(tb, errors);
7476
assertThat(errors.hasFieldErrors("name")).isFalse();
7577
}
7678

7779
@Test
7880
public void testValidationUtilsNull() throws Exception {
7981
TestBean tb = new TestBean();
8082
Errors errors = new BeanPropertyBindingResult(tb, "tb");
81-
Validator testValidator = new EmptyValidator();
82-
testValidator.validate(tb, errors);
83+
emptyValidator.validate(tb, errors);
8384
assertThat(errors.hasFieldErrors("name")).isTrue();
8485
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY");
8586
}
@@ -88,8 +89,7 @@ public void testValidationUtilsNull() throws Exception {
8889
public void testValidationUtilsEmpty() throws Exception {
8990
TestBean tb = new TestBean("");
9091
Errors errors = new BeanPropertyBindingResult(tb, "tb");
91-
Validator testValidator = new EmptyValidator();
92-
testValidator.validate(tb, errors);
92+
emptyValidator.validate(tb, errors);
9393
assertThat(errors.hasFieldErrors("name")).isTrue();
9494
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY");
9595
}
@@ -115,32 +115,31 @@ public void testValidationUtilsEmptyVariants() {
115115
@Test
116116
public void testValidationUtilsEmptyOrWhitespace() throws Exception {
117117
TestBean tb = new TestBean();
118-
Validator testValidator = new EmptyOrWhitespaceValidator();
119118

120119
// Test null
121120
Errors errors = new BeanPropertyBindingResult(tb, "tb");
122-
testValidator.validate(tb, errors);
121+
emptyOrWhitespaceValidator.validate(tb, errors);
123122
assertThat(errors.hasFieldErrors("name")).isTrue();
124123
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY_OR_WHITESPACE");
125124

126125
// Test empty String
127126
tb.setName("");
128127
errors = new BeanPropertyBindingResult(tb, "tb");
129-
testValidator.validate(tb, errors);
128+
emptyOrWhitespaceValidator.validate(tb, errors);
130129
assertThat(errors.hasFieldErrors("name")).isTrue();
131130
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY_OR_WHITESPACE");
132131

133132
// Test whitespace String
134133
tb.setName(" ");
135134
errors = new BeanPropertyBindingResult(tb, "tb");
136-
testValidator.validate(tb, errors);
135+
emptyOrWhitespaceValidator.validate(tb, errors);
137136
assertThat(errors.hasFieldErrors("name")).isTrue();
138137
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY_OR_WHITESPACE");
139138

140139
// Test OK
141140
tb.setName("Roddy");
142141
errors = new BeanPropertyBindingResult(tb, "tb");
143-
testValidator.validate(tb, errors);
142+
emptyOrWhitespaceValidator.validate(tb, errors);
144143
assertThat(errors.hasFieldErrors("name")).isFalse();
145144
}
146145

@@ -163,32 +162,4 @@ public void testValidationUtilsEmptyOrWhitespaceVariants() {
163162
assertThat(errors.getFieldError("name").getDefaultMessage()).isEqualTo("msg");
164163
}
165164

166-
167-
private static class EmptyValidator implements Validator {
168-
169-
@Override
170-
public boolean supports(Class<?> clazz) {
171-
return TestBean.class.isAssignableFrom(clazz);
172-
}
173-
174-
@Override
175-
public void validate(@Nullable Object obj, Errors errors) {
176-
ValidationUtils.rejectIfEmpty(errors, "name", "EMPTY", "You must enter a name!");
177-
}
178-
}
179-
180-
181-
private static class EmptyOrWhitespaceValidator implements Validator {
182-
183-
@Override
184-
public boolean supports(Class<?> clazz) {
185-
return TestBean.class.isAssignableFrom(clazz);
186-
}
187-
188-
@Override
189-
public void validate(@Nullable Object obj, Errors errors) {
190-
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "EMPTY_OR_WHITESPACE", "You must enter a name!");
191-
}
192-
}
193-
194165
}

0 commit comments

Comments
 (0)