parent
0fef380202
commit
5c5d8e61ae
|
|
@ -85,6 +85,9 @@ public class MethodValidationAdapter {
|
|||
|
||||
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
|
||||
|
||||
@Nullable
|
||||
private BindingResultNameResolver objectNameResolver;
|
||||
|
||||
|
||||
/**
|
||||
* Create an instance using a default JSR-303 validator underneath.
|
||||
|
|
@ -157,6 +160,19 @@ public class MethodValidationAdapter {
|
|||
return this.parameterNameDiscoverer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a resolver for {@link BindingResult} method parameters to match
|
||||
* the behavior of the higher level programming model, e.g. how the name of
|
||||
* {@code @ModelAttribute} or {@code @RequestBody} is determined in Spring MVC.
|
||||
* <p>If this is not configured, then {@link #createBindingResult} will apply
|
||||
* default behavior to resolve the name to use.
|
||||
* behavior applies.
|
||||
* @param nameResolver the resolver to use
|
||||
*/
|
||||
public void setBindingResultNameResolver(BindingResultNameResolver nameResolver) {
|
||||
this.objectNameResolver = nameResolver;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Use this method determine the validation groups to pass into
|
||||
|
|
@ -307,6 +323,10 @@ public class MethodValidationAdapter {
|
|||
|
||||
/**
|
||||
* Select an object name and create a {@link BindingResult} for the argument.
|
||||
* You can configure a {@link #setBindingResultNameResolver(BindingResultNameResolver)
|
||||
* bindingResultNameResolver} to determine in a way that matches the specific
|
||||
* programming model, e.g. {@code @ModelAttribute} or {@code @RequestBody} arguments
|
||||
* in Spring MVC.
|
||||
* <p>By default, the name is based on the parameter name, or for a return type on
|
||||
* {@link Conventions#getVariableNameForReturnType(Method, Class, Object)}.
|
||||
* <p>If a name cannot be determined for any reason, e.g. a return value with
|
||||
|
|
@ -316,22 +336,30 @@ public class MethodValidationAdapter {
|
|||
* @return the determined name
|
||||
*/
|
||||
private BindingResult createBindingResult(MethodParameter parameter, @Nullable Object argument) {
|
||||
// TODO: allow external customization via Function (e.g. from @ModelAttribute + Conventions based on type)
|
||||
String objectName = parameter.getParameterName();
|
||||
int index = parameter.getParameterIndex();
|
||||
if (index == -1) {
|
||||
try {
|
||||
Method method = parameter.getMethod();
|
||||
if (method != null) {
|
||||
Class<?> resolvedType = GenericTypeResolver.resolveReturnType(method, parameter.getContainingClass());
|
||||
objectName = Conventions.getVariableNameForReturnType(method, resolvedType, argument);
|
||||
}
|
||||
String objectName = null;
|
||||
if (this.objectNameResolver != null) {
|
||||
objectName = this.objectNameResolver.resolveName(parameter, argument);
|
||||
}
|
||||
else {
|
||||
if (parameter.getParameterIndex() != -1) {
|
||||
objectName = parameter.getParameterName();
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
// insufficient type information
|
||||
else {
|
||||
try {
|
||||
Method method = parameter.getMethod();
|
||||
if (method != null) {
|
||||
Class<?> containingClass = parameter.getContainingClass();
|
||||
Class<?> resolvedType = GenericTypeResolver.resolveReturnType(method, containingClass);
|
||||
objectName = Conventions.getVariableNameForReturnType(method, resolvedType, argument);
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
// insufficient type information
|
||||
}
|
||||
}
|
||||
}
|
||||
if (objectName == null) {
|
||||
int index = parameter.getParameterIndex();
|
||||
objectName = (parameter.getExecutable().getName() + (index != -1 ? ".arg" + index : ""));
|
||||
}
|
||||
BeanPropertyBindingResult result = new BeanPropertyBindingResult(argument, objectName);
|
||||
|
|
@ -340,6 +368,22 @@ public class MethodValidationAdapter {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Contract to determine the object name of an {@code @Valid} method parameter.
|
||||
*/
|
||||
public interface BindingResultNameResolver {
|
||||
|
||||
/**
|
||||
* Determine the name for the given method parameter.
|
||||
* @param parameter the method parameter
|
||||
* @param value the argument or return value
|
||||
* @return the name to use
|
||||
*/
|
||||
String resolveName(MethodParameter parameter, @Nullable Object value);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builds a validation result for a value method parameter with constraints
|
||||
* declared directly on it.
|
||||
|
|
|
|||
|
|
@ -39,13 +39,14 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
*/
|
||||
public class MethodValidationAdapterTests {
|
||||
|
||||
private static final MethodValidationAdapter validationAdapter = new MethodValidationAdapter();
|
||||
|
||||
private static final Person faustino1234 = new Person("Faustino1234");
|
||||
|
||||
private static final Person cayetana6789 = new Person("Cayetana6789");
|
||||
|
||||
|
||||
private final MethodValidationAdapter validationAdapter = new MethodValidationAdapter();
|
||||
|
||||
|
||||
@Test
|
||||
void validateArguments() {
|
||||
MyService target = new MyService();
|
||||
|
|
@ -83,6 +84,28 @@ public class MethodValidationAdapterTests {
|
|||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateArgumentWithCustomObjectName() {
|
||||
MyService target = new MyService();
|
||||
Method method = getMethod(target, "addStudent");
|
||||
|
||||
this.validationAdapter.setBindingResultNameResolver((parameter, value) -> "studentToAdd");
|
||||
|
||||
validateArguments(target, method, new Object[] {faustino1234, new Person("Joe"), 1}, ex -> {
|
||||
|
||||
assertThat(ex.getConstraintViolations()).hasSize(1);
|
||||
assertThat(ex.getAllValidationResults()).hasSize(1);
|
||||
|
||||
assertBeanResult(ex.getBeanResults().get(0), 0, "studentToAdd", faustino1234, List.of(
|
||||
"""
|
||||
Field error in object 'studentToAdd' on field 'name': rejected value [Faustino1234]; \
|
||||
codes [Size.studentToAdd.name,Size.name,Size.java.lang.String,Size]; \
|
||||
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: \
|
||||
codes [studentToAdd.name,name]; arguments []; default message [name],10,1]; \
|
||||
default message [size must be between 1 and 10]"""));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateReturnValue() {
|
||||
MyService target = new MyService();
|
||||
|
|
@ -158,14 +181,14 @@ public class MethodValidationAdapterTests {
|
|||
Object target, Method method, Object[] arguments, Consumer<MethodValidationResult> assertions) {
|
||||
|
||||
assertions.accept(
|
||||
validationAdapter.validateMethodArguments(target, method, arguments, new Class<?>[0]));
|
||||
this.validationAdapter.validateMethodArguments(target, method, arguments, new Class<?>[0]));
|
||||
}
|
||||
|
||||
private void validateReturnValue(
|
||||
Object target, Method method, @Nullable Object returnValue, Consumer<MethodValidationResult> assertions) {
|
||||
|
||||
assertions.accept(
|
||||
validationAdapter.validateMethodReturnValue(target, method, returnValue, new Class<?>[0]));
|
||||
this.validationAdapter.validateMethodReturnValue(target, method, returnValue, new Class<?>[0]));
|
||||
}
|
||||
|
||||
private static void assertBeanResult(
|
||||
|
|
|
|||
Loading…
Reference in New Issue