From 20e9ff9f3d97bdd0f3110ea639799ca78ffb085c Mon Sep 17 00:00:00 2001 From: Yanming Zhou Date: Mon, 11 Mar 2024 11:39:18 +0800 Subject: [PATCH 1/2] Consider HandlerMethodValidationException in DefaultErrorAttributes See gh-39865 --- .../error/DefaultErrorAttributes.java | 16 ++++++ .../servlet/error/DefaultErrorAttributes.java | 37 +++++++++++-- .../error/DefaultErrorAttributesTests.java | 43 +++++++++++++++ .../error/DefaultErrorAttributesTests.java | 52 +++++++++++++++++-- 4 files changed, 141 insertions(+), 7 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributes.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributes.java index 354b46988a8f..18765f6a67a6 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributes.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributes.java @@ -32,6 +32,7 @@ import org.springframework.util.StringUtils; import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; +import org.springframework.validation.method.MethodValidationResult; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.server.ResponseStatusException; @@ -57,6 +58,7 @@ * @author Stephane Nicoll * @author Michele Mancioppi * @author Scott Frederick + * @author Yanming Zhou * @since 2.0.0 * @see ErrorAttributes */ @@ -113,6 +115,14 @@ private String determineMessage(Throwable error, MergedAnnotation 1) ? "errors" : "error"); + } if (error instanceof ResponseStatusException responseStatusException) { return responseStatusException.getReason(); } @@ -147,6 +157,12 @@ private void handleException(Map errorAttributes, Throwable erro errorAttributes.put("errors", result.getAllErrors()); } } + if (error instanceof MethodValidationResult result) { + if (result.hasErrors()) { + errorAttributes.put("errors", + result.getAllErrors().stream().filter(ObjectError.class::isInstance).toList()); + } + } } @Override diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributes.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributes.java index ef02be96709b..4fd891848a2e 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributes.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributes.java @@ -20,6 +20,7 @@ import java.io.StringWriter; import java.util.Date; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import jakarta.servlet.RequestDispatcher; @@ -29,6 +30,7 @@ import org.springframework.boot.web.error.ErrorAttributeOptions; import org.springframework.boot.web.error.ErrorAttributeOptions.Include; +import org.springframework.context.MessageSourceResolvable; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; @@ -36,6 +38,7 @@ import org.springframework.util.StringUtils; import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; +import org.springframework.validation.method.MethodValidationResult; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.HandlerExceptionResolver; @@ -61,6 +64,7 @@ * @author Stephane Nicoll * @author Vedran Pavic * @author Scott Frederick + * @author Yanming Zhou * @since 2.0.0 * @see ErrorAttributes */ @@ -145,13 +149,20 @@ private void addErrorDetails(Map errorAttributes, WebRequest web } private void addErrorMessage(Map errorAttributes, WebRequest webRequest, Throwable error) { - BindingResult result = extractBindingResult(error); - if (result == null) { - addExceptionErrorMessage(errorAttributes, webRequest, error); + MethodValidationResult methodValidationResult = extractMethodValidationResult(error); + if (methodValidationResult != null) { + addMethodValidationResultErrorMessage(errorAttributes, methodValidationResult); } else { - addBindingResultErrorMessage(errorAttributes, result); + BindingResult bindingResult = extractBindingResult(error); + if (bindingResult != null) { + addBindingResultErrorMessage(errorAttributes, bindingResult); + } + else { + addExceptionErrorMessage(errorAttributes, webRequest, error); + } } + } private void addExceptionErrorMessage(Map errorAttributes, WebRequest webRequest, Throwable error) { @@ -189,6 +200,17 @@ private void addBindingResultErrorMessage(Map errorAttributes, B errorAttributes.put("errors", result.getAllErrors()); } + private void addMethodValidationResultErrorMessage(Map errorAttributes, + MethodValidationResult result) { + List errors = result.getAllErrors() + .stream() + .filter(ObjectError.class::isInstance) + .toList(); + errorAttributes.put("message", + "Validation failed for method='" + result.getMethod() + "'. " + "Error count: " + errors.size()); + errorAttributes.put("errors", errors); + } + private BindingResult extractBindingResult(Throwable error) { if (error instanceof BindingResult bindingResult) { return bindingResult; @@ -196,6 +218,13 @@ private BindingResult extractBindingResult(Throwable error) { return null; } + private MethodValidationResult extractMethodValidationResult(Throwable error) { + if (error instanceof MethodValidationResult methodValidationResult) { + return methodValidationResult; + } + return null; + } + private void addStackTrace(Map errorAttributes, Throwable error) { StringWriter stackTrace = new StringWriter(); error.printStackTrace(new PrintWriter(stackTrace)); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributesTests.java index 9b3d63375c5f..75baf29c65fc 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributesTests.java @@ -35,8 +35,11 @@ import org.springframework.validation.BindingResult; import org.springframework.validation.MapBindingResult; import org.springframework.validation.ObjectError; +import org.springframework.validation.method.MethodValidationResult; +import org.springframework.validation.method.ParameterValidationResult; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.support.WebExchangeBindException; +import org.springframework.web.method.annotation.HandlerMethodValidationException; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ServerWebExchange; @@ -50,6 +53,7 @@ * @author Brian Clozel * @author Stephane Nicoll * @author Scott Frederick + * @author Yanming Zhou */ class DefaultErrorAttributesTests { @@ -246,6 +250,45 @@ void extractBindingResultErrors() throws Exception { assertThat(attributes).containsEntry("errors", bindingResult.getAllErrors()); } + @Test + void extractMethodValidationResultErrors() throws Exception { + Object target = "test"; + Method method = String.class.getMethod("substring", int.class); + MethodParameter parameter = new MethodParameter(method, 0); + MethodValidationResult methodValidationResult = new MethodValidationResult() { + + @Override + public Object getTarget() { + return target; + } + + @Override + public Method getMethod() { + return method; + } + + @Override + public boolean isForReturnValue() { + return false; + } + + @Override + public List getAllValidationResults() { + return List.of(new ParameterValidationResult(parameter, -1, + List.of(new ObjectError("beginIndex", "beginIndex is negative")), null, null, null)); + } + }; + HandlerMethodValidationException ex = new HandlerMethodValidationException(methodValidationResult); + MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); + Map attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), + ErrorAttributeOptions.of(Include.MESSAGE, Include.BINDING_ERRORS)); + assertThat(attributes.get("message")).asString() + .isEqualTo( + "Validation failed for method: public java.lang.String java.lang.String.substring(int), with 1 error"); + assertThat(attributes).containsEntry("errors", + methodValidationResult.getAllErrors().stream().filter(ObjectError.class::isInstance).toList()); + } + @Test void extractBindingResultErrorsExcludeMessageAndErrors() throws Exception { Method method = getClass().getDeclaredMethod("method", String.class); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributesTests.java index fe6848d0cb3f..a930bba9ca75 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributesTests.java @@ -19,13 +19,16 @@ import java.lang.reflect.Method; import java.util.Collections; import java.util.Date; +import java.util.List; import java.util.Map; +import java.util.function.Supplier; import jakarta.servlet.ServletException; import org.junit.jupiter.api.Test; import org.springframework.boot.web.error.ErrorAttributeOptions; import org.springframework.boot.web.error.ErrorAttributeOptions.Include; +import org.springframework.context.MessageSourceResolvable; import org.springframework.core.MethodParameter; import org.springframework.http.HttpStatus; import org.springframework.mock.web.MockHttpServletRequest; @@ -34,9 +37,12 @@ import org.springframework.validation.BindingResult; import org.springframework.validation.MapBindingResult; import org.springframework.validation.ObjectError; +import org.springframework.validation.method.MethodValidationResult; +import org.springframework.validation.method.ParameterValidationResult; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.WebRequest; +import org.springframework.web.method.annotation.HandlerMethodValidationException; import org.springframework.web.servlet.ModelAndView; import static org.assertj.core.api.Assertions.assertThat; @@ -47,6 +53,7 @@ * @author Phillip Webb * @author Vedran Pavic * @author Scott Frederick + * @author Yanming Zhou */ class DefaultErrorAttributesTests { @@ -201,18 +208,57 @@ void withMethodArgumentNotValidExceptionBindingErrors() { testBindingResult(bindingResult, ex, ErrorAttributeOptions.of(Include.MESSAGE, Include.BINDING_ERRORS)); } + @Test + void withHandlerMethodValidationExceptionBindingErrors() { + Object target = "test"; + Method method = ReflectionUtils.findMethod(String.class, "substring", int.class); + MethodParameter parameter = new MethodParameter(method, 0); + MethodValidationResult methodValidationResult = new MethodValidationResult() { + + @Override + public Object getTarget() { + return target; + } + + @Override + public Method getMethod() { + return method; + } + + @Override + public boolean isForReturnValue() { + return false; + } + + @Override + public List getAllValidationResults() { + return List.of(new ParameterValidationResult(parameter, -1, + List.of(new ObjectError("beginIndex", "beginIndex is negative")), null, null, null)); + } + }; + HandlerMethodValidationException ex = new HandlerMethodValidationException(methodValidationResult); + testErrorsSupplier(methodValidationResult::getAllErrors, + "Validation failed for method='public java.lang.String java.lang.String.substring(int)'. Error count: 1", + ex, ErrorAttributeOptions.of(Include.MESSAGE, Include.BINDING_ERRORS)); + } + private void testBindingResult(BindingResult bindingResult, Exception ex, ErrorAttributeOptions options) { + testErrorsSupplier(bindingResult::getAllErrors, "Validation failed for object='objectName'. Error count: 1", ex, + options); + } + + private void testErrorsSupplier(Supplier> errorsSupplier, + String expectedMessage, Exception ex, ErrorAttributeOptions options) { this.request.setAttribute("jakarta.servlet.error.exception", ex); Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, options); if (options.isIncluded(Include.MESSAGE)) { - assertThat(attributes).containsEntry("message", - "Validation failed for object='objectName'. Error count: 1"); + assertThat(attributes).containsEntry("message", expectedMessage); } else { assertThat(attributes).doesNotContainKey("message"); } if (options.isIncluded(Include.BINDING_ERRORS)) { - assertThat(attributes).containsEntry("errors", bindingResult.getAllErrors()); + assertThat(attributes).containsEntry("errors", errorsSupplier.get()); } else { assertThat(attributes).doesNotContainKey("errors"); From 82b218937c50da213294413f1f5e8186d854f073 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 22 Apr 2024 15:34:43 +0100 Subject: [PATCH 2/2] Polish "Consider HandlerMethodValidationException in DefaultErrorAttributes" See gh-39865 --- .../error/DefaultErrorAttributes.java | 87 +++++++++---------- .../servlet/error/DefaultErrorAttributes.java | 40 +++++---- .../error/DefaultErrorAttributesTests.java | 30 ++----- .../error/DefaultErrorAttributesTests.java | 39 ++------- 4 files changed, 75 insertions(+), 121 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributes.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributes.java index 18765f6a67a6..a618c45132e2 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributes.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -20,6 +20,7 @@ import java.io.StringWriter; import java.util.Date; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -47,8 +48,8 @@ *
  • error - The error reason
  • *
  • exception - The class name of the root exception (if configured)
  • *
  • message - The exception message (if configured)
  • - *
  • errors - Any {@link ObjectError}s from a {@link BindingResult} exception (if - * configured)
  • + *
  • errors - Any {@link ObjectError}s from a {@link BindingResult} or + * {@link MethodValidationResult} exception (if configured)
  • *
  • trace - The exception stack trace (if configured)
  • *
  • path - The URL path when the exception was raised
  • *
  • requestId - Unique ID associated with the current request
  • @@ -95,9 +96,8 @@ private Map getErrorAttributes(ServerRequest request, boolean in HttpStatus errorStatus = determineHttpStatus(error, responseStatusAnnotation); errorAttributes.put("status", errorStatus.value()); errorAttributes.put("error", errorStatus.getReasonPhrase()); - errorAttributes.put("message", determineMessage(error, responseStatusAnnotation)); errorAttributes.put("requestId", request.exchange().getRequest().getId()); - handleException(errorAttributes, determineException(error), includeStackTrace); + handleException(errorAttributes, error, responseStatusAnnotation, includeStackTrace); return errorAttributes; } @@ -111,35 +111,6 @@ private HttpStatus determineHttpStatus(Throwable error, MergedAnnotation responseStatusAnnotation) { - if (error instanceof BindingResult) { - return error.getMessage(); - } - if (error instanceof MethodValidationResult methodValidationResult) { - long errorCount = methodValidationResult.getAllErrors() - .stream() - .filter(ObjectError.class::isInstance) - .count(); - return "Validation failed for method: %s, with %d %s".formatted(methodValidationResult.getMethod(), - errorCount, (errorCount > 1) ? "errors" : "error"); - } - if (error instanceof ResponseStatusException responseStatusException) { - return responseStatusException.getReason(); - } - String reason = responseStatusAnnotation.getValue("reason", String.class).orElse(""); - if (StringUtils.hasText(reason)) { - return reason; - } - return (error.getMessage() != null) ? error.getMessage() : ""; - } - - private Throwable determineException(Throwable error) { - if (error instanceof ResponseStatusException) { - return (error.getCause() != null) ? error.getCause() : error; - } - return error; - } - private void addStackTrace(Map errorAttributes, Throwable error) { StringWriter stackTrace = new StringWriter(); error.printStackTrace(new PrintWriter(stackTrace)); @@ -147,22 +118,44 @@ private void addStackTrace(Map errorAttributes, Throwable error) errorAttributes.put("trace", stackTrace.toString()); } - private void handleException(Map errorAttributes, Throwable error, boolean includeStackTrace) { - errorAttributes.put("exception", error.getClass().getName()); - if (includeStackTrace) { - addStackTrace(errorAttributes, error); + private void handleException(Map errorAttributes, Throwable error, + MergedAnnotation responseStatusAnnotation, boolean includeStackTrace) { + Throwable exception; + if (error instanceof BindingResult bindingResult) { + errorAttributes.put("message", error.getMessage()); + errorAttributes.put("errors", bindingResult.getAllErrors()); + exception = error; } - if (error instanceof BindingResult result) { - if (result.hasErrors()) { - errorAttributes.put("errors", result.getAllErrors()); - } + else if (error instanceof MethodValidationResult methodValidationResult) { + addMessageAndErrorsFromMethodValidationResult(errorAttributes, methodValidationResult); + exception = error; } - if (error instanceof MethodValidationResult result) { - if (result.hasErrors()) { - errorAttributes.put("errors", - result.getAllErrors().stream().filter(ObjectError.class::isInstance).toList()); - } + else if (error instanceof ResponseStatusException responseStatusException) { + errorAttributes.put("message", responseStatusException.getReason()); + exception = (responseStatusException.getCause() != null) ? responseStatusException.getCause() : error; + } + else { + exception = error; + String reason = responseStatusAnnotation.getValue("reason", String.class).orElse(""); + String message = StringUtils.hasText(reason) ? reason : error.getMessage(); + errorAttributes.put("message", (message != null) ? message : ""); } + errorAttributes.put("exception", exception.getClass().getName()); + if (includeStackTrace) { + addStackTrace(errorAttributes, exception); + } + } + + private void addMessageAndErrorsFromMethodValidationResult(Map errorAttributes, + MethodValidationResult result) { + List errors = result.getAllErrors() + .stream() + .filter(ObjectError.class::isInstance) + .map(ObjectError.class::cast) + .toList(); + errorAttributes.put("message", + "Validation failed for method='" + result.getMethod() + "'. Error count: " + errors.size()); + errorAttributes.put("errors", errors); } @Override diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributes.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributes.java index 4fd891848a2e..1dccd96f28da 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributes.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 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. @@ -30,7 +30,6 @@ import org.springframework.boot.web.error.ErrorAttributeOptions; import org.springframework.boot.web.error.ErrorAttributeOptions.Include; -import org.springframework.context.MessageSourceResolvable; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; @@ -53,8 +52,8 @@ *
  • error - The error reason
  • *
  • exception - The class name of the root exception (if configured)
  • *
  • message - The exception message (if configured)
  • - *
  • errors - Any {@link ObjectError}s from a {@link BindingResult} exception (if - * configured)
  • + *
  • errors - Any {@link ObjectError}s from a {@link BindingResult} or + * {@link MethodValidationResult} exception (if configured)
  • *
  • trace - The exception stack trace (if configured)
  • *
  • path - The URL path when the exception was raised
  • * @@ -149,20 +148,19 @@ private void addErrorDetails(Map errorAttributes, WebRequest web } private void addErrorMessage(Map errorAttributes, WebRequest webRequest, Throwable error) { - MethodValidationResult methodValidationResult = extractMethodValidationResult(error); - if (methodValidationResult != null) { - addMethodValidationResultErrorMessage(errorAttributes, methodValidationResult); + BindingResult bindingResult = extractBindingResult(error); + if (bindingResult != null) { + addMessageAndErrorsFromBindingResult(errorAttributes, bindingResult); } else { - BindingResult bindingResult = extractBindingResult(error); - if (bindingResult != null) { - addBindingResultErrorMessage(errorAttributes, bindingResult); + MethodValidationResult methodValidationResult = extractMethodValidationResult(error); + if (methodValidationResult != null) { + addMessageAndErrorsFromMethodValidationResult(errorAttributes, methodValidationResult); } else { addExceptionErrorMessage(errorAttributes, webRequest, error); } } - } private void addExceptionErrorMessage(Map errorAttributes, WebRequest webRequest, Throwable error) { @@ -194,20 +192,24 @@ protected String getMessage(WebRequest webRequest, Throwable error) { return "No message available"; } - private void addBindingResultErrorMessage(Map errorAttributes, BindingResult result) { - errorAttributes.put("message", "Validation failed for object='" + result.getObjectName() + "'. " - + "Error count: " + result.getErrorCount()); - errorAttributes.put("errors", result.getAllErrors()); + private void addMessageAndErrorsFromBindingResult(Map errorAttributes, BindingResult result) { + addMessageAndErrorsForValidationFailure(errorAttributes, "object='" + result.getObjectName() + "'", + result.getAllErrors()); } - private void addMethodValidationResultErrorMessage(Map errorAttributes, + private void addMessageAndErrorsFromMethodValidationResult(Map errorAttributes, MethodValidationResult result) { - List errors = result.getAllErrors() + List errors = result.getAllErrors() .stream() .filter(ObjectError.class::isInstance) + .map(ObjectError.class::cast) .toList(); - errorAttributes.put("message", - "Validation failed for method='" + result.getMethod() + "'. " + "Error count: " + errors.size()); + addMessageAndErrorsForValidationFailure(errorAttributes, "method='" + result.getMethod() + "'", errors); + } + + private void addMessageAndErrorsForValidationFailure(Map errorAttributes, String validated, + List errors) { + errorAttributes.put("message", "Validation failed for " + validated + ". Error count: " + errors.size()); errorAttributes.put("errors", errors); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributesTests.java index 75baf29c65fc..797dbcf8970f 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -255,36 +255,16 @@ void extractMethodValidationResultErrors() throws Exception { Object target = "test"; Method method = String.class.getMethod("substring", int.class); MethodParameter parameter = new MethodParameter(method, 0); - MethodValidationResult methodValidationResult = new MethodValidationResult() { - - @Override - public Object getTarget() { - return target; - } - - @Override - public Method getMethod() { - return method; - } - - @Override - public boolean isForReturnValue() { - return false; - } - - @Override - public List getAllValidationResults() { - return List.of(new ParameterValidationResult(parameter, -1, - List.of(new ObjectError("beginIndex", "beginIndex is negative")), null, null, null)); - } - }; + MethodValidationResult methodValidationResult = MethodValidationResult.create(target, method, + List.of(new ParameterValidationResult(parameter, -1, + List.of(new ObjectError("beginIndex", "beginIndex is negative")), null, null, null))); HandlerMethodValidationException ex = new HandlerMethodValidationException(methodValidationResult); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); Map attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), ErrorAttributeOptions.of(Include.MESSAGE, Include.BINDING_ERRORS)); assertThat(attributes.get("message")).asString() .isEqualTo( - "Validation failed for method: public java.lang.String java.lang.String.substring(int), with 1 error"); + "Validation failed for method='public java.lang.String java.lang.String.substring(int)'. Error count: 1"); assertThat(attributes).containsEntry("errors", methodValidationResult.getAllErrors().stream().filter(ObjectError.class::isInstance).toList()); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributesTests.java index a930bba9ca75..9ffa5cbffc12 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -21,7 +21,6 @@ import java.util.Date; import java.util.List; import java.util.Map; -import java.util.function.Supplier; import jakarta.servlet.ServletException; import org.junit.jupiter.api.Test; @@ -213,42 +212,22 @@ void withHandlerMethodValidationExceptionBindingErrors() { Object target = "test"; Method method = ReflectionUtils.findMethod(String.class, "substring", int.class); MethodParameter parameter = new MethodParameter(method, 0); - MethodValidationResult methodValidationResult = new MethodValidationResult() { - - @Override - public Object getTarget() { - return target; - } - - @Override - public Method getMethod() { - return method; - } - - @Override - public boolean isForReturnValue() { - return false; - } - - @Override - public List getAllValidationResults() { - return List.of(new ParameterValidationResult(parameter, -1, - List.of(new ObjectError("beginIndex", "beginIndex is negative")), null, null, null)); - } - }; + MethodValidationResult methodValidationResult = MethodValidationResult.create(target, method, + List.of(new ParameterValidationResult(parameter, -1, + List.of(new ObjectError("beginIndex", "beginIndex is negative")), null, null, null))); HandlerMethodValidationException ex = new HandlerMethodValidationException(methodValidationResult); - testErrorsSupplier(methodValidationResult::getAllErrors, + testErrors(methodValidationResult.getAllErrors(), "Validation failed for method='public java.lang.String java.lang.String.substring(int)'. Error count: 1", ex, ErrorAttributeOptions.of(Include.MESSAGE, Include.BINDING_ERRORS)); } private void testBindingResult(BindingResult bindingResult, Exception ex, ErrorAttributeOptions options) { - testErrorsSupplier(bindingResult::getAllErrors, "Validation failed for object='objectName'. Error count: 1", ex, + testErrors(bindingResult.getAllErrors(), "Validation failed for object='objectName'. Error count: 1", ex, options); } - private void testErrorsSupplier(Supplier> errorsSupplier, - String expectedMessage, Exception ex, ErrorAttributeOptions options) { + private void testErrors(List errors, String expectedMessage, Exception ex, + ErrorAttributeOptions options) { this.request.setAttribute("jakarta.servlet.error.exception", ex); Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, options); if (options.isIncluded(Include.MESSAGE)) { @@ -258,7 +237,7 @@ private void testErrorsSupplier(Supplier assertThat(attributes).doesNotContainKey("message"); } if (options.isIncluded(Include.BINDING_ERRORS)) { - assertThat(attributes).containsEntry("errors", errorsSupplier.get()); + assertThat(attributes).containsEntry("errors", errors); } else { assertThat(attributes).doesNotContainKey("errors");