Support cross-parameter validation

Closes gh-33271
This commit is contained in:
rstoyanchev
2024-08-09 18:53:30 +03:00
parent 0d64c90a79
commit b61eee7fb0
11 changed files with 187 additions and 39 deletions

View File

@@ -18,6 +18,7 @@ package org.springframework.validation.beanvalidation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@@ -301,6 +302,7 @@ public class MethodValidationAdapter implements MethodValidator {
Map<Path.Node, ParamValidationResultBuilder> paramViolations = new LinkedHashMap<>(); Map<Path.Node, ParamValidationResultBuilder> paramViolations = new LinkedHashMap<>();
Map<Path.Node, ParamErrorsBuilder> nestedViolations = new LinkedHashMap<>(); Map<Path.Node, ParamErrorsBuilder> nestedViolations = new LinkedHashMap<>();
List<MessageSourceResolvable> crossParamErrors = null;
for (ConstraintViolation<Object> violation : violations) { for (ConstraintViolation<Object> violation : violations) {
Iterator<Path.Node> nodes = violation.getPropertyPath().iterator(); Iterator<Path.Node> nodes = violation.getPropertyPath().iterator();
@@ -315,6 +317,11 @@ public class MethodValidationAdapter implements MethodValidator {
else if (node.getKind().equals(ElementKind.RETURN_VALUE)) { else if (node.getKind().equals(ElementKind.RETURN_VALUE)) {
parameter = parameterFunction.apply(-1); parameter = parameterFunction.apply(-1);
} }
else if (node.getKind().equals(ElementKind.CROSS_PARAMETER)) {
crossParamErrors = (crossParamErrors != null ? crossParamErrors : new ArrayList<>());
crossParamErrors.add(createCrossParamError(target, method, violation));
break;
}
else { else {
continue; continue;
} }
@@ -382,7 +389,8 @@ public class MethodValidationAdapter implements MethodValidator {
nestedViolations.forEach((key, builder) -> resultList.add(builder.build())); nestedViolations.forEach((key, builder) -> resultList.add(builder.build()));
resultList.sort(resultComparator); resultList.sort(resultComparator);
return MethodValidationResult.create(target, method, resultList); return MethodValidationResult.create(target, method, resultList,
(crossParamErrors != null ? crossParamErrors : Collections.emptyList()));
} }
private MethodParameter initMethodParameter(Method method, int index) { private MethodParameter initMethodParameter(Method method, int index) {
@@ -413,6 +421,19 @@ public class MethodValidationAdapter implements MethodValidator {
return result; return result;
} }
private MessageSourceResolvable createCrossParamError(
Object target, Method method, ConstraintViolation<Object> violation) {
String objectName = Conventions.getVariableName(target) + "#" + method.getName();
ConstraintDescriptor<?> descriptor = violation.getConstraintDescriptor();
String code = descriptor.getAnnotation().annotationType().getSimpleName();
String[] codes = this.messageCodesResolver.resolveMessageCodes(code, objectName);
Object[] arguments = this.validatorAdapter.get().getArgumentsForConstraint(objectName, "", descriptor);
return new ViolationMessageSourceResolvable(codes, arguments, violation.getMessage(), violation);
}
/** /**
* Strategy to resolve the name of an {@code @Valid} method parameter to * Strategy to resolve the name of an {@code @Valid} method parameter to

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2023 the original author or authors. * Copyright 2002-2024 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,6 +19,7 @@ package org.springframework.validation.method;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.List; import java.util.List;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
@@ -33,19 +34,26 @@ final class DefaultMethodValidationResult implements MethodValidationResult {
private final Method method; private final Method method;
private final List<ParameterValidationResult> allValidationResults; private final List<ParameterValidationResult> parameterValidationResults;
private final List<MessageSourceResolvable> crossParamResults;
private final boolean forReturnValue; private final boolean forReturnValue;
DefaultMethodValidationResult(Object target, Method method, List<ParameterValidationResult> results) { DefaultMethodValidationResult(
Assert.notEmpty(results, "'results' is required and must not be empty"); Object target, Method method, List<ParameterValidationResult> results,
List<MessageSourceResolvable> crossParamResults) {
Assert.isTrue(!results.isEmpty() || !crossParamResults.isEmpty(), "Expected validation results");
Assert.notNull(target, "'target' is required"); Assert.notNull(target, "'target' is required");
Assert.notNull(method, "Method is required"); Assert.notNull(method, "Method is required");
this.target = target; this.target = target;
this.method = method; this.method = method;
this.allValidationResults = results; this.parameterValidationResults = results;
this.forReturnValue = (results.get(0).getMethodParameter().getParameterIndex() == -1); this.crossParamResults = crossParamResults;
this.forReturnValue = (!results.isEmpty() && results.get(0).getMethodParameter().getParameterIndex() == -1);
} }
@@ -65,10 +73,14 @@ final class DefaultMethodValidationResult implements MethodValidationResult {
} }
@Override @Override
public List<ParameterValidationResult> getAllValidationResults() { public List<ParameterValidationResult> getParameterValidationResults() {
return this.allValidationResults; return this.parameterValidationResults;
} }
@Override
public List<MessageSourceResolvable> getCrossParameterValidationResults() {
return this.crossParamResults;
}
@Override @Override
public String toString() { public String toString() {

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2023 the original author or authors. * Copyright 2002-2024 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.
@@ -20,6 +20,8 @@ import java.lang.reflect.Method;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.springframework.context.MessageSourceResolvable;
/** /**
* {@link MethodValidationResult} with an empty list of results. * {@link MethodValidationResult} with an empty list of results.
* *
@@ -44,7 +46,12 @@ final class EmptyMethodValidationResult implements MethodValidationResult {
} }
@Override @Override
public List<ParameterValidationResult> getAllValidationResults() { public List<ParameterValidationResult> getParameterValidationResults() {
return Collections.emptyList();
}
@Override
public List<MessageSourceResolvable> getCrossParameterValidationResults() {
return Collections.emptyList(); return Collections.emptyList();
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2023 the original author or authors. * Copyright 2002-2024 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,6 +19,7 @@ package org.springframework.validation.method;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.List; import java.util.List;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
@@ -57,8 +58,13 @@ public class MethodValidationException extends RuntimeException implements Metho
} }
@Override @Override
public List<ParameterValidationResult> getAllValidationResults() { public List<ParameterValidationResult> getParameterValidationResults() {
return this.validationResult.getAllValidationResults(); return this.validationResult.getParameterValidationResults();
}
@Override
public List<MessageSourceResolvable> getCrossParameterValidationResults() {
return this.validationResult.getCrossParameterValidationResults();
} }
} }

View File

@@ -17,6 +17,7 @@
package org.springframework.validation.method; package org.springframework.validation.method;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List; import java.util.List;
import org.springframework.context.MessageSourceResolvable; import org.springframework.context.MessageSourceResolvable;
@@ -55,54 +56,75 @@ public interface MethodValidationResult {
* Whether the result contains any validation errors. * Whether the result contains any validation errors.
*/ */
default boolean hasErrors() { default boolean hasErrors() {
return !getAllValidationResults().isEmpty(); return !getParameterValidationResults().isEmpty();
} }
/** /**
* Return a single list with all errors from all validation results. * Return a single list with all errors from all validation results.
* @see #getAllValidationResults() * @see #getParameterValidationResults()
* @see ParameterValidationResult#getResolvableErrors() * @see ParameterValidationResult#getResolvableErrors()
*/ */
default List<? extends MessageSourceResolvable> getAllErrors() { default List<? extends MessageSourceResolvable> getAllErrors() {
return getAllValidationResults().stream() return getParameterValidationResults().stream()
.flatMap(result -> result.getResolvableErrors().stream()) .flatMap(result -> result.getResolvableErrors().stream())
.toList(); .toList();
} }
/**
* Return all validation results per method parameter, including both
* {@link #getValueResults()} and {@link #getBeanResults()}.
* <p>Use {@link #getCrossParameterValidationResults()} for access to errors
* from cross-parameter validation.
* @since 6.2
* @see #getValueResults()
* @see #getBeanResults()
*/
List<ParameterValidationResult> getParameterValidationResults();
/** /**
* Return all validation results. This includes both method parameters with * Return all validation results. This includes both method parameters with
* errors directly on them, and Object method parameters with nested errors * errors directly on them, and Object method parameters with nested errors
* on their fields and properties. * on their fields and properties.
* @see #getValueResults() * @see #getValueResults()
* @see #getBeanResults() * @see #getBeanResults()
* @deprecated deprecated in favor of {@link #getParameterValidationResults()}
*/ */
List<ParameterValidationResult> getAllValidationResults(); @Deprecated(since = "6.2", forRemoval = true)
default List<ParameterValidationResult> getAllValidationResults() {
return getParameterValidationResults();
}
/** /**
* Return the subset of {@link #getAllValidationResults() allValidationResults} * Return the subset of {@link #getParameterValidationResults() allValidationResults}
* that includes method parameters with validation errors directly on method * that includes method parameters with validation errors directly on method
* argument values. This excludes {@link #getBeanResults() beanResults} with * argument values. This excludes {@link #getBeanResults() beanResults} with
* nested errors on their fields and properties. * nested errors on their fields and properties.
*/ */
default List<ParameterValidationResult> getValueResults() { default List<ParameterValidationResult> getValueResults() {
return getAllValidationResults().stream() return getParameterValidationResults().stream()
.filter(result -> !(result instanceof ParameterErrors)) .filter(result -> !(result instanceof ParameterErrors))
.toList(); .toList();
} }
/** /**
* Return the subset of {@link #getAllValidationResults() allValidationResults} * Return the subset of {@link #getParameterValidationResults() allValidationResults}
* that includes Object method parameters with nested errors on their fields * that includes Object method parameters with nested errors on their fields
* and properties. This excludes {@link #getValueResults() valueResults} with * and properties. This excludes {@link #getValueResults() valueResults} with
* validation errors directly on method arguments. * validation errors directly on method arguments.
*/ */
default List<ParameterErrors> getBeanResults() { default List<ParameterErrors> getBeanResults() {
return getAllValidationResults().stream() return getParameterValidationResults().stream()
.filter(ParameterErrors.class::isInstance) .filter(ParameterErrors.class::isInstance)
.map(result -> (ParameterErrors) result) .map(result -> (ParameterErrors) result)
.toList(); .toList();
} }
/**
* Return errors from cross-parameter validation.
* @since 6.2
*/
List<MessageSourceResolvable> getCrossParameterValidationResults();
/** /**
* Factory method to create a {@link MethodValidationResult} instance. * Factory method to create a {@link MethodValidationResult} instance.
@@ -112,7 +134,23 @@ public interface MethodValidationResult {
* @return the created instance * @return the created instance
*/ */
static MethodValidationResult create(Object target, Method method, List<ParameterValidationResult> results) { static MethodValidationResult create(Object target, Method method, List<ParameterValidationResult> results) {
return new DefaultMethodValidationResult(target, method, results); return create(target, method, results, Collections.emptyList());
}
/**
* Factory method to create a {@link MethodValidationResult} instance.
* @param target the target Object
* @param method the target method
* @param results method validation results, expected to be non-empty
* @param crossParameterErrors cross-parameter validation errors
* @return the created instance
* @since 6.2
*/
static MethodValidationResult create(
Object target, Method method, List<ParameterValidationResult> results,
List<MessageSourceResolvable> crossParameterErrors) {
return new DefaultMethodValidationResult(target, method, results, crossParameterErrors);
} }
/** /**

View File

@@ -16,18 +16,27 @@
package org.springframework.validation.beanvalidation; package org.springframework.validation.beanvalidation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
import jakarta.validation.Constraint;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolation;
import jakarta.validation.Payload;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import jakarta.validation.constraintvalidation.SupportedValidationTarget;
import jakarta.validation.constraintvalidation.ValidationTarget;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -40,6 +49,9 @@ import org.springframework.validation.method.MethodValidationResult;
import org.springframework.validation.method.ParameterErrors; import org.springframework.validation.method.ParameterErrors;
import org.springframework.validation.method.ParameterValidationResult; import org.springframework.validation.method.ParameterValidationResult;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
@@ -77,7 +89,7 @@ class MethodValidationAdapterTests {
testArgs(target, method, new Object[] {faustino1234, cayetana6789, 3}, ex -> { testArgs(target, method, new Object[] {faustino1234, cayetana6789, 3}, ex -> {
assertThat(ex.getAllValidationResults()).hasSize(3); assertThat(ex.getParameterValidationResults()).hasSize(3);
assertBeanResult(ex.getBeanResults().get(0), 0, "student", faustino1234, List.of(""" assertBeanResult(ex.getBeanResults().get(0), 0, "student", faustino1234, List.of("""
Field error in object 'student' on field 'name': rejected value [Faustino1234]; \ Field error in object 'student' on field 'name': rejected value [Faustino1234]; \
@@ -117,7 +129,7 @@ class MethodValidationAdapterTests {
testArgs(target, method, new Object[] {faustino1234, new Person("Joe", List.of()), 1}, ex -> { testArgs(target, method, new Object[] {faustino1234, new Person("Joe", List.of()), 1}, ex -> {
assertThat(ex.getAllValidationResults()).hasSize(1); assertThat(ex.getParameterValidationResults()).hasSize(1);
assertBeanResult(ex.getBeanResults().get(0), 0, "studentToAdd", faustino1234, List.of(""" assertBeanResult(ex.getBeanResults().get(0), 0, "studentToAdd", faustino1234, List.of("""
Field error in object 'studentToAdd' on field 'name': rejected value [Faustino1234]; \ Field error in object 'studentToAdd' on field 'name': rejected value [Faustino1234]; \
@@ -134,7 +146,7 @@ class MethodValidationAdapterTests {
testReturnValue(target, getMethod(target, "getIntValue"), 4, ex -> { testReturnValue(target, getMethod(target, "getIntValue"), 4, ex -> {
assertThat(ex.getAllValidationResults()).hasSize(1); assertThat(ex.getParameterValidationResults()).hasSize(1);
assertValueResult(ex.getValueResults().get(0), -1, 4, List.of(""" assertValueResult(ex.getValueResults().get(0), -1, 4, List.of("""
org.springframework.validation.beanvalidation.MethodValidationAdapter$ViolationMessageSourceResolvable: \ org.springframework.validation.beanvalidation.MethodValidationAdapter$ViolationMessageSourceResolvable: \
@@ -151,7 +163,7 @@ class MethodValidationAdapterTests {
testReturnValue(target, getMethod(target, "getPerson"), faustino1234, ex -> { testReturnValue(target, getMethod(target, "getPerson"), faustino1234, ex -> {
assertThat(ex.getAllValidationResults()).hasSize(1); assertThat(ex.getParameterValidationResults()).hasSize(1);
assertBeanResult(ex.getBeanResults().get(0), -1, "person", faustino1234, List.of(""" assertBeanResult(ex.getBeanResults().get(0), -1, "person", faustino1234, List.of("""
Field error in object 'person' on field 'name': rejected value [Faustino1234]; \ Field error in object 'person' on field 'name': rejected value [Faustino1234]; \
@@ -169,7 +181,7 @@ class MethodValidationAdapterTests {
testArgs(target, method, new Object[] {List.of(faustino1234, cayetana6789)}, ex -> { testArgs(target, method, new Object[] {List.of(faustino1234, cayetana6789)}, ex -> {
assertThat(ex.getAllValidationResults()).hasSize(2); assertThat(ex.getParameterValidationResults()).hasSize(2);
int paramIndex = 0; int paramIndex = 0;
String objectName = "people"; String objectName = "people";
@@ -203,7 +215,7 @@ class MethodValidationAdapterTests {
Method method = getMethod(target, "addHobbies"); Method method = getMethod(target, "addHobbies");
testArgs(target, method, new Object[] {List.of(" ")}, ex -> { testArgs(target, method, new Object[] {List.of(" ")}, ex -> {
assertThat(ex.getAllValidationResults()).hasSize(1); assertThat(ex.getParameterValidationResults()).hasSize(1);
assertValueResult(ex.getValueResults().get(0), 0, " ", List.of(""" assertValueResult(ex.getValueResults().get(0), 0, " ", List.of("""
org.springframework.validation.beanvalidation.MethodValidationAdapter$ViolationMessageSourceResolvable: \ org.springframework.validation.beanvalidation.MethodValidationAdapter$ViolationMessageSourceResolvable: \
codes [NotBlank.myService#addHobbies.hobbies,NotBlank.hobbies,NotBlank.java.util.List,NotBlank]; \ codes [NotBlank.myService#addHobbies.hobbies,NotBlank.hobbies,NotBlank.java.util.List,NotBlank]; \
@@ -219,7 +231,7 @@ class MethodValidationAdapterTests {
Method method = getMethod(target, "addUniqueHobbies"); Method method = getMethod(target, "addUniqueHobbies");
testArgs(target, method, new Object[] {Set.of("test", " ")}, ex -> { testArgs(target, method, new Object[] {Set.of("test", " ")}, ex -> {
assertThat(ex.getAllValidationResults()).hasSize(1); assertThat(ex.getParameterValidationResults()).hasSize(1);
assertValueResult(ex.getValueResults().get(0), 0, Set.of("test", " "), List.of(""" assertValueResult(ex.getValueResults().get(0), 0, Set.of("test", " "), List.of("""
org.springframework.validation.beanvalidation.MethodValidationAdapter$ViolationMessageSourceResolvable: \ org.springframework.validation.beanvalidation.MethodValidationAdapter$ViolationMessageSourceResolvable: \
codes [NotBlank.myService#addUniqueHobbies.hobbies,NotBlank.hobbies,NotBlank.java.util.Set,NotBlank]; \ codes [NotBlank.myService#addUniqueHobbies.hobbies,NotBlank.hobbies,NotBlank.java.util.Set,NotBlank]; \
@@ -229,6 +241,23 @@ class MethodValidationAdapterTests {
}); });
} }
@Test
void validateCrossParams() {
MyService target = new MyService();
Method method = getMethod(target, "addRange");
testArgs(target, method, new Object[] {90, 50}, ex -> {
assertThat(ex.getParameterValidationResults()).isEmpty();
assertThat(ex.getCrossParameterValidationResults()).hasSize(1);
assertThat(ex.getCrossParameterValidationResults().get(0).toString()).isEqualTo("""
org.springframework.validation.beanvalidation.MethodValidationAdapter$ViolationMessageSourceResolvable: \
codes [RangeParams.myService#addRange,RangeParams]; \
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: \
codes [myService#addRange]; \
arguments []; default message []]; default message [Invalid range]""");
});
}
private void testArgs(Object target, Method method, Object[] args, Consumer<MethodValidationResult> consumer) { private void testArgs(Object target, Method method, Object[] args, Consumer<MethodValidationResult> consumer) {
consumer.accept(this.validationAdapter.validateArguments(target, method, null, args, new Class<?>[0])); consumer.accept(this.validationAdapter.validateArguments(target, method, null, args, new Class<?>[0]));
} }
@@ -294,6 +323,10 @@ class MethodValidationAdapterTests {
public void addUniqueHobbies(Set<@NotBlank String> hobbies) { public void addUniqueHobbies(Set<@NotBlank String> hobbies) {
} }
@RangeParams
public void addRange(int from, int to) {
}
} }
@@ -301,4 +334,29 @@ class MethodValidationAdapterTests {
private record Person(@Size(min = 1, max = 10) String name, List<@NotBlank String> hobbies) { private record Person(@Size(min = 1, max = 10) String name, List<@NotBlank String> hobbies) {
} }
@Documented
@Constraint(validatedBy = RangeParamsValidator.class)
@Target({ CONSTRUCTOR, METHOD })
@Retention(RUNTIME)
public @interface RangeParams {
String message() default "Invalid range";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@SupportedValidationTarget(ValidationTarget.PARAMETERS)
public static final class RangeParamsValidator implements ConstraintValidator<RangeParams, Object[]> {
@Override
public boolean isValid(final Object[] parameters, final ConstraintValidatorContext context) {
return false;
}
}
} }

View File

@@ -81,7 +81,7 @@ class MethodValidationProxyReactorTests {
StepVerifier.create(myService.addPerson(personMono)) StepVerifier.create(myService.addPerson(personMono))
.expectErrorSatisfies(t -> { .expectErrorSatisfies(t -> {
MethodValidationException ex = (MethodValidationException) t; MethodValidationException ex = (MethodValidationException) t;
assertThat(ex.getAllValidationResults()).hasSize(1); assertThat(ex.getParameterValidationResults()).hasSize(1);
ParameterErrors errors = ex.getBeanResults().get(0); ParameterErrors errors = ex.getBeanResults().get(0);
assertThat(errors.getErrorCount()).isEqualTo(1); assertThat(errors.getErrorCount()).isEqualTo(1);

View File

@@ -22,6 +22,7 @@ import java.util.Locale;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@@ -107,8 +108,13 @@ public class HandlerMethodValidationException extends ResponseStatusException im
} }
@Override @Override
public List<ParameterValidationResult> getAllValidationResults() { public List<ParameterValidationResult> getParameterValidationResults() {
return this.validationResult.getAllValidationResults(); return this.validationResult.getParameterValidationResults();
}
@Override
public List<MessageSourceResolvable> getCrossParameterValidationResults() {
return this.validationResult.getCrossParameterValidationResults();
} }
/** /**
@@ -116,7 +122,7 @@ public class HandlerMethodValidationException extends ResponseStatusException im
* through callback methods organized by controller method parameter type. * through callback methods organized by controller method parameter type.
*/ */
public void visitResults(Visitor visitor) { public void visitResults(Visitor visitor) {
for (ParameterValidationResult result : getAllValidationResults()) { for (ParameterValidationResult result : getParameterValidationResults()) {
MethodParameter param = result.getMethodParameter(); MethodParameter param = result.getMethodParameter();
CookieValue cookieValue = param.getParameterAnnotation(CookieValue.class); CookieValue cookieValue = param.getParameterAnnotation(CookieValue.class);
if (cookieValue != null) { if (cookieValue != null) {

View File

@@ -98,7 +98,7 @@ public final class HandlerMethodValidator implements MethodValidator {
} }
} }
} }
if (result.getAllValidationResults().size() == bindingResultCount) { if (result.getParameterValidationResults().size() == bindingResultCount) {
return; return;
} }
} }

View File

@@ -208,7 +208,7 @@ class MethodValidationTests {
assertThat(this.jakartaValidator.getValidationCount()).isEqualTo(1); assertThat(this.jakartaValidator.getValidationCount()).isEqualTo(1);
assertThat(this.jakartaValidator.getMethodValidationCount()).isEqualTo(1); assertThat(this.jakartaValidator.getMethodValidationCount()).isEqualTo(1);
assertThat(ex.getAllValidationResults()).hasSize(2); assertThat(ex.getParameterValidationResults()).hasSize(2);
assertBeanResult(ex.getBeanResults().get(0), "student", Collections.singletonList( assertBeanResult(ex.getBeanResults().get(0), "student", Collections.singletonList(
""" """
@@ -245,7 +245,7 @@ class MethodValidationTests {
assertThat(this.jakartaValidator.getValidationCount()).isEqualTo(1); assertThat(this.jakartaValidator.getValidationCount()).isEqualTo(1);
assertThat(this.jakartaValidator.getMethodValidationCount()).isEqualTo(1); assertThat(this.jakartaValidator.getMethodValidationCount()).isEqualTo(1);
assertThat(ex.getAllValidationResults()).hasSize(2); assertThat(ex.getParameterValidationResults()).hasSize(2);
assertBeanResult(ex.getBeanResults().get(0), "personList", Collections.singletonList( assertBeanResult(ex.getBeanResults().get(0), "personList", Collections.singletonList(
""" """

View File

@@ -172,7 +172,7 @@ class MethodValidationTests {
assertThat(this.jakartaValidator.getValidationCount()).isEqualTo(1); assertThat(this.jakartaValidator.getValidationCount()).isEqualTo(1);
assertThat(this.jakartaValidator.getMethodValidationCount()).isEqualTo(1); assertThat(this.jakartaValidator.getMethodValidationCount()).isEqualTo(1);
assertThat(ex.getAllValidationResults()).hasSize(2); assertThat(ex.getParameterValidationResults()).hasSize(2);
assertBeanResult(ex.getBeanResults().get(0), "student", List.of(""" assertBeanResult(ex.getBeanResults().get(0), "student", List.of("""
Field error in object 'student' on field 'name': rejected value [name=Faustino1234]; \ Field error in object 'student' on field 'name': rejected value [name=Faustino1234]; \
@@ -227,7 +227,7 @@ class MethodValidationTests {
assertThat(this.jakartaValidator.getValidationCount()).isEqualTo(1); assertThat(this.jakartaValidator.getValidationCount()).isEqualTo(1);
assertThat(this.jakartaValidator.getMethodValidationCount()).isEqualTo(1); assertThat(this.jakartaValidator.getMethodValidationCount()).isEqualTo(1);
assertThat(ex.getAllValidationResults()).hasSize(2); assertThat(ex.getParameterValidationResults()).hasSize(2);
assertBeanResult(ex.getBeanResults().get(0), "personList", List.of(""" assertBeanResult(ex.getBeanResults().get(0), "personList", List.of("""
Field error in object 'personList' on field 'name': rejected value [Faustino1234]; \ Field error in object 'personList' on field 'name': rejected value [Faustino1234]; \