From b61eee7fb0ca8cc66a6ce36e2c73af0f6ddfb82e Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Fri, 9 Aug 2024 18:53:30 +0300 Subject: [PATCH] Support cross-parameter validation Closes gh-33271 --- .../MethodValidationAdapter.java | 23 +++++- .../method/DefaultMethodValidationResult.java | 28 +++++--- .../method/EmptyMethodValidationResult.java | 11 ++- .../method/MethodValidationException.java | 12 +++- .../method/MethodValidationResult.java | 56 ++++++++++++--- .../MethodValidationAdapterTests.java | 72 +++++++++++++++++-- .../MethodValidationProxyReactorTests.java | 2 +- .../HandlerMethodValidationException.java | 12 +++- .../annotation/HandlerMethodValidator.java | 2 +- .../annotation/MethodValidationTests.java | 4 +- .../annotation/MethodValidationTests.java | 4 +- 11 files changed, 187 insertions(+), 39 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationAdapter.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationAdapter.java index 533523d3d99..d6db452e21e 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationAdapter.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationAdapter.java @@ -18,6 +18,7 @@ package org.springframework.validation.beanvalidation; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.LinkedHashMap; @@ -301,6 +302,7 @@ public class MethodValidationAdapter implements MethodValidator { Map paramViolations = new LinkedHashMap<>(); Map nestedViolations = new LinkedHashMap<>(); + List crossParamErrors = null; for (ConstraintViolation violation : violations) { Iterator nodes = violation.getPropertyPath().iterator(); @@ -315,6 +317,11 @@ public class MethodValidationAdapter implements MethodValidator { else if (node.getKind().equals(ElementKind.RETURN_VALUE)) { 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 { continue; } @@ -382,7 +389,8 @@ public class MethodValidationAdapter implements MethodValidator { nestedViolations.forEach((key, builder) -> resultList.add(builder.build())); 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) { @@ -413,6 +421,19 @@ public class MethodValidationAdapter implements MethodValidator { return result; } + private MessageSourceResolvable createCrossParamError( + Object target, Method method, ConstraintViolation 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 diff --git a/spring-context/src/main/java/org/springframework/validation/method/DefaultMethodValidationResult.java b/spring-context/src/main/java/org/springframework/validation/method/DefaultMethodValidationResult.java index edb3caf393b..49570f43582 100644 --- a/spring-context/src/main/java/org/springframework/validation/method/DefaultMethodValidationResult.java +++ b/spring-context/src/main/java/org/springframework/validation/method/DefaultMethodValidationResult.java @@ -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"); * 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.util.List; +import org.springframework.context.MessageSourceResolvable; import org.springframework.util.Assert; /** @@ -33,19 +34,26 @@ final class DefaultMethodValidationResult implements MethodValidationResult { private final Method method; - private final List allValidationResults; + private final List parameterValidationResults; + + private final List crossParamResults; private final boolean forReturnValue; - DefaultMethodValidationResult(Object target, Method method, List results) { - Assert.notEmpty(results, "'results' is required and must not be empty"); + DefaultMethodValidationResult( + Object target, Method method, List results, + List crossParamResults) { + + Assert.isTrue(!results.isEmpty() || !crossParamResults.isEmpty(), "Expected validation results"); Assert.notNull(target, "'target' is required"); Assert.notNull(method, "Method is required"); + this.target = target; this.method = method; - this.allValidationResults = results; - this.forReturnValue = (results.get(0).getMethodParameter().getParameterIndex() == -1); + this.parameterValidationResults = results; + this.crossParamResults = crossParamResults; + this.forReturnValue = (!results.isEmpty() && results.get(0).getMethodParameter().getParameterIndex() == -1); } @@ -65,10 +73,14 @@ final class DefaultMethodValidationResult implements MethodValidationResult { } @Override - public List getAllValidationResults() { - return this.allValidationResults; + public List getParameterValidationResults() { + return this.parameterValidationResults; } + @Override + public List getCrossParameterValidationResults() { + return this.crossParamResults; + } @Override public String toString() { diff --git a/spring-context/src/main/java/org/springframework/validation/method/EmptyMethodValidationResult.java b/spring-context/src/main/java/org/springframework/validation/method/EmptyMethodValidationResult.java index 484f683f106..a5d2915474f 100644 --- a/spring-context/src/main/java/org/springframework/validation/method/EmptyMethodValidationResult.java +++ b/spring-context/src/main/java/org/springframework/validation/method/EmptyMethodValidationResult.java @@ -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"); * 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.List; +import org.springframework.context.MessageSourceResolvable; + /** * {@link MethodValidationResult} with an empty list of results. * @@ -44,7 +46,12 @@ final class EmptyMethodValidationResult implements MethodValidationResult { } @Override - public List getAllValidationResults() { + public List getParameterValidationResults() { + return Collections.emptyList(); + } + + @Override + public List getCrossParameterValidationResults() { return Collections.emptyList(); } diff --git a/spring-context/src/main/java/org/springframework/validation/method/MethodValidationException.java b/spring-context/src/main/java/org/springframework/validation/method/MethodValidationException.java index 5eabc69988e..505b02287c7 100644 --- a/spring-context/src/main/java/org/springframework/validation/method/MethodValidationException.java +++ b/spring-context/src/main/java/org/springframework/validation/method/MethodValidationException.java @@ -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"); * 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.util.List; +import org.springframework.context.MessageSourceResolvable; import org.springframework.util.Assert; /** @@ -57,8 +58,13 @@ public class MethodValidationException extends RuntimeException implements Metho } @Override - public List getAllValidationResults() { - return this.validationResult.getAllValidationResults(); + public List getParameterValidationResults() { + return this.validationResult.getParameterValidationResults(); + } + + @Override + public List getCrossParameterValidationResults() { + return this.validationResult.getCrossParameterValidationResults(); } } diff --git a/spring-context/src/main/java/org/springframework/validation/method/MethodValidationResult.java b/spring-context/src/main/java/org/springframework/validation/method/MethodValidationResult.java index 756b44c153a..69ff06f55c2 100644 --- a/spring-context/src/main/java/org/springframework/validation/method/MethodValidationResult.java +++ b/spring-context/src/main/java/org/springframework/validation/method/MethodValidationResult.java @@ -17,6 +17,7 @@ package org.springframework.validation.method; import java.lang.reflect.Method; +import java.util.Collections; import java.util.List; import org.springframework.context.MessageSourceResolvable; @@ -55,54 +56,75 @@ public interface MethodValidationResult { * Whether the result contains any validation errors. */ default boolean hasErrors() { - return !getAllValidationResults().isEmpty(); + return !getParameterValidationResults().isEmpty(); } /** * Return a single list with all errors from all validation results. - * @see #getAllValidationResults() + * @see #getParameterValidationResults() * @see ParameterValidationResult#getResolvableErrors() */ default List getAllErrors() { - return getAllValidationResults().stream() + return getParameterValidationResults().stream() .flatMap(result -> result.getResolvableErrors().stream()) .toList(); } + /** + * Return all validation results per method parameter, including both + * {@link #getValueResults()} and {@link #getBeanResults()}. + *

Use {@link #getCrossParameterValidationResults()} for access to errors + * from cross-parameter validation. + * @since 6.2 + * @see #getValueResults() + * @see #getBeanResults() + */ + List getParameterValidationResults(); + /** * Return all validation results. This includes both method parameters with * errors directly on them, and Object method parameters with nested errors * on their fields and properties. * @see #getValueResults() * @see #getBeanResults() + * @deprecated deprecated in favor of {@link #getParameterValidationResults()} */ - List getAllValidationResults(); + @Deprecated(since = "6.2", forRemoval = true) + default List 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 * argument values. This excludes {@link #getBeanResults() beanResults} with * nested errors on their fields and properties. */ default List getValueResults() { - return getAllValidationResults().stream() + return getParameterValidationResults().stream() .filter(result -> !(result instanceof ParameterErrors)) .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 * and properties. This excludes {@link #getValueResults() valueResults} with * validation errors directly on method arguments. */ default List getBeanResults() { - return getAllValidationResults().stream() + return getParameterValidationResults().stream() .filter(ParameterErrors.class::isInstance) .map(result -> (ParameterErrors) result) .toList(); } + /** + * Return errors from cross-parameter validation. + * @since 6.2 + */ + List getCrossParameterValidationResults(); + /** * Factory method to create a {@link MethodValidationResult} instance. @@ -112,7 +134,23 @@ public interface MethodValidationResult { * @return the created instance */ static MethodValidationResult create(Object target, Method method, List 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 results, + List crossParameterErrors) { + + return new DefaultMethodValidationResult(target, method, results, crossParameterErrors); } /** diff --git a/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationAdapterTests.java b/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationAdapterTests.java index 76a7ab7f208..8326a5c2387 100644 --- a/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationAdapterTests.java +++ b/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationAdapterTests.java @@ -16,18 +16,27 @@ 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.util.List; import java.util.Locale; import java.util.Set; import java.util.function.Consumer; +import jakarta.validation.Constraint; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; import jakarta.validation.ConstraintViolation; +import jakarta.validation.Payload; import jakarta.validation.Valid; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; 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.BeforeEach; 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.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; /** @@ -77,7 +89,7 @@ class MethodValidationAdapterTests { 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(""" 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 -> { - assertThat(ex.getAllValidationResults()).hasSize(1); + assertThat(ex.getParameterValidationResults()).hasSize(1); assertBeanResult(ex.getBeanResults().get(0), 0, "studentToAdd", faustino1234, List.of(""" Field error in object 'studentToAdd' on field 'name': rejected value [Faustino1234]; \ @@ -134,7 +146,7 @@ class MethodValidationAdapterTests { 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(""" org.springframework.validation.beanvalidation.MethodValidationAdapter$ViolationMessageSourceResolvable: \ @@ -151,7 +163,7 @@ class MethodValidationAdapterTests { 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(""" 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 -> { - assertThat(ex.getAllValidationResults()).hasSize(2); + assertThat(ex.getParameterValidationResults()).hasSize(2); int paramIndex = 0; String objectName = "people"; @@ -203,7 +215,7 @@ class MethodValidationAdapterTests { Method method = getMethod(target, "addHobbies"); 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(""" org.springframework.validation.beanvalidation.MethodValidationAdapter$ViolationMessageSourceResolvable: \ codes [NotBlank.myService#addHobbies.hobbies,NotBlank.hobbies,NotBlank.java.util.List,NotBlank]; \ @@ -219,7 +231,7 @@ class MethodValidationAdapterTests { Method method = getMethod(target, "addUniqueHobbies"); 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(""" org.springframework.validation.beanvalidation.MethodValidationAdapter$ViolationMessageSourceResolvable: \ 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 consumer) { 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) { } + + @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) { } + + @Documented + @Constraint(validatedBy = RangeParamsValidator.class) + @Target({ CONSTRUCTOR, METHOD }) + @Retention(RUNTIME) + public @interface RangeParams { + + String message() default "Invalid range"; + + Class[] groups() default {}; + + Class[] payload() default {}; + + } + + + @SupportedValidationTarget(ValidationTarget.PARAMETERS) + public static final class RangeParamsValidator implements ConstraintValidator { + + @Override + public boolean isValid(final Object[] parameters, final ConstraintValidatorContext context) { + return false; + } + } + } diff --git a/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationProxyReactorTests.java b/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationProxyReactorTests.java index b201a2cca60..4e9b4ad4097 100644 --- a/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationProxyReactorTests.java +++ b/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationProxyReactorTests.java @@ -81,7 +81,7 @@ class MethodValidationProxyReactorTests { StepVerifier.create(myService.addPerson(personMono)) .expectErrorSatisfies(t -> { MethodValidationException ex = (MethodValidationException) t; - assertThat(ex.getAllValidationResults()).hasSize(1); + assertThat(ex.getParameterValidationResults()).hasSize(1); ParameterErrors errors = ex.getBeanResults().get(0); assertThat(errors.getErrorCount()).isEqualTo(1); diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidationException.java b/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidationException.java index dddc70e42b2..019576c4d1a 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidationException.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidationException.java @@ -22,6 +22,7 @@ import java.util.Locale; import java.util.function.Predicate; import org.springframework.context.MessageSource; +import org.springframework.context.MessageSourceResolvable; import org.springframework.core.MethodParameter; import org.springframework.http.HttpStatus; import org.springframework.lang.Nullable; @@ -107,8 +108,13 @@ public class HandlerMethodValidationException extends ResponseStatusException im } @Override - public List getAllValidationResults() { - return this.validationResult.getAllValidationResults(); + public List getParameterValidationResults() { + return this.validationResult.getParameterValidationResults(); + } + + @Override + public List getCrossParameterValidationResults() { + return this.validationResult.getCrossParameterValidationResults(); } /** @@ -116,7 +122,7 @@ public class HandlerMethodValidationException extends ResponseStatusException im * through callback methods organized by controller method parameter type. */ public void visitResults(Visitor visitor) { - for (ParameterValidationResult result : getAllValidationResults()) { + for (ParameterValidationResult result : getParameterValidationResults()) { MethodParameter param = result.getMethodParameter(); CookieValue cookieValue = param.getParameterAnnotation(CookieValue.class); if (cookieValue != null) { diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidator.java b/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidator.java index 17ae0da5d63..e854fcab751 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidator.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidator.java @@ -98,7 +98,7 @@ public final class HandlerMethodValidator implements MethodValidator { } } } - if (result.getAllValidationResults().size() == bindingResultCount) { + if (result.getParameterValidationResults().size() == bindingResultCount) { return; } } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MethodValidationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MethodValidationTests.java index 2e550b4d572..36b1652b153 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MethodValidationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MethodValidationTests.java @@ -208,7 +208,7 @@ class MethodValidationTests { assertThat(this.jakartaValidator.getValidationCount()).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( """ @@ -245,7 +245,7 @@ class MethodValidationTests { assertThat(this.jakartaValidator.getValidationCount()).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( """ diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MethodValidationTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MethodValidationTests.java index f2f6d5a8144..3831e99dbac 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MethodValidationTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MethodValidationTests.java @@ -172,7 +172,7 @@ class MethodValidationTests { assertThat(this.jakartaValidator.getValidationCount()).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(""" 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.getMethodValidationCount()).isEqualTo(1); - assertThat(ex.getAllValidationResults()).hasSize(2); + assertThat(ex.getParameterValidationResults()).hasSize(2); assertBeanResult(ex.getBeanResults().get(0), "personList", List.of(""" Field error in object 'personList' on field 'name': rejected value [Faustino1234]; \