Provide access to underlying ConstraintViolation

Closes gh-33025
This commit is contained in:
rstoyanchev
2024-07-12 14:43:12 +01:00
parent 3e48498663
commit bd31e8dacc
7 changed files with 87 additions and 15 deletions

View File

@@ -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}.
*/

View File

@@ -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;
}

View File

@@ -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) {