From a30740f8d2e590c1f3adc20fbc4ea9c653c5a94b Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Wed, 29 Apr 2020 17:56:47 -0500 Subject: [PATCH] Separate server properties for message and errors Prior to this commit, there was a property server.error.include-details that allowed configuration of the message and errors attributes in a server error response. This commit separates the control of the message and errors attributes into two separate properties named server.error.include-message and server.error.include-binding-errors. When the message attribute is excluded from a servlet response, the value is changed from a hard-coded text value to an empty value. Fixes gh-20505 --- .../web/servlet/ManagementErrorEndpoint.java | 24 +++- .../servlet/ManagementErrorEndpointTests.java | 26 ++-- ...dContextConfigurationIntegrationTests.java | 4 +- .../AbstractWebEndpointIntegrationTests.java | 2 +- .../autoconfigure/web/ErrorProperties.java | 60 ++++++--- .../AbstractErrorWebExceptionHandler.java | 32 +++-- .../DefaultErrorWebExceptionHandler.java | 43 +++++-- .../error/AbstractErrorController.java | 17 ++- .../servlet/error/BasicErrorController.java | 36 ++++-- ...itional-spring-configuration-metadata.json | 6 +- ...orWebExceptionHandlerIntegrationTests.java | 64 ++++++---- .../DefaultErrorWebExceptionHandlerTests.java | 3 +- .../BasicErrorControllerIntegrationTests.java | 114 ++++++++++-------- .../BasicErrorControllerMockMvcTests.java | 2 +- .../DefaultErrorViewIntegrationTests.java | 2 +- .../error/ErrorMvcAutoConfigurationTests.java | 4 +- ...DevToolsPropertyDefaultsPostProcessor.java | 3 +- .../DevToolPropertiesIntegrationTests.java | 4 +- .../error/DefaultErrorAttributes.java | 23 ++-- .../web/reactive/error/ErrorAttributes.java | 12 +- .../servlet/error/DefaultErrorAttributes.java | 34 +++--- .../web/servlet/error/ErrorAttributes.java | 12 +- .../error/DefaultErrorAttributesTests.java | 42 +++---- .../error/DefaultErrorAttributesTests.java | 52 ++++---- ...ctuatorCustomSecurityApplicationTests.java | 2 +- .../ui/SampleActuatorUiApplicationTests.java | 2 +- 26 files changed, 379 insertions(+), 246 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ManagementErrorEndpoint.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ManagementErrorEndpoint.java index 27902c1c79..be9909a050 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ManagementErrorEndpoint.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ManagementErrorEndpoint.java @@ -53,26 +53,38 @@ public class ManagementErrorEndpoint { @RequestMapping("${server.error.path:${error.path:/error}}") @ResponseBody public Map invoke(ServletWebRequest request) { - return this.errorAttributes.getErrorAttributes(request, includeStackTrace(request), includeDetails(request)); + return this.errorAttributes.getErrorAttributes(request, includeStackTrace(request), includeMessage(request), + includeBindingErrors(request)); } private boolean includeStackTrace(ServletWebRequest request) { switch (this.errorProperties.getIncludeStacktrace()) { case ALWAYS: return true; - case ON_TRACE_PARAM: + case ON_PARAM: return getBooleanParameter(request, "trace"); default: return false; } } - private boolean includeDetails(ServletWebRequest request) { - switch (this.errorProperties.getIncludeDetails()) { + private boolean includeMessage(ServletWebRequest request) { + switch (this.errorProperties.getIncludeMessage()) { case ALWAYS: return true; - case ON_DETAILS_PARAM: - return getBooleanParameter(request, "details"); + case ON_PARAM: + return getBooleanParameter(request, "message"); + default: + return false; + } + } + + private boolean includeBindingErrors(ServletWebRequest request) { + switch (this.errorProperties.getIncludeBindingErrors()) { + case ALWAYS: + return true; + case ON_PARAM: + return getBooleanParameter(request, "errors"); default: return false; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ManagementErrorEndpointTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ManagementErrorEndpointTests.java index 7c4362626a..e9ace84d8d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ManagementErrorEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ManagementErrorEndpointTests.java @@ -51,16 +51,16 @@ class ManagementErrorEndpointTests { void errorResponseNeverDetails() { ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(this.errorAttributes, this.errorProperties); Map response = endpoint.invoke(new ServletWebRequest(new MockHttpServletRequest())); - assertThat(response).containsEntry("message", "An error occurred while processing the request"); + assertThat(response).containsEntry("message", ""); assertThat(response).doesNotContainKey("trace"); } @Test void errorResponseAlwaysDetails() { this.errorProperties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.ALWAYS); - this.errorProperties.setIncludeDetails(ErrorProperties.IncludeDetails.ALWAYS); + this.errorProperties.setIncludeMessage(ErrorProperties.IncludeAttribute.ALWAYS); this.request.addParameter("trace", "false"); - this.request.addParameter("details", "false"); + this.request.addParameter("message", "false"); ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(this.errorAttributes, this.errorProperties); Map response = endpoint.invoke(new ServletWebRequest(this.request)); assertThat(response).containsEntry("message", "test exception"); @@ -70,20 +70,20 @@ class ManagementErrorEndpointTests { @Test void errorResponseParamsAbsent() { - this.errorProperties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM); - this.errorProperties.setIncludeDetails(ErrorProperties.IncludeDetails.ON_DETAILS_PARAM); + this.errorProperties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.ON_PARAM); + this.errorProperties.setIncludeMessage(ErrorProperties.IncludeAttribute.ON_PARAM); ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(this.errorAttributes, this.errorProperties); Map response = endpoint.invoke(new ServletWebRequest(this.request)); - assertThat(response).containsEntry("message", "An error occurred while processing the request"); + assertThat(response).containsEntry("message", ""); assertThat(response).doesNotContainKey("trace"); } @Test void errorResponseParamsTrue() { - this.errorProperties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM); - this.errorProperties.setIncludeDetails(ErrorProperties.IncludeDetails.ON_DETAILS_PARAM); + this.errorProperties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.ON_PARAM); + this.errorProperties.setIncludeMessage(ErrorProperties.IncludeAttribute.ON_PARAM); this.request.addParameter("trace", "true"); - this.request.addParameter("details", "true"); + this.request.addParameter("message", "true"); ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(this.errorAttributes, this.errorProperties); Map response = endpoint.invoke(new ServletWebRequest(this.request)); assertThat(response).containsEntry("message", "test exception"); @@ -93,13 +93,13 @@ class ManagementErrorEndpointTests { @Test void errorResponseParamsFalse() { - this.errorProperties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM); - this.errorProperties.setIncludeDetails(ErrorProperties.IncludeDetails.ON_DETAILS_PARAM); + this.errorProperties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.ON_PARAM); + this.errorProperties.setIncludeMessage(ErrorProperties.IncludeAttribute.ON_PARAM); this.request.addParameter("trace", "false"); - this.request.addParameter("details", "false"); + this.request.addParameter("message", "false"); ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(this.errorAttributes, this.errorProperties); Map response = endpoint.invoke(new ServletWebRequest(this.request)); - assertThat(response).containsEntry("message", "An error occurred while processing the request"); + assertThat(response).containsEntry("message", ""); assertThat(response).doesNotContainKey("trace"); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationIntegrationTests.java index f3630545f9..d0517a20aa 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationIntegrationTests.java @@ -67,14 +67,14 @@ class WebMvcEndpointChildContextConfigurationIntegrationTests { ClientResponse response = client.get().uri("actuator/fail").accept(MediaType.APPLICATION_JSON).exchange() .block(); Map body = response.bodyToMono(Map.class).block(); - assertThat(body).containsEntry("message", "An error occurred while processing the request"); + assertThat(body).containsEntry("message", ""); assertThat(body).doesNotContainKey("trace"); }); } @Test void errorPageAndErrorControllerIncludeDetails() { - this.runner.withPropertyValues("server.error.include-stacktrace=always", "server.error.include-details=always") + this.runner.withPropertyValues("server.error.include-stacktrace=always", "server.error.include-message=always") .run((context) -> { String port = context.getEnvironment().getProperty("local.management.port"); WebClient client = WebClient.create("http://localhost:" + port); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/AbstractWebEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/AbstractWebEndpointIntegrationTests.java index 9e334b2a83..53c7d1da8a 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/AbstractWebEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/AbstractWebEndpointIntegrationTests.java @@ -412,7 +412,7 @@ public abstract class AbstractWebEndpointIntegrationTests properties = new HashMap<>(); properties.put("endpointPath", endpointPath); - properties.put("server.error.include-details", "always"); + properties.put("server.error.include-message", "always"); applicationContext.getEnvironment().getPropertySources().addLast(new MapPropertySource("test", properties)); applicationContext.refresh(); try { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorProperties.java index c7b0e88960..0d7e7a4026 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorProperties.java @@ -46,9 +46,14 @@ public class ErrorProperties { private IncludeStacktrace includeStacktrace = IncludeStacktrace.NEVER; /** - * When to include "message" and "errors" attributes. + * When to include "message" attribute. */ - private IncludeDetails includeDetails = IncludeDetails.NEVER; + private IncludeAttribute includeMessage = IncludeAttribute.NEVER; + + /** + * When to include "errors" attribute. + */ + private IncludeAttribute includeBindingErrors = IncludeAttribute.NEVER; private final Whitelabel whitelabel = new Whitelabel(); @@ -76,12 +81,20 @@ public class ErrorProperties { this.includeStacktrace = includeStacktrace; } - public IncludeDetails getIncludeDetails() { - return this.includeDetails; + public IncludeAttribute getIncludeMessage() { + return this.includeMessage; } - public void setIncludeDetails(IncludeDetails includeDetails) { - this.includeDetails = includeDetails; + public void setIncludeMessage(IncludeAttribute includeMessage) { + this.includeMessage = includeMessage; + } + + public IncludeAttribute getIncludeBindingErrors() { + return this.includeMessage; + } + + public void setIncludeBindingErrors(IncludeAttribute includeMessage) { + this.includeMessage = includeMessage; } public Whitelabel getWhitelabel() { @@ -96,39 +109,56 @@ public class ErrorProperties { /** * Never add stacktrace information. */ - NEVER, + NEVER(IncludeAttribute.NEVER), /** * Always add stacktrace information. */ - ALWAYS, + ALWAYS(IncludeAttribute.ALWAYS), + + /** + * Add error attribute when the appropriate request parameter is "true". + */ + ON_PARAM(IncludeAttribute.ON_PARAM), /** * Add stacktrace information when the "trace" request parameter is "true". + * @deprecated since 2.3.0 in favor of {@link #ON_PARAM} */ - ON_TRACE_PARAM + @Deprecated + ON_TRACE_PARAM(IncludeAttribute.ON_PARAM); + + private final IncludeAttribute include; + + IncludeStacktrace(IncludeAttribute include) { + this.include = include; + } + + public IncludeAttribute getInclude() { + return this.include; + } } /** - * Include error details attributes options. + * Include error attributes options. */ - public enum IncludeDetails { + public enum IncludeAttribute { /** - * Never add error detail information. + * Never add error attribute. */ NEVER, /** - * Always add error detail information. + * Always add error attribute. */ ALWAYS, /** - * Add error details information when the "details" request parameter is "true". + * Add error attribute when the appropriate request parameter is "true". */ - ON_DETAILS_PARAM + ON_PARAM } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java index 3662f3da69..05f3b63550 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java @@ -134,24 +134,26 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept * @param includeStackTrace whether to include the error stacktrace information * @return the error attributes as a Map * @deprecated since 2.3.0 in favor of - * {@link #getErrorAttributes(ServerRequest, boolean, boolean)} + * {@link #getErrorAttributes(ServerRequest, boolean, boolean, boolean)} */ @Deprecated protected Map getErrorAttributes(ServerRequest request, boolean includeStackTrace) { - return this.errorAttributes.getErrorAttributes(request, includeStackTrace, false); + return this.errorAttributes.getErrorAttributes(request, includeStackTrace, false, false); } /** * Extract the error attributes from the current request, to be used to populate error * views or JSON payloads. * @param request the source request - * @param includeStackTrace whether to include the error stacktrace information - * @param includeDetails whether to include message and errors attributes + * @param includeStackTrace whether to include the stacktrace attribute + * @param includeMessage whether to include the message attribute + * @param includeBindingErrors whether to include the errors attribute * @return the error attributes as a Map */ protected Map getErrorAttributes(ServerRequest request, boolean includeStackTrace, - boolean includeDetails) { - return this.errorAttributes.getErrorAttributes(request, includeStackTrace, includeDetails); + boolean includeMessage, boolean includeBindingErrors) { + return this.errorAttributes.getErrorAttributes(request, includeStackTrace, includeMessage, + includeBindingErrors); } /** @@ -173,13 +175,23 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept } /** - * Check whether the details attribute has been set on the given request. + * Check whether the message attribute has been set on the given request. * @param request the source request - * @return {@code true} if the error details have been requested, {@code false} + * @return {@code true} if the message attribute has been requested, {@code false} * otherwise */ - protected boolean isDetailsEnabled(ServerRequest request) { - return getBooleanParameter(request, "details"); + protected boolean isMessageEnabled(ServerRequest request) { + return getBooleanParameter(request, "message"); + } + + /** + * Check whether the errors attribute has been set on the given request. + * @param request the source request + * @return {@code true} if the errors attribute has been requested, {@code false} + * otherwise + */ + protected boolean isBindingErrorsEnabled(ServerRequest request) { + return getBooleanParameter(request, "errors"); } private boolean getBooleanParameter(ServerRequest request, String parameterName) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandler.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandler.java index ca49c11cdb..cacf6ce5bf 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandler.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandler.java @@ -114,8 +114,10 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa */ protected Mono renderErrorView(ServerRequest request) { boolean includeStackTrace = isIncludeStackTrace(request, MediaType.TEXT_HTML); - boolean includeDetails = isIncludeDetails(request, MediaType.TEXT_HTML); - Map error = getErrorAttributes(request, includeStackTrace, includeDetails); + boolean includeMessage = isIncludeMessage(request, MediaType.TEXT_HTML); + boolean includeBindingErrors = isIncludeBindingErrors(request, MediaType.TEXT_HTML); + Map error = getErrorAttributes(request, includeStackTrace, includeMessage, + includeBindingErrors); int errorStatus = getHttpStatus(error); ServerResponse.BodyBuilder responseBody = ServerResponse.status(errorStatus).contentType(TEXT_HTML_UTF8); return Flux.just(getData(errorStatus).toArray(new String[] {})) @@ -143,8 +145,10 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa */ protected Mono renderErrorResponse(ServerRequest request) { boolean includeStackTrace = isIncludeStackTrace(request, MediaType.ALL); - boolean includeDetails = isIncludeDetails(request, MediaType.ALL); - Map error = getErrorAttributes(request, includeStackTrace, includeDetails); + boolean includeMessage = isIncludeMessage(request, MediaType.ALL); + boolean includeBindingErrors = isIncludeBindingErrors(request, MediaType.ALL); + Map error = getErrorAttributes(request, includeStackTrace, includeMessage, + includeBindingErrors); return ServerResponse.status(getHttpStatus(error)).contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(error)); } @@ -155,10 +159,12 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa * @param produces the media type produced (or {@code MediaType.ALL}) * @return if the stacktrace attribute should be included */ + @SuppressWarnings("deprecation") protected boolean isIncludeStackTrace(ServerRequest request, MediaType produces) { switch (this.errorProperties.getIncludeStacktrace()) { case ALWAYS: return true; + case ON_PARAM: case ON_TRACE_PARAM: return isTraceEnabled(request); default: @@ -167,17 +173,34 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa } /** - * Determine if the message and errors attributes should be included. + * Determine if the message attribute should be included. * @param request the source request * @param produces the media type produced (or {@code MediaType.ALL}) - * @return if the message and errors attributes should be included + * @return if the message attribute should be included */ - protected boolean isIncludeDetails(ServerRequest request, MediaType produces) { - switch (this.errorProperties.getIncludeDetails()) { + protected boolean isIncludeMessage(ServerRequest request, MediaType produces) { + switch (this.errorProperties.getIncludeMessage()) { case ALWAYS: return true; - case ON_DETAILS_PARAM: - return isDetailsEnabled(request); + case ON_PARAM: + return isMessageEnabled(request); + default: + return false; + } + } + + /** + * Determine if the errors attribute should be included. + * @param request the source request + * @param produces the media type produced (or {@code MediaType.ALL}) + * @return if the errors attribute should be included + */ + protected boolean isIncludeBindingErrors(ServerRequest request, MediaType produces) { + switch (this.errorProperties.getIncludeBindingErrors()) { + case ALWAYS: + return true; + case ON_PARAM: + return isBindingErrorsEnabled(request); default: return false; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/AbstractErrorController.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/AbstractErrorController.java index 4c57f01bf7..33c6b09bc8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/AbstractErrorController.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/AbstractErrorController.java @@ -74,25 +74,30 @@ public abstract class AbstractErrorController implements ErrorController { * @param includeStackTrace if stack trace elements should be included * @return the error attributes * @deprecated since 2.3.0 in favor of - * {@link #getErrorAttributes(HttpServletRequest, boolean, boolean)} + * {@link #getErrorAttributes(HttpServletRequest, boolean, boolean, boolean)} */ @Deprecated protected Map getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) { - return getErrorAttributes(request, includeStackTrace, false); + return getErrorAttributes(request, includeStackTrace, false, false); } protected Map getErrorAttributes(HttpServletRequest request, boolean includeStackTrace, - boolean includeDetails) { + boolean includeMessage, boolean includeBindingErrors) { WebRequest webRequest = new ServletWebRequest(request); - return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace, includeDetails); + return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace, includeMessage, + includeBindingErrors); } protected boolean getTraceParameter(HttpServletRequest request) { return getBooleanParameter(request, "trace"); } - protected boolean getDetailsParameter(HttpServletRequest request) { - return getBooleanParameter(request, "details"); + protected boolean getMessageParameter(HttpServletRequest request) { + return getBooleanParameter(request, "message"); + } + + protected boolean getErrorsParameter(HttpServletRequest request) { + return getBooleanParameter(request, "errors"); } protected boolean getBooleanParameter(HttpServletRequest request, String parameterName) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorController.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorController.java index fc45e9e552..fceec6f519 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorController.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorController.java @@ -88,7 +88,8 @@ public class BasicErrorController extends AbstractErrorController { public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); Map model = Collections.unmodifiableMap(getErrorAttributes(request, - isIncludeStackTrace(request, MediaType.TEXT_HTML), isIncludeDetails(request, MediaType.TEXT_HTML))); + isIncludeStackTrace(request, MediaType.TEXT_HTML), isIncludeMessage(request, MediaType.TEXT_HTML), + isIncludeBindingErrors(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); @@ -101,7 +102,7 @@ public class BasicErrorController extends AbstractErrorController { return new ResponseEntity<>(status); } Map body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL), - isIncludeDetails(request, MediaType.ALL)); + isIncludeMessage(request, MediaType.ALL), isIncludeBindingErrors(request, MediaType.TEXT_HTML)); return new ResponseEntity<>(body, status); } @@ -117,10 +118,12 @@ public class BasicErrorController extends AbstractErrorController { * @param produces the media type produced (or {@code MediaType.ALL}) * @return if the stacktrace attribute should be included */ + @SuppressWarnings("deprecation") protected boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) { switch (getErrorProperties().getIncludeStacktrace()) { case ALWAYS: return true; + case ON_PARAM: case ON_TRACE_PARAM: return getTraceParameter(request); default: @@ -129,17 +132,34 @@ public class BasicErrorController extends AbstractErrorController { } /** - * Determine if the error details attributes should be included. + * Determine if the message attribute should be included. * @param request the source request * @param produces the media type produced (or {@code MediaType.ALL}) - * @return if the error details attributes should be included + * @return if the message attribute should be included */ - protected boolean isIncludeDetails(HttpServletRequest request, MediaType produces) { - switch (getErrorProperties().getIncludeDetails()) { + protected boolean isIncludeMessage(HttpServletRequest request, MediaType produces) { + switch (getErrorProperties().getIncludeMessage()) { case ALWAYS: return true; - case ON_DETAILS_PARAM: - return getDetailsParameter(request); + case ON_PARAM: + return getMessageParameter(request); + default: + return false; + } + } + + /** + * Determine if the errors attribute should be included. + * @param request the source request + * @param produces the media type produced (or {@code MediaType.ALL}) + * @return if the errors attribute should be included + */ + protected boolean isIncludeBindingErrors(HttpServletRequest request, MediaType produces) { + switch (getErrorProperties().getIncludeMessage()) { + case ALWAYS: + return true; + case ON_PARAM: + return getErrorsParameter(request); default: return false; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 9046fb6b13..599702a658 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -37,7 +37,11 @@ } }, { - "name": "server.error.include-details", + "name": "server.error.include-binding-errors", + "defaultValue": "never" + }, + { + "name": "server.error.include-message", "defaultValue": "never" }, { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerIntegrationTests.java index 52dd04fcc4..a0209bec51 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerIntegrationTests.java @@ -99,7 +99,7 @@ class DefaultErrorWebExceptionHandlerIntegrationTests { @Test void htmlError() { - this.contextRunner.withPropertyValues("server.error.include-details=always").run((context) -> { + this.contextRunner.withPropertyValues("server.error.include-message=always").run((context) -> { WebTestClient client = getWebClient(context); String body = client.get().uri("/").accept(MediaType.TEXT_HTML).exchange().expectStatus() .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectHeader().contentType(TEXT_HTML_UTF8) @@ -116,26 +116,43 @@ class DefaultErrorWebExceptionHandlerIntegrationTests { .isBadRequest().expectBody().jsonPath("status").isEqualTo("400").jsonPath("error") .isEqualTo(HttpStatus.BAD_REQUEST.getReasonPhrase()).jsonPath("path").isEqualTo(("/bind")) .jsonPath("exception").doesNotExist().jsonPath("errors").doesNotExist().jsonPath("message") - .isNotEmpty().jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId()); + .isEmpty().jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId()); }); } @Test - void bindingResultErrorIncludeDetails() { - this.contextRunner.withPropertyValues("server.error.include-details=on-details-param").run((context) -> { - WebTestClient client = getWebClient(context); - client.post().uri("/bind?details=true").contentType(MediaType.APPLICATION_JSON).bodyValue("{}").exchange() - .expectStatus().isBadRequest().expectBody().jsonPath("status").isEqualTo("400").jsonPath("error") - .isEqualTo(HttpStatus.BAD_REQUEST.getReasonPhrase()).jsonPath("path").isEqualTo(("/bind")) - .jsonPath("exception").doesNotExist().jsonPath("errors").isArray().jsonPath("message").isNotEmpty() - .jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId()); - }); + void bindingResultErrorIncludeMessageAndErrors() { + this.contextRunner.withPropertyValues("server.error.include-message=on-param", + "server.error.include-binding-errors=on-param").run((context) -> { + WebTestClient client = getWebClient(context); + client.post().uri("/bind?message=true&errors=true").contentType(MediaType.APPLICATION_JSON) + .bodyValue("{}").exchange().expectStatus().isBadRequest().expectBody().jsonPath("status") + .isEqualTo("400").jsonPath("error").isEqualTo(HttpStatus.BAD_REQUEST.getReasonPhrase()) + .jsonPath("path").isEqualTo(("/bind")).jsonPath("exception").doesNotExist() + .jsonPath("errors").isArray().jsonPath("message").isNotEmpty().jsonPath("requestId") + .isEqualTo(this.logIdFilter.getLogId()); + }); + } + + @Test + void includeStackTraceOnTraceParam() { + this.contextRunner.withPropertyValues("server.error.include-exception=true", + "server.error.include-stacktrace=on-trace-param").run((context) -> { + WebTestClient client = getWebClient(context); + client.get().uri("/?trace=true").exchange().expectStatus() + .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectBody().jsonPath("status") + .isEqualTo("500").jsonPath("error") + .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()).jsonPath("exception") + .isEqualTo(IllegalStateException.class.getName()).jsonPath("trace").exists() + .jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId()); + }); } @Test void includeStackTraceOnParam() { - this.contextRunner.withPropertyValues("server.error.include-exception=true", - "server.error.include-stacktrace=on-trace-param").run((context) -> { + this.contextRunner + .withPropertyValues("server.error.include-exception=true", "server.error.include-stacktrace=on-param") + .run((context) -> { WebTestClient client = getWebClient(context); client.get().uri("/?trace=true").exchange().expectStatus() .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectBody().jsonPath("status") @@ -177,11 +194,12 @@ class DefaultErrorWebExceptionHandlerIntegrationTests { } @Test - void includeDetailsOnParam() { - this.contextRunner.withPropertyValues("server.error.include-exception=true", - "server.error.include-details=on-details-param").run((context) -> { + void includeMessageOnParam() { + this.contextRunner + .withPropertyValues("server.error.include-exception=true", "server.error.include-message=on-param") + .run((context) -> { WebTestClient client = getWebClient(context); - client.get().uri("/?details=true").exchange().expectStatus() + client.get().uri("/?message=true").exchange().expectStatus() .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectBody().jsonPath("status") .isEqualTo("500").jsonPath("error") .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()).jsonPath("exception") @@ -191,9 +209,9 @@ class DefaultErrorWebExceptionHandlerIntegrationTests { } @Test - void alwaysIncludeDetails() { + void alwaysIncludeMessage() { this.contextRunner - .withPropertyValues("server.error.include-exception=true", "server.error.include-details=always") + .withPropertyValues("server.error.include-exception=true", "server.error.include-message=always") .run((context) -> { WebTestClient client = getWebClient(context); client.get().uri("/?trace=false").exchange().expectStatus() @@ -206,9 +224,9 @@ class DefaultErrorWebExceptionHandlerIntegrationTests { } @Test - void neverIncludeDetails() { + void neverIncludeMessage() { this.contextRunner - .withPropertyValues("server.error.include-exception=true", "server.error.include-details=never") + .withPropertyValues("server.error.include-exception=true", "server.error.include-message=never") .run((context) -> { WebTestClient client = getWebClient(context); client.get().uri("/?trace=true").exchange().expectStatus() @@ -235,7 +253,7 @@ class DefaultErrorWebExceptionHandlerIntegrationTests { void defaultErrorView() { this.contextRunner .withPropertyValues("spring.mustache.prefix=classpath:/unknown/", - "server.error.include-stacktrace=always", "server.error.include-details=always") + "server.error.include-stacktrace=always", "server.error.include-message=always") .run((context) -> { WebTestClient client = getWebClient(context); String body = client.get().uri("/").accept(MediaType.TEXT_HTML).exchange().expectStatus() @@ -250,7 +268,7 @@ class DefaultErrorWebExceptionHandlerIntegrationTests { @Test void escapeHtmlInDefaultErrorView() { this.contextRunner - .withPropertyValues("spring.mustache.prefix=classpath:/unknown/", "server.error.include-details=always") + .withPropertyValues("spring.mustache.prefix=classpath:/unknown/", "server.error.include-message=always") .run((context) -> { WebTestClient client = getWebClient(context); String body = client.get().uri("/html").accept(MediaType.TEXT_HTML).exchange().expectStatus() diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerTests.java index b410fe9552..feaefe8c8b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerTests.java @@ -66,7 +66,8 @@ class DefaultErrorWebExceptionHandlerTests { ResourceProperties resourceProperties = new ResourceProperties(); ErrorProperties errorProperties = new ErrorProperties(); ApplicationContext context = new AnnotationConfigReactiveWebApplicationContext(); - given(errorAttributes.getErrorAttributes(any(), anyBoolean(), anyBoolean())).willReturn(getErrorAttributes()); + given(errorAttributes.getErrorAttributes(any(), anyBoolean(), anyBoolean(), anyBoolean())) + .willReturn(getErrorAttributes()); DefaultErrorWebExceptionHandler exceptionHandler = new DefaultErrorWebExceptionHandler(errorAttributes, resourceProperties, errorProperties, context); setupViewResolver(exceptionHandler); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerIntegrationTests.java index e29cc0d69a..ac5f9ec3de 100755 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerIntegrationTests.java @@ -88,57 +88,63 @@ class BasicErrorControllerIntegrationTests { void testErrorForMachineClientDefault() { load(); ResponseEntity entity = new TestRestTemplate().getForEntity(createUrl("?trace=true"), Map.class); - assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", null, - "An error occurred while processing the request", "/"); + assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", null, "", "/"); assertThat(entity.getBody().containsKey("exception")).isFalse(); assertThat(entity.getBody().containsKey("trace")).isFalse(); } @Test - void testErrorForMachineClientWithParamsTrue() { + void testErrorForMachineClientWithTraceParamsTrue() { load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-trace-param", - "--server.error.include-details=on-details-param"); - exceptionWithStackTraceAndDetails("?trace=true&details=true"); + "--server.error.include-message=on-param"); + exceptionWithStackTraceAndMessage("?trace=true&message=true"); + } + + @Test + void testErrorForMachineClientWithParamsTrue() { + load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-param", + "--server.error.include-message=on-param"); + exceptionWithStackTraceAndMessage("?trace=true&message=true"); } @Test void testErrorForMachineClientWithParamsFalse() { - load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-trace-param", - "--server.error.include-details=on-details-param"); - exceptionWithoutStackTraceAndDetails("?trace=false&details=false"); + load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-param", + "--server.error.include-message=on-param"); + exceptionWithoutStackTraceAndMessage("?trace=false&message=false"); } @Test void testErrorForMachineClientWithParamsAbsent() { - load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-trace-param", - "--server.error.include-details=on-details-param"); - exceptionWithoutStackTraceAndDetails(""); + load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-param", + "--server.error.include-message=on-param"); + exceptionWithoutStackTraceAndMessage(""); } @Test void testErrorForMachineClientNeverParams() { load("--server.error.include-exception=true", "--server.error.include-stacktrace=never", - "--server.error.include-details=never"); - exceptionWithoutStackTraceAndDetails("?trace=true&details=true"); + "--server.error.include-message=never"); + exceptionWithoutStackTraceAndMessage("?trace=true&message=true"); } @Test void testErrorForMachineClientAlwaysParams() { load("--server.error.include-exception=true", "--server.error.include-stacktrace=always", - "--server.error.include-details=always"); - exceptionWithStackTraceAndDetails("?trace=false&details=false"); + "--server.error.include-message=always"); + exceptionWithStackTraceAndMessage("?trace=false&message=false"); } @Test void testErrorForMachineClientAlwaysParamsWithoutMessage() { - load("--server.error.include-exception=true", "--server.error.include-details=always"); + load("--server.error.include-exception=true", "--server.error.include-message=always"); ResponseEntity entity = new TestRestTemplate().getForEntity(createUrl("/noMessage"), Map.class); assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", IllegalStateException.class, "No message available", "/noMessage"); } @SuppressWarnings("rawtypes") - private void exceptionWithStackTraceAndDetails(String path) { + private void exceptionWithStackTraceAndMessage(String path) { ResponseEntity entity = new TestRestTemplate().getForEntity(createUrl(path), Map.class); assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", IllegalStateException.class, "Expected!", "/"); @@ -146,26 +152,25 @@ class BasicErrorControllerIntegrationTests { } @SuppressWarnings("rawtypes") - private void exceptionWithoutStackTraceAndDetails(String path) { + private void exceptionWithoutStackTraceAndMessage(String path) { ResponseEntity entity = new TestRestTemplate().getForEntity(createUrl(path), Map.class); - assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", IllegalStateException.class, - "An error occurred while processing the request", "/"); + assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", IllegalStateException.class, "", "/"); assertThat(entity.getBody().containsKey("trace")).isFalse(); } @Test @SuppressWarnings("rawtypes") - void testErrorForAnnotatedExceptionWithoutDetails() { + void testErrorForAnnotatedExceptionWithoutMessage() { load("--server.error.include-exception=true"); ResponseEntity entity = new TestRestTemplate().getForEntity(createUrl("/annotated"), Map.class); assertErrorAttributes(entity.getBody(), "400", "Bad Request", TestConfiguration.Errors.ExpectedException.class, - "An error occurred while processing the request", "/annotated"); + "", "/annotated"); } @Test @SuppressWarnings("rawtypes") - void testErrorForAnnotatedExceptionWithDetails() { - load("--server.error.include-exception=true", "--server.error.include-details=always"); + void testErrorForAnnotatedExceptionWithMessage() { + load("--server.error.include-exception=true", "--server.error.include-message=always"); ResponseEntity entity = new TestRestTemplate().getForEntity(createUrl("/annotated"), Map.class); assertErrorAttributes(entity.getBody(), "400", "Bad Request", TestConfiguration.Errors.ExpectedException.class, "Expected!", "/annotated"); @@ -173,18 +178,17 @@ class BasicErrorControllerIntegrationTests { @Test @SuppressWarnings("rawtypes") - void testErrorForAnnotatedNoReasonExceptionWithoutDetails() { + void testErrorForAnnotatedNoReasonExceptionWithoutMessage() { load("--server.error.include-exception=true"); ResponseEntity entity = new TestRestTemplate().getForEntity(createUrl("/annotatedNoReason"), Map.class); assertErrorAttributes(entity.getBody(), "406", "Not Acceptable", - TestConfiguration.Errors.NoReasonExpectedException.class, - "An error occurred while processing the request", "/annotatedNoReason"); + TestConfiguration.Errors.NoReasonExpectedException.class, "", "/annotatedNoReason"); } @Test @SuppressWarnings("rawtypes") - void testErrorForAnnotatedNoReasonExceptionWithDetails() { - load("--server.error.include-exception=true", "--server.error.include-details=always"); + void testErrorForAnnotatedNoReasonExceptionWithMessage() { + load("--server.error.include-exception=true", "--server.error.include-message=always"); ResponseEntity entity = new TestRestTemplate().getForEntity(createUrl("/annotatedNoReason"), Map.class); assertErrorAttributes(entity.getBody(), "406", "Not Acceptable", TestConfiguration.Errors.NoReasonExpectedException.class, "Expected message", "/annotatedNoReason"); @@ -192,8 +196,8 @@ class BasicErrorControllerIntegrationTests { @Test @SuppressWarnings("rawtypes") - void testErrorForAnnotatedNoMessageExceptionWithDetails() { - load("--server.error.include-exception=true", "--server.error.include-details=always"); + void testErrorForAnnotatedNoMessageExceptionWithMessage() { + load("--server.error.include-exception=true", "--server.error.include-message=always"); ResponseEntity entity = new TestRestTemplate().getForEntity(createUrl("/annotatedNoMessage"), Map.class); assertErrorAttributes(entity.getBody(), "406", "Not Acceptable", TestConfiguration.Errors.NoReasonExpectedException.class, "No message available", @@ -201,37 +205,42 @@ class BasicErrorControllerIntegrationTests { } @Test - void testBindingExceptionForMachineClientWithDetailsParamTrue() { - load("--server.error.include-exception=true", "--server.error.include-details=on-details-param"); - bindingExceptionWithDetails("?details=true"); + void testBindingExceptionForMachineClientWithMessageAndErrorsParamTrue() { + load("--server.error.include-exception=true", "--server.error.include-message=on-param", + "--server.error.include-binding-errors=on-param"); + bindingExceptionWithMessageAndErrors("?message=true&errors=true"); } @Test - void testBindingExceptionForMachineClientWithDetailsParamFalse() { - load("--server.error.include-exception=true", "--server.error.include-details=on-details-param"); - bindingExceptionWithoutDetails("?details=false"); + void testBindingExceptionForMachineClientWithMessageAndErrorsParamFalse() { + load("--server.error.include-exception=true", "--server.error.include-message=on-param", + "--server.error.include-binding-errors=on-param"); + bindingExceptionWithoutMessageAndErrors("?message=false&errors=false"); } @Test - void testBindingExceptionForMachineClientWithDetailsParamAbsent() { - load("--server.error.include-exception=true", "--server.error.include-details=on-details-param"); - bindingExceptionWithoutDetails(""); + void testBindingExceptionForMachineClientWithMessageAndErrorsParamAbsent() { + load("--server.error.include-exception=true", "--server.error.include-message=on-param", + "--server.error.include-binding-errors=on-param"); + bindingExceptionWithoutMessageAndErrors(""); } @Test - void testBindingExceptionForMachineClientAlwaysDetails() { - load("--server.error.include-exception=true", "--server.error.include-details=always"); - bindingExceptionWithDetails("?details=false"); + void testBindingExceptionForMachineClientAlwaysMessageAndErrors() { + load("--server.error.include-exception=true", "--server.error.include-message=always", + "--server.error.include-binding-errors=always"); + bindingExceptionWithMessageAndErrors("?message=false&errors=false"); } @Test - void testBindingExceptionForMachineClientNeverDetails() { - load("--server.error.include-exception=true", "--server.error.include-details=never"); - bindingExceptionWithoutDetails("?details=true"); + void testBindingExceptionForMachineClientNeverMessageAndErrors() { + load("--server.error.include-exception=true", "--server.error.include-message=never", + "--server.error.include-binding-errors=never"); + bindingExceptionWithoutMessageAndErrors("?message=true&errors=true"); } @SuppressWarnings({ "rawtypes" }) - private void bindingExceptionWithDetails(String param) { + private void bindingExceptionWithMessageAndErrors(String param) { ResponseEntity entity = new TestRestTemplate().getForEntity(createUrl("/bind" + param), Map.class); assertErrorAttributes(entity.getBody(), "400", "Bad Request", BindException.class, "Validation failed for object='test'. Error count: 1", "/bind"); @@ -239,10 +248,9 @@ class BasicErrorControllerIntegrationTests { } @SuppressWarnings({ "rawtypes" }) - private void bindingExceptionWithoutDetails(String param) { + private void bindingExceptionWithoutMessageAndErrors(String param) { ResponseEntity entity = new TestRestTemplate().getForEntity(createUrl("/bind" + param), Map.class); - assertErrorAttributes(entity.getBody(), "400", "Bad Request", BindException.class, "Validation failed", - "/bind"); + assertErrorAttributes(entity.getBody(), "400", "Bad Request", BindException.class, "", "/bind"); assertThat(entity.getBody().containsKey("errors")).isFalse(); } @@ -253,8 +261,8 @@ class BasicErrorControllerIntegrationTests { RequestEntity request = RequestEntity.post(URI.create(createUrl("/bodyValidation"))) .accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON).body("{}"); ResponseEntity entity = new TestRestTemplate().exchange(request, Map.class); - assertErrorAttributes(entity.getBody(), "400", "Bad Request", MethodArgumentNotValidException.class, - "Validation failed", "/bodyValidation"); + assertErrorAttributes(entity.getBody(), "400", "Bad Request", MethodArgumentNotValidException.class, "", + "/bodyValidation"); assertThat(entity.getBody().containsKey("errors")).isFalse(); } @@ -262,7 +270,7 @@ class BasicErrorControllerIntegrationTests { @SuppressWarnings("rawtypes") void testBindingExceptionForMachineClientDefault() { load(); - RequestEntity request = RequestEntity.get(URI.create(createUrl("/bind?trace=true,details=true"))) + RequestEntity request = RequestEntity.get(URI.create(createUrl("/bind?trace=true,message=true"))) .accept(MediaType.APPLICATION_JSON).build(); ResponseEntity entity = new TestRestTemplate().exchange(request, Map.class); assertThat(entity.getBody().containsKey("exception")).isFalse(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerMockMvcTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerMockMvcTests.java index 1b8e424ce3..b1886b944a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerMockMvcTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerMockMvcTests.java @@ -69,7 +69,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * @author Dave Syer * @author Scott Frederick */ -@SpringBootTest(properties = { "server.error.include-details=always" }) +@SpringBootTest(properties = { "server.error.include-message=always" }) @DirtiesContext class BasicErrorControllerMockMvcTests { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/DefaultErrorViewIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/DefaultErrorViewIntegrationTests.java index 1eeaa98f2b..236a9f1d65 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/DefaultErrorViewIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/DefaultErrorViewIntegrationTests.java @@ -52,7 +52,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * @author Dave Syer * @author Scott Frederick */ -@SpringBootTest(properties = { "server.error.include-details=always" }) +@SpringBootTest(properties = { "server.error.include-message=always" }) @DirtiesContext class DefaultErrorViewIntegrationTests { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfigurationTests.java index f7a4a6f602..631e7b97ab 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfigurationTests.java @@ -51,7 +51,7 @@ class ErrorMvcAutoConfigurationTests { ErrorAttributes errorAttributes = context.getBean(ErrorAttributes.class); DispatcherServletWebRequest webRequest = createWebRequest(new IllegalStateException("Exception message"), false); - errorView.render(errorAttributes.getErrorAttributes(webRequest, true, true), webRequest.getRequest(), + errorView.render(errorAttributes.getErrorAttributes(webRequest, true, true, true), webRequest.getRequest(), webRequest.getResponse()); assertThat(webRequest.getResponse().getContentType()).isEqualTo("text/html;charset=UTF-8"); String responseString = ((MockHttpServletResponse) webRequest.getResponse()).getContentAsString(); @@ -69,7 +69,7 @@ class ErrorMvcAutoConfigurationTests { ErrorAttributes errorAttributes = context.getBean(ErrorAttributes.class); DispatcherServletWebRequest webRequest = createWebRequest(new IllegalStateException("Exception message"), true); - errorView.render(errorAttributes.getErrorAttributes(webRequest, true, true), webRequest.getRequest(), + errorView.render(errorAttributes.getErrorAttributes(webRequest, true, true, true), webRequest.getRequest(), webRequest.getResponse()); assertThat(output).contains("Cannot render error page for request [/path] " + "and exception [Exception message] as the response has " diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsPropertyDefaultsPostProcessor.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsPropertyDefaultsPostProcessor.java index 097455684b..70bdfc1926 100755 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsPropertyDefaultsPostProcessor.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsPropertyDefaultsPostProcessor.java @@ -71,7 +71,8 @@ public class DevToolsPropertyDefaultsPostProcessor implements EnvironmentPostPro properties.put("spring.resources.chain.cache", "false"); properties.put("spring.template.provider.cache", "false"); properties.put("spring.mvc.log-resolved-exception", "true"); - properties.put("server.error.include-details", "ALWAYS"); + properties.put("server.error.include-binding-errors", "ALWAYS"); + properties.put("server.error.include-message", "ALWAYS"); properties.put("server.error.include-stacktrace", "ALWAYS"); properties.put("server.servlet.jsp.init-parameters.development", "true"); properties.put("spring.reactor.debug", "true"); diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/env/DevToolPropertiesIntegrationTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/env/DevToolPropertiesIntegrationTests.java index e9f47fe418..6c76cfa766 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/env/DevToolPropertiesIntegrationTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/env/DevToolPropertiesIntegrationTests.java @@ -108,8 +108,8 @@ class DevToolPropertiesIntegrationTests { ConfigurableEnvironment environment = this.context.getEnvironment(); String includeStackTrace = environment.getProperty("server.error.include-stacktrace"); assertThat(includeStackTrace).isEqualTo(ErrorProperties.IncludeStacktrace.ALWAYS.toString()); - String includeDetails = environment.getProperty("server.error.include-details"); - assertThat(includeDetails).isEqualTo(ErrorProperties.IncludeDetails.ALWAYS.toString()); + String includeMessage = environment.getProperty("server.error.include-message"); + assertThat(includeMessage).isEqualTo(ErrorProperties.IncludeAttribute.ALWAYS.toString()); } protected ConfigurableApplicationContext getContext(Supplier supplier) 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 81535d06f6..72168b8b97 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 @@ -30,7 +30,6 @@ import org.springframework.util.StringUtils; import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.support.WebExchangeBindException; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ServerWebExchange; @@ -82,12 +81,12 @@ public class DefaultErrorAttributes implements ErrorAttributes { @Override @Deprecated public Map getErrorAttributes(ServerRequest request, boolean includeStackTrace) { - return getErrorAttributes(request, includeStackTrace, false); + return getErrorAttributes(request, includeStackTrace, false, false); } @Override public Map getErrorAttributes(ServerRequest request, boolean includeStackTrace, - boolean includeDetails) { + boolean includeMessage, boolean includeBindingErrors) { Map errorAttributes = new LinkedHashMap<>(); errorAttributes.put("timestamp", new Date()); errorAttributes.put("path", request.path()); @@ -97,9 +96,9 @@ public class DefaultErrorAttributes implements ErrorAttributes { HttpStatus errorStatus = determineHttpStatus(error, responseStatusAnnotation); errorAttributes.put("status", errorStatus.value()); errorAttributes.put("error", errorStatus.getReasonPhrase()); - errorAttributes.put("message", determineMessage(error, responseStatusAnnotation, includeDetails)); + errorAttributes.put("message", determineMessage(error, responseStatusAnnotation, includeMessage)); errorAttributes.put("requestId", request.exchange().getRequest().getId()); - handleException(errorAttributes, determineException(error), includeStackTrace, includeDetails); + handleException(errorAttributes, determineException(error), includeStackTrace, includeBindingErrors); return errorAttributes; } @@ -111,13 +110,13 @@ public class DefaultErrorAttributes implements ErrorAttributes { } private String determineMessage(Throwable error, MergedAnnotation responseStatusAnnotation, - boolean includeDetails) { - if (error instanceof WebExchangeBindException) { - return includeDetails ? error.getMessage() : "Validation failed"; - } - if (!includeDetails) { + boolean includeMessage) { + if (!includeMessage) { return ""; } + if (error instanceof BindingResult) { + return error.getMessage(); + } if (error instanceof ResponseStatusException) { return ((ResponseStatusException) error).getReason(); } @@ -143,14 +142,14 @@ public class DefaultErrorAttributes implements ErrorAttributes { } private void handleException(Map errorAttributes, Throwable error, boolean includeStackTrace, - boolean includeDetails) { + boolean includeBindingErrors) { if (this.includeException) { errorAttributes.put("exception", error.getClass().getName()); } if (includeStackTrace) { addStackTrace(errorAttributes, error); } - if (includeDetails && (error instanceof BindingResult)) { + if (includeBindingErrors && (error instanceof BindingResult)) { BindingResult result = (BindingResult) error; if (result.hasErrors()) { errorAttributes.put("errors", result.getAllErrors()); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/error/ErrorAttributes.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/error/ErrorAttributes.java index 1058c03f5c..b84346caff 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/error/ErrorAttributes.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/error/ErrorAttributes.java @@ -36,10 +36,10 @@ public interface ErrorAttributes { * Return a {@link Map} of the error attributes. The map can be used as the model of * an error page, or returned as a {@link ServerResponse} body. * @param request the source request - * @param includeStackTrace if stack trace elements should be included + * @param includeStackTrace if stack trace attribute should be included * @return a map of error attributes * @deprecated since 2.3.0 in favor of - * {@link #getErrorAttributes(ServerRequest, boolean, boolean)} + * {@link #getErrorAttributes(ServerRequest, boolean, boolean, boolean)} */ Map getErrorAttributes(ServerRequest request, boolean includeStackTrace); @@ -47,11 +47,13 @@ public interface ErrorAttributes { * Return a {@link Map} of the error attributes. The map can be used as the model of * an error page, or returned as a {@link ServerResponse} body. * @param request the source request - * @param includeStackTrace if stack trace elements should be included - * @param includeDetails if message and errors elements should be included + * @param includeStackTrace if stack trace attribute should be included + * @param includeMessage if message attribute should be included + * @param includeBindingErrors if errors attribute should be included * @return a map of error attributes */ - Map getErrorAttributes(ServerRequest request, boolean includeStackTrace, boolean includeDetails); + Map getErrorAttributes(ServerRequest request, boolean includeStackTrace, boolean includeMessage, + boolean includeBindingErrors); /** * Return the underlying cause of the error or {@code null} if the error cannot be 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 51cd65f8cb..f48edeec3c 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 @@ -104,16 +104,16 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException @Override @Deprecated public Map getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { - return getErrorAttributes(webRequest, includeStackTrace, false); + return getErrorAttributes(webRequest, includeStackTrace, false, false); } @Override public Map getErrorAttributes(WebRequest webRequest, boolean includeStackTrace, - boolean includeDetails) { + boolean includeMessage, boolean includeBindingErrors) { Map errorAttributes = new LinkedHashMap<>(); errorAttributes.put("timestamp", new Date()); addStatus(errorAttributes, webRequest); - addErrorDetails(errorAttributes, webRequest, includeStackTrace, includeDetails); + addErrorDetails(errorAttributes, webRequest, includeStackTrace, includeMessage, includeBindingErrors); addPath(errorAttributes, webRequest); return errorAttributes; } @@ -136,7 +136,7 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException } private void addErrorDetails(Map errorAttributes, WebRequest webRequest, boolean includeStackTrace, - boolean includeDetails) { + boolean includeMessage, boolean includeBindingErrors) { Throwable error = getError(webRequest); if (error != null) { while (error instanceof ServletException && error.getCause() != null) { @@ -149,24 +149,24 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException addStackTrace(errorAttributes, error); } } - addErrorMessage(errorAttributes, webRequest, error, includeDetails); + addErrorMessage(errorAttributes, webRequest, error, includeMessage, includeBindingErrors); } private void addErrorMessage(Map errorAttributes, WebRequest webRequest, Throwable error, - boolean includeDetails) { + boolean includeMessage, boolean includeBindingErrors) { BindingResult result = extractBindingResult(error); if (result == null) { - addExceptionErrorMessage(errorAttributes, webRequest, error, includeDetails); + addExceptionErrorMessage(errorAttributes, webRequest, error, includeMessage); } else { - addBindingResultErrorMessage(errorAttributes, result, includeDetails); + addBindingResultErrorMessage(errorAttributes, result, includeMessage, includeBindingErrors); } } private void addExceptionErrorMessage(Map errorAttributes, WebRequest webRequest, Throwable error, - boolean includeDetails) { - if (!includeDetails) { - errorAttributes.put("message", "An error occurred while processing the request"); + boolean includeMessage) { + if (!includeMessage) { + errorAttributes.put("message", ""); return; } Object message = getAttribute(webRequest, RequestDispatcher.ERROR_MESSAGE); @@ -180,16 +180,12 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException } private void addBindingResultErrorMessage(Map errorAttributes, BindingResult result, - boolean includeDetails) { - if (!includeDetails) { - errorAttributes.put("message", "Validation failed"); - return; - } - if (result.hasErrors()) { + boolean includeMessage, boolean includeBindingErrors) { + errorAttributes.put("message", (includeMessage) ? "Validation failed for object='" + result.getObjectName() + + "'. " + "Error count: " + result.getErrorCount() : ""); + if (includeBindingErrors && result.hasErrors()) { errorAttributes.put("errors", result.getAllErrors()); } - errorAttributes.put("message", "Validation failed for object='" + result.getObjectName() + "'. " - + "Error count: " + result.getErrorCount()); } private BindingResult extractBindingResult(Throwable error) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/error/ErrorAttributes.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/error/ErrorAttributes.java index 916639f642..d59b53f234 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/error/ErrorAttributes.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/error/ErrorAttributes.java @@ -37,10 +37,10 @@ public interface ErrorAttributes { * an error page {@link ModelAndView}, or returned as a * {@link ResponseBody @ResponseBody}. * @param webRequest the source request - * @param includeStackTrace if stack trace elements should be included + * @param includeStackTrace if stack trace element should be included * @return a map of error attributes * @deprecated since 2.3.0 in favor of - * {@link #getErrorAttributes(WebRequest, boolean, boolean)} + * {@link #getErrorAttributes(WebRequest, boolean, boolean, boolean)} */ @Deprecated Map getErrorAttributes(WebRequest webRequest, boolean includeStackTrace); @@ -50,12 +50,14 @@ public interface ErrorAttributes { * an error page {@link ModelAndView}, or returned as a * {@link ResponseBody @ResponseBody}. * @param webRequest the source request - * @param includeStackTrace if stack trace elements should be included - * @param includeDetails if message and errors elements should be included + * @param includeStackTrace if stack trace element should be included + * @param includeMessage if message element should be included + * @param includeBindingErrors if errors element should be included * @return a map of error attributes * @since 2.3.0 */ - Map getErrorAttributes(WebRequest webRequest, boolean includeStackTrace, boolean includeDetails); + Map getErrorAttributes(WebRequest webRequest, boolean includeStackTrace, boolean includeMessage, + boolean includeBindingErrors); /** * Return the underlying cause of the error or {@code null} if the error cannot be 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 dfc4824595..cf3d7e4cf6 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 @@ -62,7 +62,7 @@ class DefaultErrorAttributesTests { MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test").build()); ServerRequest request = ServerRequest.create(exchange, this.readers); assertThatIllegalStateException() - .isThrownBy(() -> this.errorAttributes.getErrorAttributes(request, false, false)) + .isThrownBy(() -> this.errorAttributes.getErrorAttributes(request, false, false, false)) .withMessageContaining("Missing exception attribute in ServerWebExchange"); } @@ -70,7 +70,7 @@ class DefaultErrorAttributesTests { void includeTimeStamp() { MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); Map attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, NOT_FOUND), - false, false); + false, false, false); assertThat(attributes.get("timestamp")).isInstanceOf(Date.class); } @@ -79,7 +79,7 @@ class DefaultErrorAttributesTests { Error error = new OutOfMemoryError("Test error"); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); Map attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, error), - false, false); + false, false, false); assertThat(attributes.get("error")).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()); assertThat(attributes.get("status")).isEqualTo(500); } @@ -89,7 +89,7 @@ class DefaultErrorAttributesTests { Exception error = new CustomException(); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); Map attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, error), - false, false); + false, false, false); assertThat(attributes.get("error")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.getReasonPhrase()); assertThat(attributes.get("message")).isEqualTo(""); assertThat(attributes.get("status")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.value()); @@ -100,7 +100,7 @@ class DefaultErrorAttributesTests { Exception error = new CustomException("Test Message"); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); Map attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, error), - false, true); + false, true, false); assertThat(attributes.get("error")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.getReasonPhrase()); assertThat(attributes.get("message")).isEqualTo("Test Message"); assertThat(attributes.get("status")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.value()); @@ -111,7 +111,7 @@ class DefaultErrorAttributesTests { Exception error = new Custom2Exception(); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); Map attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, error), - false, true); + false, true, false); assertThat(attributes.get("error")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.getReasonPhrase()); assertThat(attributes.get("status")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.value()); assertThat(attributes.get("message")).isEqualTo("Nope!"); @@ -121,7 +121,7 @@ class DefaultErrorAttributesTests { void includeStatusCode() { MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); Map attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, NOT_FOUND), - false, false); + false, false, false); assertThat(attributes.get("error")).isEqualTo(HttpStatus.NOT_FOUND.getReasonPhrase()); assertThat(attributes.get("status")).isEqualTo(404); } @@ -131,18 +131,18 @@ class DefaultErrorAttributesTests { Error error = new OutOfMemoryError("Test error"); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); ServerRequest serverRequest = buildServerRequest(request, error); - Map attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true); + Map attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true, false); assertThat(this.errorAttributes.getError(serverRequest)).isSameAs(error); assertThat(attributes.get("exception")).isNull(); assertThat(attributes.get("message")).isEqualTo("Test error"); } @Test - void excludeDetails() { + void excludeMessage() { Error error = new OutOfMemoryError("Test error"); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); ServerRequest serverRequest = buildServerRequest(request, error); - Map attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, false); + Map attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, false, false); assertThat(this.errorAttributes.getError(serverRequest)).isSameAs(error); assertThat(attributes.get("message")).isEqualTo(""); } @@ -153,7 +153,7 @@ class DefaultErrorAttributesTests { this.errorAttributes = new DefaultErrorAttributes(true); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); ServerRequest serverRequest = buildServerRequest(request, error); - Map attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true); + Map attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true, false); assertThat(this.errorAttributes.getError(serverRequest)).isSameAs(error); assertThat(attributes.get("exception")).isEqualTo(RuntimeException.class.getName()); assertThat(attributes.get("message")).isEqualTo("Test"); @@ -166,7 +166,7 @@ class DefaultErrorAttributesTests { this.errorAttributes = new DefaultErrorAttributes(true); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); ServerRequest serverRequest = buildServerRequest(request, error); - Map attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true); + Map attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true, false); assertThat(attributes.get("status")).isEqualTo(400); assertThat(attributes.get("message")).isEqualTo("invalid request"); assertThat(attributes.get("exception")).isEqualTo(RuntimeException.class.getName()); @@ -180,7 +180,7 @@ class DefaultErrorAttributesTests { this.errorAttributes = new DefaultErrorAttributes(true); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); ServerRequest serverRequest = buildServerRequest(request, error); - Map attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true); + Map attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true, false); assertThat(attributes.get("status")).isEqualTo(406); assertThat(attributes.get("message")).isEqualTo("could not process request"); assertThat(attributes.get("exception")).isEqualTo(ResponseStatusException.class.getName()); @@ -192,7 +192,7 @@ class DefaultErrorAttributesTests { RuntimeException ex = new RuntimeException("Test"); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); Map attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), false, - false); + false, false); assertThat(attributes.get("trace")).isNull(); } @@ -201,7 +201,7 @@ class DefaultErrorAttributesTests { RuntimeException ex = new RuntimeException("Test"); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); Map attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), true, - false); + false, false); assertThat(attributes.get("trace").toString()).startsWith("java.lang"); } @@ -209,7 +209,7 @@ class DefaultErrorAttributesTests { void includePath() { MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); Map attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, NOT_FOUND), - false, false); + false, false, false); assertThat(attributes.get("path")).isEqualTo("/test"); } @@ -217,7 +217,7 @@ class DefaultErrorAttributesTests { void includeLogPrefix() { MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); ServerRequest serverRequest = buildServerRequest(request, NOT_FOUND); - Map attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, false); + Map attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, false, false); assertThat(attributes.get("requestId")).isEqualTo(serverRequest.exchange().getRequest().getId()); } @@ -230,7 +230,7 @@ class DefaultErrorAttributesTests { Exception ex = new WebExchangeBindException(stringParam, bindingResult); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); Map attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), false, - true); + true, true); assertThat(attributes.get("message")).asString() .startsWith("Validation failed for argument at index 0 in method: " + "int org.springframework.boot.web.reactive.error.DefaultErrorAttributesTests" @@ -239,7 +239,7 @@ class DefaultErrorAttributesTests { } @Test - void extractBindingResultErrorsExcludeDetails() throws Exception { + void extractBindingResultErrorsExcludeMessageAndErrors() throws Exception { Method method = getClass().getDeclaredMethod("method", String.class); MethodParameter stringParam = new MethodParameter(method, 0); BindingResult bindingResult = new MapBindingResult(Collections.singletonMap("a", "b"), "objectName"); @@ -247,8 +247,8 @@ class DefaultErrorAttributesTests { Exception ex = new WebExchangeBindException(stringParam, bindingResult); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); Map attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), false, - false); - assertThat(attributes.get("message")).isEqualTo("Validation failed"); + false, false); + assertThat(attributes.get("message")).isEqualTo(""); assertThat(attributes.containsKey("errors")).isFalse(); } 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 f2e4b32021..5d91643eda 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 @@ -54,21 +54,21 @@ class DefaultErrorAttributesTests { @Test void includeTimeStamp() { - Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false); + Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false, false); assertThat(attributes.get("timestamp")).isInstanceOf(Date.class); } @Test void specificStatusCode() { this.request.setAttribute("javax.servlet.error.status_code", 404); - Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false); + Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false, false); assertThat(attributes.get("error")).isEqualTo(HttpStatus.NOT_FOUND.getReasonPhrase()); assertThat(attributes.get("status")).isEqualTo(404); } @Test void missingStatusCode() { - Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false); + Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false, false); assertThat(attributes.get("error")).isEqualTo("None"); assertThat(attributes.get("status")).isEqualTo(999); } @@ -78,7 +78,7 @@ class DefaultErrorAttributesTests { RuntimeException ex = new RuntimeException("Test"); ModelAndView modelAndView = this.errorAttributes.resolveException(this.request, null, null, ex); this.request.setAttribute("javax.servlet.error.exception", new RuntimeException("Ignored")); - Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true); + Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true, false); assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(ex); assertThat(modelAndView).isNull(); assertThat(attributes.containsKey("exception")).isFalse(); @@ -86,46 +86,46 @@ class DefaultErrorAttributesTests { } @Test - void servletErrorWithDetail() { + void servletErrorWithMessage() { RuntimeException ex = new RuntimeException("Test"); this.request.setAttribute("javax.servlet.error.exception", ex); - Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true); + Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true, false); assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(ex); assertThat(attributes.containsKey("exception")).isFalse(); assertThat(attributes.get("message")).isEqualTo("Test"); } @Test - void servletErrorWithoutDetail() { + void servletErrorWithoutMessage() { RuntimeException ex = new RuntimeException("Test"); this.request.setAttribute("javax.servlet.error.exception", ex); - Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false); + Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false, false); assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(ex); assertThat(attributes.containsKey("exception")).isFalse(); - assertThat(attributes.get("message").toString()).contains("An error occurred"); + assertThat(attributes.get("message").toString()).contains(""); } @Test - void servletMessageWithDetail() { + void servletMessageWithMessage() { this.request.setAttribute("javax.servlet.error.message", "Test"); - Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true); + Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true, false); assertThat(attributes.containsKey("exception")).isFalse(); assertThat(attributes.get("message")).isEqualTo("Test"); } @Test - void servletMessageWithoutDetail() { + void servletMessageWithoutMessage() { this.request.setAttribute("javax.servlet.error.message", "Test"); - Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false); + Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false, false); assertThat(attributes.containsKey("exception")).isFalse(); - assertThat(attributes.get("message")).asString().contains("An error occurred"); + assertThat(attributes.get("message")).asString().contains(""); } @Test void nullExceptionMessage() { this.request.setAttribute("javax.servlet.error.exception", new RuntimeException()); this.request.setAttribute("javax.servlet.error.message", "Test"); - Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true); + Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true, false); assertThat(attributes.containsKey("exception")).isFalse(); assertThat(attributes.get("message")).isEqualTo("Test"); } @@ -133,7 +133,7 @@ class DefaultErrorAttributesTests { @Test void nullExceptionMessageAndServletMessage() { this.request.setAttribute("javax.servlet.error.exception", new RuntimeException()); - Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true); + Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true, false); assertThat(attributes.containsKey("exception")).isFalse(); assertThat(attributes.get("message")).isEqualTo("No message available"); } @@ -143,7 +143,7 @@ class DefaultErrorAttributesTests { RuntimeException ex = new RuntimeException("Test"); ServletException wrapped = new ServletException(new ServletException(ex)); this.request.setAttribute("javax.servlet.error.exception", wrapped); - Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true); + Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true, false); assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(wrapped); assertThat(attributes.containsKey("exception")).isFalse(); assertThat(attributes.get("message")).isEqualTo("Test"); @@ -153,7 +153,7 @@ class DefaultErrorAttributesTests { void getError() { Error error = new OutOfMemoryError("Test error"); this.request.setAttribute("javax.servlet.error.exception", error); - Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true); + Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true, false); assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(error); assertThat(attributes.containsKey("exception")).isFalse(); assertThat(attributes.get("message")).isEqualTo("Test error"); @@ -183,17 +183,17 @@ class DefaultErrorAttributesTests { testBindingResult(bindingResult, ex, true); } - private void testBindingResult(BindingResult bindingResult, Exception ex, boolean includeDetails) { + private void testBindingResult(BindingResult bindingResult, Exception ex, boolean includeMessageAndErrors) { this.request.setAttribute("javax.servlet.error.exception", ex); Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, - includeDetails); - if (includeDetails) { + includeMessageAndErrors, includeMessageAndErrors); + if (includeMessageAndErrors) { assertThat(attributes.get("message")) .isEqualTo("Validation failed for object='objectName'. Error count: 1"); assertThat(attributes.get("errors")).isEqualTo(bindingResult.getAllErrors()); } else { - assertThat(attributes.get("message")).isEqualTo("Validation failed"); + assertThat(attributes.get("message")).isEqualTo(""); assertThat(attributes.containsKey("errors")).isFalse(); } } @@ -203,7 +203,7 @@ class DefaultErrorAttributesTests { DefaultErrorAttributes errorAttributes = new DefaultErrorAttributes(true); RuntimeException ex = new RuntimeException("Test"); this.request.setAttribute("javax.servlet.error.exception", ex); - Map attributes = errorAttributes.getErrorAttributes(this.webRequest, false, true); + Map attributes = errorAttributes.getErrorAttributes(this.webRequest, false, true, false); assertThat(attributes.get("exception")).isEqualTo(RuntimeException.class.getName()); assertThat(attributes.get("message")).isEqualTo("Test"); } @@ -212,7 +212,7 @@ class DefaultErrorAttributesTests { void withStackTraceAttribute() { RuntimeException ex = new RuntimeException("Test"); this.request.setAttribute("javax.servlet.error.exception", ex); - Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, true, false); + Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, true, false, false); assertThat(attributes.get("trace").toString()).startsWith("java.lang"); } @@ -220,14 +220,14 @@ class DefaultErrorAttributesTests { void withoutStackTraceAttribute() { RuntimeException ex = new RuntimeException("Test"); this.request.setAttribute("javax.servlet.error.exception", ex); - Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false); + Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false, false); assertThat(attributes.containsKey("trace")).isFalse(); } @Test void path() { this.request.setAttribute("javax.servlet.error.request_uri", "path"); - Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false); + Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false, false); assertThat(attributes.get("path")).isEqualTo("path"); } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-custom-security/src/test/java/smoketest/actuator/customsecurity/SampleActuatorCustomSecurityApplicationTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-custom-security/src/test/java/smoketest/actuator/customsecurity/SampleActuatorCustomSecurityApplicationTests.java index 1e3ee25ac8..af2a76e5b1 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-custom-security/src/test/java/smoketest/actuator/customsecurity/SampleActuatorCustomSecurityApplicationTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-custom-security/src/test/java/smoketest/actuator/customsecurity/SampleActuatorCustomSecurityApplicationTests.java @@ -37,7 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Scott Frederick */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - properties = { "server.error.include-details=always" }) + properties = { "server.error.include-message=always" }) class SampleActuatorCustomSecurityApplicationTests extends AbstractSampleActuatorCustomSecurityTests { @LocalServerPort diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-ui/src/test/java/smoketest/actuator/ui/SampleActuatorUiApplicationTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-ui/src/test/java/smoketest/actuator/ui/SampleActuatorUiApplicationTests.java index 15b7a7a8b4..6ca9b19981 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-ui/src/test/java/smoketest/actuator/ui/SampleActuatorUiApplicationTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-ui/src/test/java/smoketest/actuator/ui/SampleActuatorUiApplicationTests.java @@ -39,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Dave Syer */ -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { "server.error.include-details=always" }) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { "server.error.include-message=always" }) class SampleActuatorUiApplicationTests { @Autowired