Add default web handling of method validation errors
Closes gh-30644
This commit is contained in:
@@ -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.
|
||||
@@ -25,15 +25,18 @@ import reactor.core.publisher.Mono;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.MessageSourceAware;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.ProblemDetail;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.validation.beanvalidation.MethodValidationException;
|
||||
import org.springframework.web.ErrorResponse;
|
||||
import org.springframework.web.ErrorResponseException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.support.WebExchangeBindException;
|
||||
import org.springframework.web.method.annotation.HandlerMethodValidationException;
|
||||
import org.springframework.web.server.MethodNotAllowedException;
|
||||
import org.springframework.web.server.MissingRequestValueException;
|
||||
import org.springframework.web.server.NotAcceptableStatusException;
|
||||
@@ -97,10 +100,12 @@ public abstract class ResponseEntityExceptionHandler implements MessageSourceAwa
|
||||
MissingRequestValueException.class,
|
||||
UnsatisfiedRequestParameterException.class,
|
||||
WebExchangeBindException.class,
|
||||
HandlerMethodValidationException.class,
|
||||
ServerWebInputException.class,
|
||||
ServerErrorException.class,
|
||||
ResponseStatusException.class,
|
||||
ErrorResponseException.class
|
||||
ErrorResponseException.class,
|
||||
MethodValidationException.class
|
||||
})
|
||||
public final Mono<ResponseEntity<Object>> handleException(Exception ex, ServerWebExchange exchange) {
|
||||
if (ex instanceof MethodNotAllowedException theEx) {
|
||||
@@ -121,6 +126,9 @@ public abstract class ResponseEntityExceptionHandler implements MessageSourceAwa
|
||||
else if (ex instanceof WebExchangeBindException theEx) {
|
||||
return handleWebExchangeBindException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
|
||||
}
|
||||
else if (ex instanceof HandlerMethodValidationException theEx) {
|
||||
return handleHandlerMethodValidationException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
|
||||
}
|
||||
else if (ex instanceof ServerWebInputException theEx) {
|
||||
return handleServerWebInputException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
|
||||
}
|
||||
@@ -133,6 +141,9 @@ public abstract class ResponseEntityExceptionHandler implements MessageSourceAwa
|
||||
else if (ex instanceof ErrorResponseException theEx) {
|
||||
return handleErrorResponseException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
|
||||
}
|
||||
else if (ex instanceof MethodValidationException theEx) {
|
||||
return handleMethodValidationException(theEx, HttpStatus.INTERNAL_SERVER_ERROR, exchange);
|
||||
}
|
||||
else {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Unexpected exception type: " + ex.getClass().getName());
|
||||
@@ -237,6 +248,23 @@ public abstract class ResponseEntityExceptionHandler implements MessageSourceAwa
|
||||
return handleExceptionInternal(ex, null, headers, status, exchange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize the handling of {@link HandlerMethodValidationException}.
|
||||
* <p>This method delegates to {@link #handleExceptionInternal}.
|
||||
* @param ex the exception to handle
|
||||
* @param headers the headers to use for the response
|
||||
* @param status the status code to use for the response
|
||||
* @param exchange the current request and response
|
||||
* @return a {@code Mono} with the {@code ResponseEntity} for the response
|
||||
* @since 6.1
|
||||
*/
|
||||
protected Mono<ResponseEntity<Object>> handleHandlerMethodValidationException(
|
||||
HandlerMethodValidationException ex, HttpHeaders headers, HttpStatusCode status,
|
||||
ServerWebExchange exchange) {
|
||||
|
||||
return handleExceptionInternal(ex, null, headers, status, exchange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize the handling of {@link ServerWebInputException}.
|
||||
* <p>This method delegates to {@link #handleExceptionInternal}.
|
||||
@@ -301,6 +329,22 @@ public abstract class ResponseEntityExceptionHandler implements MessageSourceAwa
|
||||
return handleExceptionInternal(ex, null, headers, status, exchange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize the handling of {@link MethodValidationException}.
|
||||
* <p>This method delegates to {@link #handleExceptionInternal}.
|
||||
* @param ex the exception to handle
|
||||
* @param status the status code to use for the response
|
||||
* @param exchange the current request and response
|
||||
* @return a {@code Mono} with the {@code ResponseEntity} for the response
|
||||
* @since 6.1
|
||||
*/
|
||||
protected Mono<ResponseEntity<Object>> handleMethodValidationException(
|
||||
MethodValidationException ex, HttpStatus status, ServerWebExchange exchange) {
|
||||
|
||||
ProblemDetail body = createProblemDetail(ex, status, "Validation failed", null, null, exchange);
|
||||
return handleExceptionInternal(ex, body, null, status, exchange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to create a {@link ProblemDetail} for any exception
|
||||
* that doesn't implement {@link ErrorResponse}, also performing a
|
||||
|
||||
@@ -43,7 +43,6 @@ import org.springframework.validation.FieldError;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
import org.springframework.validation.beanvalidation.MethodValidationException;
|
||||
import org.springframework.validation.beanvalidation.ParameterValidationResult;
|
||||
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
@@ -55,6 +54,7 @@ import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
|
||||
import org.springframework.web.bind.support.WebExchangeBindException;
|
||||
import org.springframework.web.context.support.GenericWebApplicationContext;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.method.annotation.HandlerMethodValidationException;
|
||||
import org.springframework.web.reactive.HandlerResult;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest;
|
||||
@@ -202,7 +202,7 @@ public class MethodValidationTests {
|
||||
|
||||
StepVerifier.create(this.handlerAdapter.handle(exchange, hm))
|
||||
.consumeErrorWith(throwable -> {
|
||||
MethodValidationException ex = (MethodValidationException) throwable;
|
||||
HandlerMethodValidationException ex = (HandlerMethodValidationException) throwable;
|
||||
|
||||
assertThat(this.jakartaValidator.getValidationCount()).isEqualTo(1);
|
||||
assertThat(this.jakartaValidator.getMethodValidationCount()).isEqualTo(1);
|
||||
|
||||
@@ -37,9 +37,12 @@ import org.springframework.http.ProblemDetail;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.validation.BeanPropertyBindingResult;
|
||||
import org.springframework.validation.beanvalidation.MethodValidationException;
|
||||
import org.springframework.validation.beanvalidation.MethodValidationResult;
|
||||
import org.springframework.web.ErrorResponse;
|
||||
import org.springframework.web.ErrorResponseException;
|
||||
import org.springframework.web.bind.support.WebExchangeBindException;
|
||||
import org.springframework.web.method.annotation.HandlerMethodValidationException;
|
||||
import org.springframework.web.server.MethodNotAllowedException;
|
||||
import org.springframework.web.server.MissingRequestValueException;
|
||||
import org.springframework.web.server.NotAcceptableStatusException;
|
||||
@@ -53,6 +56,7 @@ import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRe
|
||||
import org.springframework.web.testfixture.server.MockServerWebExchange;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.mock;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ResponseEntityExceptionHandler}.
|
||||
@@ -105,6 +109,21 @@ public class ResponseEntityExceptionHandlerTests {
|
||||
testException(new WebExchangeBindException(null, new BeanPropertyBindingResult(new Object(), "foo")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handlerMethodValidationException() {
|
||||
testException(new HandlerMethodValidationException(mock(MethodValidationResult.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void methodValidationException() {
|
||||
MethodValidationException ex = new MethodValidationException(mock(MethodValidationResult.class));
|
||||
ResponseEntity<?> entity = this.exceptionHandler.handleException(ex, this.exchange).block();
|
||||
|
||||
assertThat(entity).isNotNull();
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
assertThat(entity.getBody()).isInstanceOf(ProblemDetail.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleServerWebInputException() {
|
||||
testException(new ServerWebInputException(""));
|
||||
|
||||
Reference in New Issue
Block a user