Add Visitor to HandlerMethodValidationException
Closes gh-30813
This commit is contained in:
@@ -19,11 +19,24 @@ package org.springframework.web.method.annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.validation.method.MethodValidationResult;
|
||||
import org.springframework.validation.method.ParameterErrors;
|
||||
import org.springframework.validation.method.ParameterValidationResult;
|
||||
import org.springframework.web.bind.annotation.CookieValue;
|
||||
import org.springframework.web.bind.annotation.MatrixVariable;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.util.BindErrorUtils;
|
||||
|
||||
@@ -43,10 +56,24 @@ public class HandlerMethodValidationException extends ResponseStatusException im
|
||||
|
||||
private final MethodValidationResult validationResult;
|
||||
|
||||
private final Predicate<MethodParameter> modelAttribitePredicate;
|
||||
|
||||
private final Predicate<MethodParameter> requestParamPredicate;
|
||||
|
||||
|
||||
public HandlerMethodValidationException(MethodValidationResult validationResult) {
|
||||
this(validationResult,
|
||||
param -> param.hasParameterAnnotation(ModelAttribute.class),
|
||||
param -> param.hasParameterAnnotation(RequestParam.class));
|
||||
}
|
||||
|
||||
public HandlerMethodValidationException(MethodValidationResult validationResult,
|
||||
Predicate<MethodParameter> modelAttribitePredicate, Predicate<MethodParameter> requestParamPredicate) {
|
||||
|
||||
super(initHttpStatus(validationResult), "Validation failure", null, null, null);
|
||||
this.validationResult = validationResult;
|
||||
this.modelAttribitePredicate = modelAttribitePredicate;
|
||||
this.requestParamPredicate = requestParamPredicate;
|
||||
}
|
||||
|
||||
private static HttpStatus initHttpStatus(MethodValidationResult validationResult) {
|
||||
@@ -84,4 +111,133 @@ public class HandlerMethodValidationException extends ResponseStatusException im
|
||||
return this.validationResult.getAllValidationResults();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a {@link Visitor Visitor} to handle {@link ParameterValidationResult}s
|
||||
* through callback methods organized by controller method parameter type.
|
||||
*/
|
||||
public void visitResults(Visitor visitor) {
|
||||
for (ParameterValidationResult result : getAllValidationResults()) {
|
||||
MethodParameter param = result.getMethodParameter();
|
||||
CookieValue cookieValue = param.getParameterAnnotation(CookieValue.class);
|
||||
if (cookieValue != null) {
|
||||
visitor.cookieValue(cookieValue, result);
|
||||
continue;
|
||||
}
|
||||
MatrixVariable matrixVariable = param.getParameterAnnotation(MatrixVariable.class);
|
||||
if (matrixVariable != null) {
|
||||
visitor.matrixVariable(matrixVariable, result);
|
||||
continue;
|
||||
}
|
||||
if (this.modelAttribitePredicate.test(param)) {
|
||||
ModelAttribute modelAttribute = param.getParameterAnnotation(ModelAttribute.class);
|
||||
visitor.modelAttribute(modelAttribute, asErrors(result));
|
||||
continue;
|
||||
}
|
||||
PathVariable pathVariable = param.getParameterAnnotation(PathVariable.class);
|
||||
if (pathVariable != null) {
|
||||
visitor.pathVariable(pathVariable, result);
|
||||
continue;
|
||||
}
|
||||
RequestBody requestBody = param.getParameterAnnotation(RequestBody.class);
|
||||
if (requestBody != null) {
|
||||
visitor.requestBody(requestBody, asErrors(result));
|
||||
continue;
|
||||
}
|
||||
RequestHeader requestHeader = param.getParameterAnnotation(RequestHeader.class);
|
||||
if (requestHeader != null) {
|
||||
visitor.requestHeader(requestHeader, result);
|
||||
continue;
|
||||
}
|
||||
if (this.requestParamPredicate.test(param)) {
|
||||
RequestParam requestParam = param.getParameterAnnotation(RequestParam.class);
|
||||
visitor.requestParam(requestParam, result);
|
||||
continue;
|
||||
}
|
||||
RequestPart requestPart = param.getParameterAnnotation(RequestPart.class);
|
||||
if (requestPart != null) {
|
||||
visitor.requestPart(requestPart, asErrors(result));
|
||||
continue;
|
||||
}
|
||||
visitor.other(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static ParameterErrors asErrors(ParameterValidationResult result) {
|
||||
Assert.state(result instanceof ParameterErrors, "Expected ParameterErrors");
|
||||
return (ParameterErrors) result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Contract to handle validation results with callbacks by controller method
|
||||
* parameter type, with {@link #other} serving as the fallthrough.
|
||||
*/
|
||||
public interface Visitor {
|
||||
|
||||
/**
|
||||
* Handle results for {@code @CookieValue} method parameters.
|
||||
* @param cookieValue the annotation declared on the parameter
|
||||
* @param result the validation result
|
||||
*/
|
||||
void cookieValue(CookieValue cookieValue, ParameterValidationResult result);
|
||||
|
||||
/**
|
||||
* Handle results for {@code @MatrixVariable} method parameters.
|
||||
* @param matrixVariable the annotation declared on the parameter
|
||||
* @param result the validation result
|
||||
*/
|
||||
void matrixVariable(MatrixVariable matrixVariable, ParameterValidationResult result);
|
||||
|
||||
/**
|
||||
* Handle results for {@code @ModelAttribute} method parameters.
|
||||
* @param modelAttribute the optional {@code ModelAttribute} annotation,
|
||||
* possibly {@code null} if the method parameter is declared without it.
|
||||
* @param errors the validation errors
|
||||
*/
|
||||
void modelAttribute(@Nullable ModelAttribute modelAttribute, ParameterErrors errors);
|
||||
|
||||
/**
|
||||
* Handle results for {@code @PathVariable} method parameters.
|
||||
* @param pathVariable the annotation declared on the parameter
|
||||
* @param result the validation result
|
||||
*/
|
||||
void pathVariable(PathVariable pathVariable, ParameterValidationResult result);
|
||||
|
||||
/**
|
||||
* Handle results for {@code @RequestBody} method parameters.
|
||||
* @param requestBody the annotation declared on the parameter
|
||||
* @param errors the validation error
|
||||
*/
|
||||
void requestBody(RequestBody requestBody, ParameterErrors errors);
|
||||
|
||||
/**
|
||||
* Handle results for {@code @RequestHeader} method parameters.
|
||||
* @param requestHeader the annotation declared on the parameter
|
||||
* @param result the validation result
|
||||
*/
|
||||
void requestHeader(RequestHeader requestHeader, ParameterValidationResult result);
|
||||
|
||||
/**
|
||||
* Handle results for {@code @RequestParam} method parameters.
|
||||
* @param requestParam the optional {@code RequestParam} annotation,
|
||||
* possibly {@code null} if the method parameter is declared without it.
|
||||
* @param result the validation result
|
||||
*/
|
||||
void requestParam(@Nullable RequestParam requestParam, ParameterValidationResult result);
|
||||
|
||||
/**
|
||||
* Handle results for {@code @RequestPart} method parameters.
|
||||
* @param requestPart the annotation declared on the parameter
|
||||
* @param errors the validation errors
|
||||
*/
|
||||
void requestPart(RequestPart requestPart, ParameterErrors errors);
|
||||
|
||||
/**
|
||||
* Handle other results that aren't any of the above.
|
||||
* @param result the validation result
|
||||
*/
|
||||
void other(ParameterValidationResult result);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.web.method.annotation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import jakarta.validation.Validator;
|
||||
|
||||
@@ -54,9 +55,17 @@ public final class HandlerMethodValidator implements MethodValidator {
|
||||
|
||||
private final MethodValidationAdapter validationAdapter;
|
||||
|
||||
private final Predicate<MethodParameter> modelAttribitePredicate;
|
||||
|
||||
private final Predicate<MethodParameter> requestParamPredicate;
|
||||
|
||||
|
||||
private HandlerMethodValidator(MethodValidationAdapter validationAdapter,
|
||||
Predicate<MethodParameter> modelAttribitePredicate, Predicate<MethodParameter> requestParamPredicate) {
|
||||
|
||||
private HandlerMethodValidator(MethodValidationAdapter validationAdapter) {
|
||||
this.validationAdapter = validationAdapter;
|
||||
this.modelAttribitePredicate = modelAttribitePredicate;
|
||||
this.requestParamPredicate = requestParamPredicate;
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +102,8 @@ public final class HandlerMethodValidator implements MethodValidator {
|
||||
}
|
||||
}
|
||||
|
||||
throw new HandlerMethodValidationException(result);
|
||||
throw new HandlerMethodValidationException(
|
||||
result, this.modelAttribitePredicate, this.requestParamPredicate);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -130,11 +140,13 @@ public final class HandlerMethodValidator implements MethodValidator {
|
||||
*/
|
||||
@Nullable
|
||||
public static MethodValidator from(
|
||||
@Nullable WebBindingInitializer initializer, @Nullable ParameterNameDiscoverer paramNameDiscoverer) {
|
||||
@Nullable WebBindingInitializer initializer, @Nullable ParameterNameDiscoverer paramNameDiscoverer,
|
||||
Predicate<MethodParameter> modelAttribitePredicate, Predicate<MethodParameter> requestParamPredicate) {
|
||||
|
||||
if (initializer instanceof ConfigurableWebBindingInitializer configurableInitializer) {
|
||||
if (configurableInitializer.getValidator() instanceof Validator validator) {
|
||||
MethodValidationAdapter adapter = new MethodValidationAdapter(validator);
|
||||
adapter.setObjectNameResolver(objectNameResolver);
|
||||
if (paramNameDiscoverer != null) {
|
||||
adapter.setParameterNameDiscoverer(paramNameDiscoverer);
|
||||
}
|
||||
@@ -142,9 +154,7 @@ public final class HandlerMethodValidator implements MethodValidator {
|
||||
if (codesResolver != null) {
|
||||
adapter.setMessageCodesResolver(codesResolver);
|
||||
}
|
||||
HandlerMethodValidator methodValidator = new HandlerMethodValidator(adapter);
|
||||
adapter.setObjectNameResolver(objectNameResolver);
|
||||
return methodValidator;
|
||||
return new HandlerMethodValidator(adapter, modelAttribitePredicate, requestParamPredicate);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -127,7 +127,7 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu
|
||||
* the given method parameter.
|
||||
*/
|
||||
@Nullable
|
||||
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
|
||||
public HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
|
||||
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
|
||||
if (result == null) {
|
||||
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
|
||||
|
||||
Reference in New Issue
Block a user