Provide access to underlying ConstraintViolation
Closes gh-33025
This commit is contained in:
parent
3e48498663
commit
bd31e8dacc
|
@ -50,6 +50,7 @@ import org.springframework.core.MethodParameter;
|
|||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.function.SingletonSupplier;
|
||||
import org.springframework.validation.BeanPropertyBindingResult;
|
||||
import org.springframework.validation.BindingResult;
|
||||
|
@ -402,7 +403,7 @@ public class MethodValidationAdapter implements MethodValidator {
|
|||
String[] codes = this.messageCodesResolver.resolveMessageCodes(code, objectName, paramName, parameterType);
|
||||
Object[] arguments = this.validatorAdapter.get().getArgumentsForConstraint(objectName, paramName, descriptor);
|
||||
|
||||
return new DefaultMessageSourceResolvable(codes, arguments, violation.getMessage());
|
||||
return new ViolationMessageSourceResolvable(codes, arguments, violation.getMessage(), violation);
|
||||
}
|
||||
|
||||
private BindingResult createBindingResult(MethodParameter parameter, @Nullable Object argument) {
|
||||
|
@ -472,7 +473,11 @@ public class MethodValidationAdapter implements MethodValidator {
|
|||
public ParameterValidationResult build() {
|
||||
return new ParameterValidationResult(
|
||||
this.parameter, this.value, this.resolvableErrors, this.container,
|
||||
this.containerIndex, this.containerKey);
|
||||
this.containerIndex, this.containerKey,
|
||||
(error, sourceType) -> {
|
||||
Assert.isTrue(sourceType.equals(ConstraintViolation.class), "Unexpected source type");
|
||||
return ((ViolationMessageSourceResolvable) error).getViolation();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -526,6 +531,24 @@ public class MethodValidationAdapter implements MethodValidator {
|
|||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class ViolationMessageSourceResolvable extends DefaultMessageSourceResolvable {
|
||||
|
||||
private final transient ConstraintViolation<Object> violation;
|
||||
|
||||
public ViolationMessageSourceResolvable(
|
||||
String[] codes, Object[] arguments, String defaultMessage, ConstraintViolation<Object> violation) {
|
||||
|
||||
super(codes, arguments, defaultMessage);
|
||||
this.violation = violation;
|
||||
}
|
||||
|
||||
public ConstraintViolation<Object> getViolation() {
|
||||
return this.violation;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Default algorithm to select an object name, as described in {@link #setObjectNameResolver}.
|
||||
*/
|
||||
|
|
|
@ -47,7 +47,9 @@ public class ParameterErrors extends ParameterValidationResult implements Errors
|
|||
MethodParameter parameter, @Nullable Object argument, Errors errors,
|
||||
@Nullable Object container, @Nullable Integer index, @Nullable Object key) {
|
||||
|
||||
super(parameter, argument, errors.getAllErrors(), container, index, key);
|
||||
super(parameter, argument, errors.getAllErrors(),
|
||||
container, index, key, (error, sourceType) -> ((FieldError) error).unwrap(sourceType));
|
||||
|
||||
this.errors = errors;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@ package org.springframework.validation.method;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.context.MessageSourceResolvable;
|
||||
import org.springframework.core.MethodParameter;
|
||||
|
@ -62,13 +64,16 @@ public class ParameterValidationResult {
|
|||
@Nullable
|
||||
private final Object containerKey;
|
||||
|
||||
private final BiFunction<MessageSourceResolvable, Class<?>, Object> sourceLookup;
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@code ParameterValidationResult}.
|
||||
*/
|
||||
public ParameterValidationResult(
|
||||
MethodParameter param, @Nullable Object arg, Collection<? extends MessageSourceResolvable> errors,
|
||||
@Nullable Object container, @Nullable Integer index, @Nullable Object key) {
|
||||
@Nullable Object container, @Nullable Integer index, @Nullable Object key,
|
||||
BiFunction<MessageSourceResolvable, Class<?>, Object> sourceLookup) {
|
||||
|
||||
Assert.notNull(param, "MethodParameter is required");
|
||||
Assert.notEmpty(errors, "`resolvableErrors` must not be empty");
|
||||
|
@ -78,18 +83,36 @@ public class ParameterValidationResult {
|
|||
this.container = container;
|
||||
this.containerIndex = index;
|
||||
this.containerKey = key;
|
||||
this.sourceLookup = sourceLookup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code ParameterValidationResult}.
|
||||
* @deprecated in favor of
|
||||
* {@link ParameterValidationResult#ParameterValidationResult(MethodParameter, Object, Collection, Object, Integer, Object)}
|
||||
* {@link ParameterValidationResult#ParameterValidationResult(MethodParameter, Object, Collection, Object, Integer, Object, Function)}
|
||||
*/
|
||||
@Deprecated(since = "6.2", forRemoval = true)
|
||||
public ParameterValidationResult(
|
||||
MethodParameter param, @Nullable Object arg, Collection<? extends MessageSourceResolvable> errors,
|
||||
@Nullable Object container, @Nullable Integer index, @Nullable Object key) {
|
||||
|
||||
this(param, arg, errors, container, index, key, (error, sourceType) -> {
|
||||
throw new IllegalArgumentException("No source object of the given type");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code ParameterValidationResult}.
|
||||
* @deprecated in favor of
|
||||
* {@link ParameterValidationResult#ParameterValidationResult(MethodParameter, Object, Collection, Object, Integer, Object, Function)}
|
||||
*/
|
||||
@Deprecated(since = "6.1.3", forRemoval = true)
|
||||
public ParameterValidationResult(
|
||||
MethodParameter param, @Nullable Object arg, Collection<? extends MessageSourceResolvable> errors) {
|
||||
|
||||
this(param, arg, errors, null, null, null);
|
||||
this(param, arg, errors, null, null, null, (error, sourceType) -> {
|
||||
throw new IllegalArgumentException("No source object of the given type");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
@ -164,6 +187,17 @@ public class ParameterValidationResult {
|
|||
return this.containerKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap the source behind the given error. For Jakarta Bean validation the
|
||||
* source is a {@link jakarta.validation.ConstraintViolation}.
|
||||
* @param sourceType the expected source type
|
||||
* @return the source object of the given type
|
||||
* @since 6.2
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T unwrap(MessageSourceResolvable error, Class<T> sourceType) {
|
||||
return (T) this.sourceLookup.apply(error, sourceType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.util.Locale;
|
|||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
|
@ -99,7 +100,7 @@ class MethodValidationAdapterTests {
|
|||
default message [must not be blank]"""));
|
||||
|
||||
assertValueResult(ex.getValueResults().get(0), 2, 3, List.of("""
|
||||
org.springframework.context.support.DefaultMessageSourceResolvable: \
|
||||
org.springframework.validation.beanvalidation.MethodValidationAdapter$ViolationMessageSourceResolvable: \
|
||||
codes [Max.myService#addStudent.degrees,Max.degrees,Max.int,Max]; \
|
||||
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: \
|
||||
codes [myService#addStudent.degrees,degrees]; arguments []; default message [degrees],2]; \
|
||||
|
@ -136,7 +137,7 @@ class MethodValidationAdapterTests {
|
|||
assertThat(ex.getAllValidationResults()).hasSize(1);
|
||||
|
||||
assertValueResult(ex.getValueResults().get(0), -1, 4, List.of("""
|
||||
org.springframework.context.support.DefaultMessageSourceResolvable: \
|
||||
org.springframework.validation.beanvalidation.MethodValidationAdapter$ViolationMessageSourceResolvable: \
|
||||
codes [Min.myService#getIntValue,Min,Min.int]; \
|
||||
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: \
|
||||
codes [myService#getIntValue]; arguments []; default message [],5]; \
|
||||
|
@ -204,7 +205,7 @@ class MethodValidationAdapterTests {
|
|||
testArgs(target, method, new Object[] {List.of(" ")}, ex -> {
|
||||
assertThat(ex.getAllValidationResults()).hasSize(1);
|
||||
assertValueResult(ex.getValueResults().get(0), 0, " ", List.of("""
|
||||
org.springframework.context.support.DefaultMessageSourceResolvable: \
|
||||
org.springframework.validation.beanvalidation.MethodValidationAdapter$ViolationMessageSourceResolvable: \
|
||||
codes [NotBlank.myService#addHobbies.hobbies,NotBlank.hobbies,NotBlank.java.util.List,NotBlank]; \
|
||||
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: \
|
||||
codes [myService#addHobbies.hobbies,hobbies]; \
|
||||
|
@ -220,7 +221,7 @@ class MethodValidationAdapterTests {
|
|||
testArgs(target, method, new Object[] {Set.of("test", " ")}, ex -> {
|
||||
assertThat(ex.getAllValidationResults()).hasSize(1);
|
||||
assertValueResult(ex.getValueResults().get(0), 0, Set.of("test", " "), List.of("""
|
||||
org.springframework.context.support.DefaultMessageSourceResolvable: \
|
||||
org.springframework.validation.beanvalidation.MethodValidationAdapter$ViolationMessageSourceResolvable: \
|
||||
codes [NotBlank.myService#addUniqueHobbies.hobbies,NotBlank.hobbies,NotBlank.java.util.Set,NotBlank]; \
|
||||
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: \
|
||||
codes [myService#addUniqueHobbies.hobbies,hobbies]; \
|
||||
|
@ -254,9 +255,14 @@ class MethodValidationAdapterTests {
|
|||
|
||||
assertThat(result.getMethodParameter().getParameterIndex()).isEqualTo(parameterIndex);
|
||||
assertThat(result.getArgument()).isEqualTo(argument);
|
||||
assertThat(result.getResolvableErrors())
|
||||
|
||||
List<MessageSourceResolvable> resolvableErrors = result.getResolvableErrors();
|
||||
assertThat(resolvableErrors)
|
||||
.extracting(MessageSourceResolvable::toString)
|
||||
.containsExactlyInAnyOrderElementsOf(errors);
|
||||
|
||||
resolvableErrors.forEach(error ->
|
||||
assertThat(result.unwrap(error, ConstraintViolation.class)).isNotNull());
|
||||
}
|
||||
|
||||
private static Method getMethod(Object target, String methodName) {
|
||||
|
|
|
@ -128,7 +128,8 @@ class HandlerMethodValidationExceptionTests {
|
|||
}
|
||||
else {
|
||||
MessageSourceResolvable error = new DefaultMessageSourceResolvable("Size");
|
||||
return new ParameterValidationResult(param, "123", List.of(error), null, null, null);
|
||||
return new ParameterValidationResult(
|
||||
param, "123", List.of(error), null, null, null, (e, t) -> null);
|
||||
}
|
||||
})
|
||||
.toList());
|
||||
|
|
|
@ -220,7 +220,7 @@ class MethodValidationTests {
|
|||
|
||||
assertValueResult(ex.getValueResults().get(0), 2, "123", Collections.singletonList(
|
||||
"""
|
||||
org.springframework.context.support.DefaultMessageSourceResolvable: \
|
||||
org.springframework.validation.beanvalidation.MethodValidationAdapter$ViolationMessageSourceResolvable: \
|
||||
codes [Size.validController#handle.myHeader,Size.myHeader,Size.java.lang.String,Size]; \
|
||||
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: \
|
||||
codes [validController#handle.myHeader,myHeader]; arguments []; default message [myHeader],10,5]; \
|
||||
|
@ -360,9 +360,15 @@ class MethodValidationTests {
|
|||
|
||||
assertThat(result.getMethodParameter().getParameterIndex()).isEqualTo(parameterIndex);
|
||||
assertThat(result.getArgument()).isEqualTo(argument);
|
||||
assertThat(result.getResolvableErrors())
|
||||
|
||||
List<MessageSourceResolvable> resolvableErrors = result.getResolvableErrors();
|
||||
assertThat(resolvableErrors)
|
||||
.extracting(MessageSourceResolvable::toString)
|
||||
.containsExactlyInAnyOrderElementsOf(errors);
|
||||
|
||||
resolvableErrors.forEach(error ->
|
||||
assertThat(result.unwrap(error, ConstraintViolation.class)).isNotNull());
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -183,7 +183,7 @@ class MethodValidationTests {
|
|||
));
|
||||
|
||||
assertValueResult(ex.getValueResults().get(0), 2, "123", List.of("""
|
||||
org.springframework.context.support.DefaultMessageSourceResolvable: \
|
||||
org.springframework.validation.beanvalidation.MethodValidationAdapter$ViolationMessageSourceResolvable: \
|
||||
codes [Size.validController#handle.myHeader,Size.myHeader,Size.java.lang.String,Size]; \
|
||||
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: \
|
||||
codes [validController#handle.myHeader,myHeader]; arguments []; default message [myHeader],10,5]; \
|
||||
|
|
Loading…
Reference in New Issue