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.
This commit is contained in:
parent
7492c0ea03
commit
5f98afc180
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2021 the original author or authors.
|
* Copyright 2002-2023 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.
|
||||||
|
|
@ -16,6 +16,10 @@
|
||||||
|
|
||||||
package org.springframework.validation;
|
package org.springframework.validation;
|
||||||
|
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A validator for application-specific objects.
|
* A validator for application-specific objects.
|
||||||
*
|
*
|
||||||
|
|
@ -59,6 +63,7 @@ package org.springframework.validation;
|
||||||
* application.
|
* application.
|
||||||
*
|
*
|
||||||
* @author Rod Johnson
|
* @author Rod Johnson
|
||||||
|
* @author Toshiaki Maki
|
||||||
* @see SmartValidator
|
* @see SmartValidator
|
||||||
* @see Errors
|
* @see Errors
|
||||||
* @see ValidationUtils
|
* @see ValidationUtils
|
||||||
|
|
@ -92,4 +97,38 @@ public interface Validator {
|
||||||
*/
|
*/
|
||||||
void validate(Object target, Errors errors);
|
void validate(Object target, Errors errors);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes the {@link BiConsumer} containing the validation logic for the specific type
|
||||||
|
* <code><T></code> and returns the {@link Validator} instance.<br>
|
||||||
|
* This validator implements the <i>typical</i> {@link #supports(Class)} method
|
||||||
|
* for the given <code><T></code>.<br>
|
||||||
|
*
|
||||||
|
* By using this method, a {@link Validator} can be implemented as follows:
|
||||||
|
*
|
||||||
|
* <pre class="code">Validator passwordEqualsValidator = Validator.of(PasswordResetForm.class, (form, errors) -> {
|
||||||
|
* if (!Objects.equals(form.getPassword(), form.getConfirmPassword())) {
|
||||||
|
* errors.rejectValue("confirmPassword",
|
||||||
|
* "PasswordEqualsValidator.passwordResetForm.password",
|
||||||
|
* "password and confirm password must be same.");
|
||||||
|
* }
|
||||||
|
* });</pre>
|
||||||
|
* @param targetClass the class of the object that is to be validated
|
||||||
|
* @param delegate the validation logic to delegate for the specific type <code><T></code>
|
||||||
|
* @param <T> the type of the object that is to be validated
|
||||||
|
* @return the {@link Validator} instance
|
||||||
|
*/
|
||||||
|
static <T> Validator of(Class<T> targetClass, BiConsumer<T, Errors> delegate) {
|
||||||
|
Assert.notNull(targetClass, "'targetClass' must not be null.");
|
||||||
|
return new Validator() {
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> clazz) {
|
||||||
|
return targetClass.isAssignableFrom(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validate(Object target, Errors errors) {
|
||||||
|
delegate.accept(targetClass.cast(target), errors);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,16 @@ import static org.assertj.core.api.Assertions.entry;
|
||||||
*/
|
*/
|
||||||
class DataBinderTests {
|
class DataBinderTests {
|
||||||
|
|
||||||
|
Validator spouseValidator = Validator.of(TestBean.class, (tb, errors) -> {
|
||||||
|
if (tb == null || "XXX".equals(tb.getName())) {
|
||||||
|
errors.rejectValue("", "SPOUSE_NOT_AVAILABLE");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (tb.getAge() < 32) {
|
||||||
|
errors.rejectValue("age", "TOO_YOUNG", "simply too young");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void bindingNoErrors() throws BindException {
|
void bindingNoErrors() throws BindException {
|
||||||
TestBean rod = new TestBean();
|
TestBean rod = new TestBean();
|
||||||
|
|
@ -1144,7 +1154,6 @@ class DataBinderTests {
|
||||||
errors.setNestedPath("spouse");
|
errors.setNestedPath("spouse");
|
||||||
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
|
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
|
||||||
assertThat(errors.getFieldValue("age")).isEqualTo("argh");
|
assertThat(errors.getFieldValue("age")).isEqualTo("argh");
|
||||||
Validator spouseValidator = new SpouseValidator();
|
|
||||||
spouseValidator.validate(tb.getSpouse(), errors);
|
spouseValidator.validate(tb.getSpouse(), errors);
|
||||||
|
|
||||||
errors.setNestedPath("");
|
errors.setNestedPath("");
|
||||||
|
|
@ -1195,7 +1204,6 @@ class DataBinderTests {
|
||||||
|
|
||||||
errors.setNestedPath("spouse.");
|
errors.setNestedPath("spouse.");
|
||||||
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
|
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
|
||||||
Validator spouseValidator = new SpouseValidator();
|
|
||||||
spouseValidator.validate(tb.getSpouse(), errors);
|
spouseValidator.validate(tb.getSpouse(), errors);
|
||||||
|
|
||||||
errors.setNestedPath("");
|
errors.setNestedPath("");
|
||||||
|
|
@ -1267,7 +1275,6 @@ class DataBinderTests {
|
||||||
|
|
||||||
errors.setNestedPath("spouse.");
|
errors.setNestedPath("spouse.");
|
||||||
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
|
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
|
||||||
Validator spouseValidator = new SpouseValidator();
|
|
||||||
spouseValidator.validate(tb.getSpouse(), errors);
|
spouseValidator.validate(tb.getSpouse(), errors);
|
||||||
|
|
||||||
errors.setNestedPath("");
|
errors.setNestedPath("");
|
||||||
|
|
@ -1332,7 +1339,6 @@ class DataBinderTests {
|
||||||
testValidator.validate(tb, errors);
|
testValidator.validate(tb, errors);
|
||||||
errors.setNestedPath("spouse.");
|
errors.setNestedPath("spouse.");
|
||||||
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
|
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
|
||||||
Validator spouseValidator = new SpouseValidator();
|
|
||||||
spouseValidator.validate(tb.getSpouse(), errors);
|
spouseValidator.validate(tb.getSpouse(), errors);
|
||||||
errors.setNestedPath("");
|
errors.setNestedPath("");
|
||||||
|
|
||||||
|
|
@ -1348,7 +1354,6 @@ class DataBinderTests {
|
||||||
TestBean tb = new TestBean();
|
TestBean tb = new TestBean();
|
||||||
tb.setName("XXX");
|
tb.setName("XXX");
|
||||||
Errors errors = new BeanPropertyBindingResult(tb, "tb");
|
Errors errors = new BeanPropertyBindingResult(tb, "tb");
|
||||||
Validator spouseValidator = new SpouseValidator();
|
|
||||||
spouseValidator.validate(tb, errors);
|
spouseValidator.validate(tb, errors);
|
||||||
|
|
||||||
assertThat(errors.hasGlobalErrors()).isTrue();
|
assertThat(errors.hasGlobalErrors()).isTrue();
|
||||||
|
|
@ -2160,28 +2165,6 @@ class DataBinderTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class SpouseValidator implements Validator {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supports(Class<?> clazz) {
|
|
||||||
return TestBean.class.isAssignableFrom(clazz);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void validate(@Nullable Object obj, Errors errors) {
|
|
||||||
TestBean tb = (TestBean) obj;
|
|
||||||
if (tb == null || "XXX".equals(tb.getName())) {
|
|
||||||
errors.rejectValue("", "SPOUSE_NOT_AVAILABLE");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (tb.getAge() < 32) {
|
|
||||||
errors.rejectValue("age", "TOO_YOUNG", "simply too young");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static class GrowingList<E> extends AbstractList<E> {
|
private static class GrowingList<E> extends AbstractList<E> {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2023 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.
|
||||||
|
|
@ -19,7 +19,6 @@ package org.springframework.validation;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.beans.testfixture.beans.TestBean;
|
import org.springframework.beans.testfixture.beans.TestBean;
|
||||||
import org.springframework.lang.Nullable;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
|
|
@ -34,6 +33,10 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||||
*/
|
*/
|
||||||
public class ValidationUtilsTests {
|
public class ValidationUtilsTests {
|
||||||
|
|
||||||
|
Validator emptyValidator = Validator.of(TestBean.class, (testBean, errors) -> ValidationUtils.rejectIfEmpty(errors, "name", "EMPTY", "You must enter a name!"));
|
||||||
|
|
||||||
|
Validator emptyOrWhitespaceValidator = Validator.of(TestBean.class, (testBean, errors) -> ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "EMPTY_OR_WHITESPACE", "You must enter a name!"));
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInvokeValidatorWithNullValidator() throws Exception {
|
public void testInvokeValidatorWithNullValidator() throws Exception {
|
||||||
TestBean tb = new TestBean();
|
TestBean tb = new TestBean();
|
||||||
|
|
@ -46,14 +49,14 @@ public class ValidationUtilsTests {
|
||||||
public void testInvokeValidatorWithNullErrors() throws Exception {
|
public void testInvokeValidatorWithNullErrors() throws Exception {
|
||||||
TestBean tb = new TestBean();
|
TestBean tb = new TestBean();
|
||||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||||
ValidationUtils.invokeValidator(new EmptyValidator(), tb, null));
|
ValidationUtils.invokeValidator(emptyValidator, tb, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInvokeValidatorSunnyDay() throws Exception {
|
public void testInvokeValidatorSunnyDay() throws Exception {
|
||||||
TestBean tb = new TestBean();
|
TestBean tb = new TestBean();
|
||||||
Errors errors = new BeanPropertyBindingResult(tb, "tb");
|
Errors errors = new BeanPropertyBindingResult(tb, "tb");
|
||||||
ValidationUtils.invokeValidator(new EmptyValidator(), tb, errors);
|
ValidationUtils.invokeValidator(emptyValidator, tb, errors);
|
||||||
assertThat(errors.hasFieldErrors("name")).isTrue();
|
assertThat(errors.hasFieldErrors("name")).isTrue();
|
||||||
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY");
|
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY");
|
||||||
}
|
}
|
||||||
|
|
@ -62,15 +65,14 @@ public class ValidationUtilsTests {
|
||||||
public void testValidationUtilsSunnyDay() throws Exception {
|
public void testValidationUtilsSunnyDay() throws Exception {
|
||||||
TestBean tb = new TestBean("");
|
TestBean tb = new TestBean("");
|
||||||
|
|
||||||
Validator testValidator = new EmptyValidator();
|
|
||||||
tb.setName(" ");
|
tb.setName(" ");
|
||||||
Errors errors = new BeanPropertyBindingResult(tb, "tb");
|
Errors errors = new BeanPropertyBindingResult(tb, "tb");
|
||||||
testValidator.validate(tb, errors);
|
emptyValidator.validate(tb, errors);
|
||||||
assertThat(errors.hasFieldErrors("name")).isFalse();
|
assertThat(errors.hasFieldErrors("name")).isFalse();
|
||||||
|
|
||||||
tb.setName("Roddy");
|
tb.setName("Roddy");
|
||||||
errors = new BeanPropertyBindingResult(tb, "tb");
|
errors = new BeanPropertyBindingResult(tb, "tb");
|
||||||
testValidator.validate(tb, errors);
|
emptyValidator.validate(tb, errors);
|
||||||
assertThat(errors.hasFieldErrors("name")).isFalse();
|
assertThat(errors.hasFieldErrors("name")).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,8 +80,7 @@ public class ValidationUtilsTests {
|
||||||
public void testValidationUtilsNull() throws Exception {
|
public void testValidationUtilsNull() throws Exception {
|
||||||
TestBean tb = new TestBean();
|
TestBean tb = new TestBean();
|
||||||
Errors errors = new BeanPropertyBindingResult(tb, "tb");
|
Errors errors = new BeanPropertyBindingResult(tb, "tb");
|
||||||
Validator testValidator = new EmptyValidator();
|
emptyValidator.validate(tb, errors);
|
||||||
testValidator.validate(tb, errors);
|
|
||||||
assertThat(errors.hasFieldErrors("name")).isTrue();
|
assertThat(errors.hasFieldErrors("name")).isTrue();
|
||||||
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY");
|
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY");
|
||||||
}
|
}
|
||||||
|
|
@ -88,8 +89,7 @@ public class ValidationUtilsTests {
|
||||||
public void testValidationUtilsEmpty() throws Exception {
|
public void testValidationUtilsEmpty() throws Exception {
|
||||||
TestBean tb = new TestBean("");
|
TestBean tb = new TestBean("");
|
||||||
Errors errors = new BeanPropertyBindingResult(tb, "tb");
|
Errors errors = new BeanPropertyBindingResult(tb, "tb");
|
||||||
Validator testValidator = new EmptyValidator();
|
emptyValidator.validate(tb, errors);
|
||||||
testValidator.validate(tb, errors);
|
|
||||||
assertThat(errors.hasFieldErrors("name")).isTrue();
|
assertThat(errors.hasFieldErrors("name")).isTrue();
|
||||||
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY");
|
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY");
|
||||||
}
|
}
|
||||||
|
|
@ -115,32 +115,31 @@ public class ValidationUtilsTests {
|
||||||
@Test
|
@Test
|
||||||
public void testValidationUtilsEmptyOrWhitespace() throws Exception {
|
public void testValidationUtilsEmptyOrWhitespace() throws Exception {
|
||||||
TestBean tb = new TestBean();
|
TestBean tb = new TestBean();
|
||||||
Validator testValidator = new EmptyOrWhitespaceValidator();
|
|
||||||
|
|
||||||
// Test null
|
// Test null
|
||||||
Errors errors = new BeanPropertyBindingResult(tb, "tb");
|
Errors errors = new BeanPropertyBindingResult(tb, "tb");
|
||||||
testValidator.validate(tb, errors);
|
emptyOrWhitespaceValidator.validate(tb, errors);
|
||||||
assertThat(errors.hasFieldErrors("name")).isTrue();
|
assertThat(errors.hasFieldErrors("name")).isTrue();
|
||||||
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY_OR_WHITESPACE");
|
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY_OR_WHITESPACE");
|
||||||
|
|
||||||
// Test empty String
|
// Test empty String
|
||||||
tb.setName("");
|
tb.setName("");
|
||||||
errors = new BeanPropertyBindingResult(tb, "tb");
|
errors = new BeanPropertyBindingResult(tb, "tb");
|
||||||
testValidator.validate(tb, errors);
|
emptyOrWhitespaceValidator.validate(tb, errors);
|
||||||
assertThat(errors.hasFieldErrors("name")).isTrue();
|
assertThat(errors.hasFieldErrors("name")).isTrue();
|
||||||
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY_OR_WHITESPACE");
|
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY_OR_WHITESPACE");
|
||||||
|
|
||||||
// Test whitespace String
|
// Test whitespace String
|
||||||
tb.setName(" ");
|
tb.setName(" ");
|
||||||
errors = new BeanPropertyBindingResult(tb, "tb");
|
errors = new BeanPropertyBindingResult(tb, "tb");
|
||||||
testValidator.validate(tb, errors);
|
emptyOrWhitespaceValidator.validate(tb, errors);
|
||||||
assertThat(errors.hasFieldErrors("name")).isTrue();
|
assertThat(errors.hasFieldErrors("name")).isTrue();
|
||||||
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY_OR_WHITESPACE");
|
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY_OR_WHITESPACE");
|
||||||
|
|
||||||
// Test OK
|
// Test OK
|
||||||
tb.setName("Roddy");
|
tb.setName("Roddy");
|
||||||
errors = new BeanPropertyBindingResult(tb, "tb");
|
errors = new BeanPropertyBindingResult(tb, "tb");
|
||||||
testValidator.validate(tb, errors);
|
emptyOrWhitespaceValidator.validate(tb, errors);
|
||||||
assertThat(errors.hasFieldErrors("name")).isFalse();
|
assertThat(errors.hasFieldErrors("name")).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,32 +162,4 @@ public class ValidationUtilsTests {
|
||||||
assertThat(errors.getFieldError("name").getDefaultMessage()).isEqualTo("msg");
|
assertThat(errors.getFieldError("name").getDefaultMessage()).isEqualTo("msg");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class EmptyValidator implements Validator {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supports(Class<?> clazz) {
|
|
||||||
return TestBean.class.isAssignableFrom(clazz);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void validate(@Nullable Object obj, Errors errors) {
|
|
||||||
ValidationUtils.rejectIfEmpty(errors, "name", "EMPTY", "You must enter a name!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static class EmptyOrWhitespaceValidator implements Validator {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supports(Class<?> clazz) {
|
|
||||||
return TestBean.class.isAssignableFrom(clazz);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void validate(@Nullable Object obj, Errors errors) {
|
|
||||||
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "EMPTY_OR_WHITESPACE", "You must enter a name!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue