Skip to content

Validator factory methods with BiConsumer #29890

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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");
* you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,10 @@

package org.springframework.validation;

import java.util.function.BiConsumer;

import org.springframework.util.Assert;

/**
* A validator for application-specific objects.
*
Expand Down Expand Up @@ -59,6 +63,7 @@
* application.
*
* @author Rod Johnson
* @author Toshiaki Maki
* @see SmartValidator
* @see Errors
* @see ValidationUtils
Expand Down Expand Up @@ -92,4 +97,38 @@ public interface Validator {
*/
void validate(Object target, Errors errors);

/**
* Takes the {@link BiConsumer} containing the validation logic for the specific type
* <code>&lt;T&gt;</code> and returns the {@link Validator} instance.<br>
* This validator implements the <i>typical</i> {@link #supports(Class)} method
* for the given <code>&lt;T&gt;</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>&lt;T&gt;</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);
}
};
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -82,6 +82,16 @@
*/
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
void bindingNoErrors() throws BindException {
TestBean rod = new TestBean();
Expand Down Expand Up @@ -1144,7 +1154,6 @@ void validatorNoErrors() throws Exception {
errors.setNestedPath("spouse");
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
assertThat(errors.getFieldValue("age")).isEqualTo("argh");
Validator spouseValidator = new SpouseValidator();
spouseValidator.validate(tb.getSpouse(), errors);

errors.setNestedPath("");
Expand Down Expand Up @@ -1195,7 +1204,6 @@ void validatorWithErrors() {

errors.setNestedPath("spouse.");
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
Validator spouseValidator = new SpouseValidator();
spouseValidator.validate(tb.getSpouse(), errors);

errors.setNestedPath("");
Expand Down Expand Up @@ -1267,7 +1275,6 @@ void validatorWithErrorsAndCodesPrefix() {

errors.setNestedPath("spouse.");
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
Validator spouseValidator = new SpouseValidator();
spouseValidator.validate(tb.getSpouse(), errors);

errors.setNestedPath("");
Expand Down Expand Up @@ -1332,7 +1339,6 @@ void validatorWithNestedObjectNull() {
testValidator.validate(tb, errors);
errors.setNestedPath("spouse.");
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
Validator spouseValidator = new SpouseValidator();
spouseValidator.validate(tb.getSpouse(), errors);
errors.setNestedPath("");

Expand All @@ -1348,7 +1354,6 @@ void nestedValidatorWithoutNestedPath() {
TestBean tb = new TestBean();
tb.setName("XXX");
Errors errors = new BeanPropertyBindingResult(tb, "tb");
Validator spouseValidator = new SpouseValidator();
spouseValidator.validate(tb, errors);

assertThat(errors.hasGlobalErrors()).isTrue();
Expand Down Expand Up @@ -2160,28 +2165,6 @@ public void validate(@Nullable Object obj, Errors errors) {
}
}


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")
private static class GrowingList<E> extends AbstractList<E> {

Expand Down
Original file line number Diff line number Diff line change
@@ -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");
* you may not use this file except in compliance with the License.
Expand All @@ -19,7 +19,6 @@
import org.junit.jupiter.api.Test;

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.assertThatIllegalArgumentException;
Expand All @@ -34,6 +33,10 @@
*/
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
public void testInvokeValidatorWithNullValidator() throws Exception {
TestBean tb = new TestBean();
Expand All @@ -46,14 +49,14 @@ public void testInvokeValidatorWithNullValidator() throws Exception {
public void testInvokeValidatorWithNullErrors() throws Exception {
TestBean tb = new TestBean();
assertThatIllegalArgumentException().isThrownBy(() ->
ValidationUtils.invokeValidator(new EmptyValidator(), tb, null));
ValidationUtils.invokeValidator(emptyValidator, tb, null));
}

@Test
public void testInvokeValidatorSunnyDay() throws Exception {
TestBean tb = new TestBean();
Errors errors = new BeanPropertyBindingResult(tb, "tb");
ValidationUtils.invokeValidator(new EmptyValidator(), tb, errors);
ValidationUtils.invokeValidator(emptyValidator, tb, errors);
assertThat(errors.hasFieldErrors("name")).isTrue();
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY");
}
Expand All @@ -62,24 +65,22 @@ public void testInvokeValidatorSunnyDay() throws Exception {
public void testValidationUtilsSunnyDay() throws Exception {
TestBean tb = new TestBean("");

Validator testValidator = new EmptyValidator();
tb.setName(" ");
Errors errors = new BeanPropertyBindingResult(tb, "tb");
testValidator.validate(tb, errors);
emptyValidator.validate(tb, errors);
assertThat(errors.hasFieldErrors("name")).isFalse();

tb.setName("Roddy");
errors = new BeanPropertyBindingResult(tb, "tb");
testValidator.validate(tb, errors);
emptyValidator.validate(tb, errors);
assertThat(errors.hasFieldErrors("name")).isFalse();
}

@Test
public void testValidationUtilsNull() throws Exception {
TestBean tb = new TestBean();
Errors errors = new BeanPropertyBindingResult(tb, "tb");
Validator testValidator = new EmptyValidator();
testValidator.validate(tb, errors);
emptyValidator.validate(tb, errors);
assertThat(errors.hasFieldErrors("name")).isTrue();
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY");
}
Expand All @@ -88,8 +89,7 @@ public void testValidationUtilsNull() throws Exception {
public void testValidationUtilsEmpty() throws Exception {
TestBean tb = new TestBean("");
Errors errors = new BeanPropertyBindingResult(tb, "tb");
Validator testValidator = new EmptyValidator();
testValidator.validate(tb, errors);
emptyValidator.validate(tb, errors);
assertThat(errors.hasFieldErrors("name")).isTrue();
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY");
}
Expand All @@ -115,32 +115,31 @@ public void testValidationUtilsEmptyVariants() {
@Test
public void testValidationUtilsEmptyOrWhitespace() throws Exception {
TestBean tb = new TestBean();
Validator testValidator = new EmptyOrWhitespaceValidator();

// Test null
Errors errors = new BeanPropertyBindingResult(tb, "tb");
testValidator.validate(tb, errors);
emptyOrWhitespaceValidator.validate(tb, errors);
assertThat(errors.hasFieldErrors("name")).isTrue();
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY_OR_WHITESPACE");

// Test empty String
tb.setName("");
errors = new BeanPropertyBindingResult(tb, "tb");
testValidator.validate(tb, errors);
emptyOrWhitespaceValidator.validate(tb, errors);
assertThat(errors.hasFieldErrors("name")).isTrue();
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY_OR_WHITESPACE");

// Test whitespace String
tb.setName(" ");
errors = new BeanPropertyBindingResult(tb, "tb");
testValidator.validate(tb, errors);
emptyOrWhitespaceValidator.validate(tb, errors);
assertThat(errors.hasFieldErrors("name")).isTrue();
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY_OR_WHITESPACE");

// Test OK
tb.setName("Roddy");
errors = new BeanPropertyBindingResult(tb, "tb");
testValidator.validate(tb, errors);
emptyOrWhitespaceValidator.validate(tb, errors);
assertThat(errors.hasFieldErrors("name")).isFalse();
}

Expand All @@ -163,32 +162,4 @@ public void testValidationUtilsEmptyOrWhitespaceVariants() {
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!");
}
}

}