Commit a30740f8 authored by Scott Frederick's avatar Scott Frederick

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
parent 18a9a229
...@@ -53,26 +53,38 @@ public class ManagementErrorEndpoint { ...@@ -53,26 +53,38 @@ public class ManagementErrorEndpoint {
@RequestMapping("${server.error.path:${error.path:/error}}") @RequestMapping("${server.error.path:${error.path:/error}}")
@ResponseBody @ResponseBody
public Map<String, Object> invoke(ServletWebRequest request) { public Map<String, Object> 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) { private boolean includeStackTrace(ServletWebRequest request) {
switch (this.errorProperties.getIncludeStacktrace()) { switch (this.errorProperties.getIncludeStacktrace()) {
case ALWAYS: case ALWAYS:
return true; return true;
case ON_TRACE_PARAM: case ON_PARAM:
return getBooleanParameter(request, "trace"); return getBooleanParameter(request, "trace");
default: default:
return false; return false;
} }
} }
private boolean includeDetails(ServletWebRequest request) { private boolean includeMessage(ServletWebRequest request) {
switch (this.errorProperties.getIncludeDetails()) { switch (this.errorProperties.getIncludeMessage()) {
case ALWAYS: case ALWAYS:
return true; return true;
case ON_DETAILS_PARAM: case ON_PARAM:
return getBooleanParameter(request, "details"); 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: default:
return false; return false;
} }
......
...@@ -51,16 +51,16 @@ class ManagementErrorEndpointTests { ...@@ -51,16 +51,16 @@ class ManagementErrorEndpointTests {
void errorResponseNeverDetails() { void errorResponseNeverDetails() {
ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(this.errorAttributes, this.errorProperties); ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(this.errorAttributes, this.errorProperties);
Map<String, Object> response = endpoint.invoke(new ServletWebRequest(new MockHttpServletRequest())); Map<String, Object> 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"); assertThat(response).doesNotContainKey("trace");
} }
@Test @Test
void errorResponseAlwaysDetails() { void errorResponseAlwaysDetails() {
this.errorProperties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.ALWAYS); 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("trace", "false");
this.request.addParameter("details", "false"); this.request.addParameter("message", "false");
ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(this.errorAttributes, this.errorProperties); ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(this.errorAttributes, this.errorProperties);
Map<String, Object> response = endpoint.invoke(new ServletWebRequest(this.request)); Map<String, Object> response = endpoint.invoke(new ServletWebRequest(this.request));
assertThat(response).containsEntry("message", "test exception"); assertThat(response).containsEntry("message", "test exception");
...@@ -70,20 +70,20 @@ class ManagementErrorEndpointTests { ...@@ -70,20 +70,20 @@ class ManagementErrorEndpointTests {
@Test @Test
void errorResponseParamsAbsent() { void errorResponseParamsAbsent() {
this.errorProperties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM); this.errorProperties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.ON_PARAM);
this.errorProperties.setIncludeDetails(ErrorProperties.IncludeDetails.ON_DETAILS_PARAM); this.errorProperties.setIncludeMessage(ErrorProperties.IncludeAttribute.ON_PARAM);
ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(this.errorAttributes, this.errorProperties); ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(this.errorAttributes, this.errorProperties);
Map<String, Object> response = endpoint.invoke(new ServletWebRequest(this.request)); Map<String, Object> 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"); assertThat(response).doesNotContainKey("trace");
} }
@Test @Test
void errorResponseParamsTrue() { void errorResponseParamsTrue() {
this.errorProperties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM); this.errorProperties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.ON_PARAM);
this.errorProperties.setIncludeDetails(ErrorProperties.IncludeDetails.ON_DETAILS_PARAM); this.errorProperties.setIncludeMessage(ErrorProperties.IncludeAttribute.ON_PARAM);
this.request.addParameter("trace", "true"); this.request.addParameter("trace", "true");
this.request.addParameter("details", "true"); this.request.addParameter("message", "true");
ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(this.errorAttributes, this.errorProperties); ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(this.errorAttributes, this.errorProperties);
Map<String, Object> response = endpoint.invoke(new ServletWebRequest(this.request)); Map<String, Object> response = endpoint.invoke(new ServletWebRequest(this.request));
assertThat(response).containsEntry("message", "test exception"); assertThat(response).containsEntry("message", "test exception");
...@@ -93,13 +93,13 @@ class ManagementErrorEndpointTests { ...@@ -93,13 +93,13 @@ class ManagementErrorEndpointTests {
@Test @Test
void errorResponseParamsFalse() { void errorResponseParamsFalse() {
this.errorProperties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM); this.errorProperties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.ON_PARAM);
this.errorProperties.setIncludeDetails(ErrorProperties.IncludeDetails.ON_DETAILS_PARAM); this.errorProperties.setIncludeMessage(ErrorProperties.IncludeAttribute.ON_PARAM);
this.request.addParameter("trace", "false"); this.request.addParameter("trace", "false");
this.request.addParameter("details", "false"); this.request.addParameter("message", "false");
ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(this.errorAttributes, this.errorProperties); ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(this.errorAttributes, this.errorProperties);
Map<String, Object> response = endpoint.invoke(new ServletWebRequest(this.request)); Map<String, Object> 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"); assertThat(response).doesNotContainKey("trace");
} }
......
...@@ -67,14 +67,14 @@ class WebMvcEndpointChildContextConfigurationIntegrationTests { ...@@ -67,14 +67,14 @@ class WebMvcEndpointChildContextConfigurationIntegrationTests {
ClientResponse response = client.get().uri("actuator/fail").accept(MediaType.APPLICATION_JSON).exchange() ClientResponse response = client.get().uri("actuator/fail").accept(MediaType.APPLICATION_JSON).exchange()
.block(); .block();
Map<Object, Object> body = response.bodyToMono(Map.class).block(); Map<Object, Object> 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"); assertThat(body).doesNotContainKey("trace");
}); });
} }
@Test @Test
void errorPageAndErrorControllerIncludeDetails() { 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) -> { .run((context) -> {
String port = context.getEnvironment().getProperty("local.management.port"); String port = context.getEnvironment().getProperty("local.management.port");
WebClient client = WebClient.create("http://localhost:" + port); WebClient client = WebClient.create("http://localhost:" + port);
......
...@@ -412,7 +412,7 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable ...@@ -412,7 +412,7 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
contextCustomizer.accept(applicationContext); contextCustomizer.accept(applicationContext);
Map<String, Object> properties = new HashMap<>(); Map<String, Object> properties = new HashMap<>();
properties.put("endpointPath", endpointPath); 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.getEnvironment().getPropertySources().addLast(new MapPropertySource("test", properties));
applicationContext.refresh(); applicationContext.refresh();
try { try {
......
...@@ -46,9 +46,14 @@ public class ErrorProperties { ...@@ -46,9 +46,14 @@ public class ErrorProperties {
private IncludeStacktrace includeStacktrace = IncludeStacktrace.NEVER; 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(); private final Whitelabel whitelabel = new Whitelabel();
...@@ -76,12 +81,20 @@ public class ErrorProperties { ...@@ -76,12 +81,20 @@ public class ErrorProperties {
this.includeStacktrace = includeStacktrace; this.includeStacktrace = includeStacktrace;
} }
public IncludeDetails getIncludeDetails() { public IncludeAttribute getIncludeMessage() {
return this.includeDetails; return this.includeMessage;
}
public void setIncludeMessage(IncludeAttribute includeMessage) {
this.includeMessage = includeMessage;
} }
public void setIncludeDetails(IncludeDetails includeDetails) { public IncludeAttribute getIncludeBindingErrors() {
this.includeDetails = includeDetails; return this.includeMessage;
}
public void setIncludeBindingErrors(IncludeAttribute includeMessage) {
this.includeMessage = includeMessage;
} }
public Whitelabel getWhitelabel() { public Whitelabel getWhitelabel() {
...@@ -96,39 +109,56 @@ public class ErrorProperties { ...@@ -96,39 +109,56 @@ public class ErrorProperties {
/** /**
* Never add stacktrace information. * Never add stacktrace information.
*/ */
NEVER, NEVER(IncludeAttribute.NEVER),
/** /**
* Always add stacktrace information. * 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". * 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, NEVER,
/** /**
* Always add error detail information. * Always add error attribute.
*/ */
ALWAYS, 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
} }
......
...@@ -134,24 +134,26 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept ...@@ -134,24 +134,26 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept
* @param includeStackTrace whether to include the error stacktrace information * @param includeStackTrace whether to include the error stacktrace information
* @return the error attributes as a Map * @return the error attributes as a Map
* @deprecated since 2.3.0 in favor of * @deprecated since 2.3.0 in favor of
* {@link #getErrorAttributes(ServerRequest, boolean, boolean)} * {@link #getErrorAttributes(ServerRequest, boolean, boolean, boolean)}
*/ */
@Deprecated @Deprecated
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) { protected Map<String, Object> 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 * Extract the error attributes from the current request, to be used to populate error
* views or JSON payloads. * views or JSON payloads.
* @param request the source request * @param request the source request
* @param includeStackTrace whether to include the error stacktrace information * @param includeStackTrace whether to include the stacktrace attribute
* @param includeDetails whether to include message and errors attributes * @param includeMessage whether to include the message attribute
* @param includeBindingErrors whether to include the errors attribute
* @return the error attributes as a Map * @return the error attributes as a Map
*/ */
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace, protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace,
boolean includeDetails) { boolean includeMessage, boolean includeBindingErrors) {
return this.errorAttributes.getErrorAttributes(request, includeStackTrace, includeDetails); return this.errorAttributes.getErrorAttributes(request, includeStackTrace, includeMessage,
includeBindingErrors);
} }
/** /**
...@@ -173,13 +175,23 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept ...@@ -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 message attribute has been requested, {@code false}
* otherwise
*/
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 * @param request the source request
* @return {@code true} if the error details have been requested, {@code false} * @return {@code true} if the errors attribute has been requested, {@code false}
* otherwise * otherwise
*/ */
protected boolean isDetailsEnabled(ServerRequest request) { protected boolean isBindingErrorsEnabled(ServerRequest request) {
return getBooleanParameter(request, "details"); return getBooleanParameter(request, "errors");
} }
private boolean getBooleanParameter(ServerRequest request, String parameterName) { private boolean getBooleanParameter(ServerRequest request, String parameterName) {
......
...@@ -114,8 +114,10 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa ...@@ -114,8 +114,10 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa
*/ */
protected Mono<ServerResponse> renderErrorView(ServerRequest request) { protected Mono<ServerResponse> renderErrorView(ServerRequest request) {
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.TEXT_HTML); boolean includeStackTrace = isIncludeStackTrace(request, MediaType.TEXT_HTML);
boolean includeDetails = isIncludeDetails(request, MediaType.TEXT_HTML); boolean includeMessage = isIncludeMessage(request, MediaType.TEXT_HTML);
Map<String, Object> error = getErrorAttributes(request, includeStackTrace, includeDetails); boolean includeBindingErrors = isIncludeBindingErrors(request, MediaType.TEXT_HTML);
Map<String, Object> error = getErrorAttributes(request, includeStackTrace, includeMessage,
includeBindingErrors);
int errorStatus = getHttpStatus(error); int errorStatus = getHttpStatus(error);
ServerResponse.BodyBuilder responseBody = ServerResponse.status(errorStatus).contentType(TEXT_HTML_UTF8); ServerResponse.BodyBuilder responseBody = ServerResponse.status(errorStatus).contentType(TEXT_HTML_UTF8);
return Flux.just(getData(errorStatus).toArray(new String[] {})) return Flux.just(getData(errorStatus).toArray(new String[] {}))
...@@ -143,8 +145,10 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa ...@@ -143,8 +145,10 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa
*/ */
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) { protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.ALL); boolean includeStackTrace = isIncludeStackTrace(request, MediaType.ALL);
boolean includeDetails = isIncludeDetails(request, MediaType.ALL); boolean includeMessage = isIncludeMessage(request, MediaType.ALL);
Map<String, Object> error = getErrorAttributes(request, includeStackTrace, includeDetails); boolean includeBindingErrors = isIncludeBindingErrors(request, MediaType.ALL);
Map<String, Object> error = getErrorAttributes(request, includeStackTrace, includeMessage,
includeBindingErrors);
return ServerResponse.status(getHttpStatus(error)).contentType(MediaType.APPLICATION_JSON) return ServerResponse.status(getHttpStatus(error)).contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(error)); .body(BodyInserters.fromValue(error));
} }
...@@ -155,10 +159,12 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa ...@@ -155,10 +159,12 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa
* @param produces the media type produced (or {@code MediaType.ALL}) * @param produces the media type produced (or {@code MediaType.ALL})
* @return if the stacktrace attribute should be included * @return if the stacktrace attribute should be included
*/ */
@SuppressWarnings("deprecation")
protected boolean isIncludeStackTrace(ServerRequest request, MediaType produces) { protected boolean isIncludeStackTrace(ServerRequest request, MediaType produces) {
switch (this.errorProperties.getIncludeStacktrace()) { switch (this.errorProperties.getIncludeStacktrace()) {
case ALWAYS: case ALWAYS:
return true; return true;
case ON_PARAM:
case ON_TRACE_PARAM: case ON_TRACE_PARAM:
return isTraceEnabled(request); return isTraceEnabled(request);
default: default:
...@@ -167,17 +173,34 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa ...@@ -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 request the source request
* @param produces the media type produced (or {@code MediaType.ALL}) * @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) { protected boolean isIncludeMessage(ServerRequest request, MediaType produces) {
switch (this.errorProperties.getIncludeDetails()) { switch (this.errorProperties.getIncludeMessage()) {
case ALWAYS: case ALWAYS:
return true; return true;
case ON_DETAILS_PARAM: case ON_PARAM:
return isDetailsEnabled(request); 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: default:
return false; return false;
} }
......
...@@ -74,25 +74,30 @@ public abstract class AbstractErrorController implements ErrorController { ...@@ -74,25 +74,30 @@ public abstract class AbstractErrorController implements ErrorController {
* @param includeStackTrace if stack trace elements should be included * @param includeStackTrace if stack trace elements should be included
* @return the error attributes * @return the error attributes
* @deprecated since 2.3.0 in favor of * @deprecated since 2.3.0 in favor of
* {@link #getErrorAttributes(HttpServletRequest, boolean, boolean)} * {@link #getErrorAttributes(HttpServletRequest, boolean, boolean, boolean)}
*/ */
@Deprecated @Deprecated
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) { protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
return getErrorAttributes(request, includeStackTrace, false); return getErrorAttributes(request, includeStackTrace, false, false);
} }
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace, protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace,
boolean includeDetails) { boolean includeMessage, boolean includeBindingErrors) {
WebRequest webRequest = new ServletWebRequest(request); 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) { protected boolean getTraceParameter(HttpServletRequest request) {
return getBooleanParameter(request, "trace"); return getBooleanParameter(request, "trace");
} }
protected boolean getDetailsParameter(HttpServletRequest request) { protected boolean getMessageParameter(HttpServletRequest request) {
return getBooleanParameter(request, "details"); return getBooleanParameter(request, "message");
}
protected boolean getErrorsParameter(HttpServletRequest request) {
return getBooleanParameter(request, "errors");
} }
protected boolean getBooleanParameter(HttpServletRequest request, String parameterName) { protected boolean getBooleanParameter(HttpServletRequest request, String parameterName) {
......
...@@ -88,7 +88,8 @@ public class BasicErrorController extends AbstractErrorController { ...@@ -88,7 +88,8 @@ public class BasicErrorController extends AbstractErrorController {
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request); HttpStatus status = getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(request, Map<String, Object> 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()); response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model); ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
...@@ -101,7 +102,7 @@ public class BasicErrorController extends AbstractErrorController { ...@@ -101,7 +102,7 @@ public class BasicErrorController extends AbstractErrorController {
return new ResponseEntity<>(status); return new ResponseEntity<>(status);
} }
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL), Map<String, Object> 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); return new ResponseEntity<>(body, status);
} }
...@@ -117,10 +118,12 @@ public class BasicErrorController extends AbstractErrorController { ...@@ -117,10 +118,12 @@ public class BasicErrorController extends AbstractErrorController {
* @param produces the media type produced (or {@code MediaType.ALL}) * @param produces the media type produced (or {@code MediaType.ALL})
* @return if the stacktrace attribute should be included * @return if the stacktrace attribute should be included
*/ */
@SuppressWarnings("deprecation")
protected boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) { protected boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) {
switch (getErrorProperties().getIncludeStacktrace()) { switch (getErrorProperties().getIncludeStacktrace()) {
case ALWAYS: case ALWAYS:
return true; return true;
case ON_PARAM:
case ON_TRACE_PARAM: case ON_TRACE_PARAM:
return getTraceParameter(request); return getTraceParameter(request);
default: default:
...@@ -129,17 +132,34 @@ public class BasicErrorController extends AbstractErrorController { ...@@ -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 request the source request
* @param produces the media type produced (or {@code MediaType.ALL}) * @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) { protected boolean isIncludeMessage(HttpServletRequest request, MediaType produces) {
switch (getErrorProperties().getIncludeDetails()) { switch (getErrorProperties().getIncludeMessage()) {
case ALWAYS: case ALWAYS:
return true; return true;
case ON_DETAILS_PARAM: case ON_PARAM:
return getDetailsParameter(request); 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: default:
return false; return false;
} }
......
...@@ -37,7 +37,11 @@ ...@@ -37,7 +37,11 @@
} }
}, },
{ {
"name": "server.error.include-details", "name": "server.error.include-binding-errors",
"defaultValue": "never"
},
{
"name": "server.error.include-message",
"defaultValue": "never" "defaultValue": "never"
}, },
{ {
......
...@@ -99,7 +99,7 @@ class DefaultErrorWebExceptionHandlerIntegrationTests { ...@@ -99,7 +99,7 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
@Test @Test
void htmlError() { 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); WebTestClient client = getWebClient(context);
String body = client.get().uri("/").accept(MediaType.TEXT_HTML).exchange().expectStatus() String body = client.get().uri("/").accept(MediaType.TEXT_HTML).exchange().expectStatus()
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectHeader().contentType(TEXT_HTML_UTF8) .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectHeader().contentType(TEXT_HTML_UTF8)
...@@ -116,24 +116,26 @@ class DefaultErrorWebExceptionHandlerIntegrationTests { ...@@ -116,24 +116,26 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
.isBadRequest().expectBody().jsonPath("status").isEqualTo("400").jsonPath("error") .isBadRequest().expectBody().jsonPath("status").isEqualTo("400").jsonPath("error")
.isEqualTo(HttpStatus.BAD_REQUEST.getReasonPhrase()).jsonPath("path").isEqualTo(("/bind")) .isEqualTo(HttpStatus.BAD_REQUEST.getReasonPhrase()).jsonPath("path").isEqualTo(("/bind"))
.jsonPath("exception").doesNotExist().jsonPath("errors").doesNotExist().jsonPath("message") .jsonPath("exception").doesNotExist().jsonPath("errors").doesNotExist().jsonPath("message")
.isNotEmpty().jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId()); .isEmpty().jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId());
}); });
} }
@Test @Test
void bindingResultErrorIncludeDetails() { void bindingResultErrorIncludeMessageAndErrors() {
this.contextRunner.withPropertyValues("server.error.include-details=on-details-param").run((context) -> { this.contextRunner.withPropertyValues("server.error.include-message=on-param",
WebTestClient client = getWebClient(context); "server.error.include-binding-errors=on-param").run((context) -> {
client.post().uri("/bind?details=true").contentType(MediaType.APPLICATION_JSON).bodyValue("{}").exchange() WebTestClient client = getWebClient(context);
.expectStatus().isBadRequest().expectBody().jsonPath("status").isEqualTo("400").jsonPath("error") client.post().uri("/bind?message=true&errors=true").contentType(MediaType.APPLICATION_JSON)
.isEqualTo(HttpStatus.BAD_REQUEST.getReasonPhrase()).jsonPath("path").isEqualTo(("/bind")) .bodyValue("{}").exchange().expectStatus().isBadRequest().expectBody().jsonPath("status")
.jsonPath("exception").doesNotExist().jsonPath("errors").isArray().jsonPath("message").isNotEmpty() .isEqualTo("400").jsonPath("error").isEqualTo(HttpStatus.BAD_REQUEST.getReasonPhrase())
.jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId()); .jsonPath("path").isEqualTo(("/bind")).jsonPath("exception").doesNotExist()
}); .jsonPath("errors").isArray().jsonPath("message").isNotEmpty().jsonPath("requestId")
.isEqualTo(this.logIdFilter.getLogId());
});
} }
@Test @Test
void includeStackTraceOnParam() { void includeStackTraceOnTraceParam() {
this.contextRunner.withPropertyValues("server.error.include-exception=true", this.contextRunner.withPropertyValues("server.error.include-exception=true",
"server.error.include-stacktrace=on-trace-param").run((context) -> { "server.error.include-stacktrace=on-trace-param").run((context) -> {
WebTestClient client = getWebClient(context); WebTestClient client = getWebClient(context);
...@@ -146,6 +148,21 @@ class DefaultErrorWebExceptionHandlerIntegrationTests { ...@@ -146,6 +148,21 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
}); });
} }
@Test
void includeStackTraceOnParam() {
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")
.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 @Test
void alwaysIncludeStackTrace() { void alwaysIncludeStackTrace() {
this.contextRunner this.contextRunner
...@@ -177,11 +194,12 @@ class DefaultErrorWebExceptionHandlerIntegrationTests { ...@@ -177,11 +194,12 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
} }
@Test @Test
void includeDetailsOnParam() { void includeMessageOnParam() {
this.contextRunner.withPropertyValues("server.error.include-exception=true", this.contextRunner
"server.error.include-details=on-details-param").run((context) -> { .withPropertyValues("server.error.include-exception=true", "server.error.include-message=on-param")
.run((context) -> {
WebTestClient client = getWebClient(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(HttpStatus.INTERNAL_SERVER_ERROR).expectBody().jsonPath("status")
.isEqualTo("500").jsonPath("error") .isEqualTo("500").jsonPath("error")
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()).jsonPath("exception") .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()).jsonPath("exception")
...@@ -191,9 +209,9 @@ class DefaultErrorWebExceptionHandlerIntegrationTests { ...@@ -191,9 +209,9 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
} }
@Test @Test
void alwaysIncludeDetails() { void alwaysIncludeMessage() {
this.contextRunner 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) -> { .run((context) -> {
WebTestClient client = getWebClient(context); WebTestClient client = getWebClient(context);
client.get().uri("/?trace=false").exchange().expectStatus() client.get().uri("/?trace=false").exchange().expectStatus()
...@@ -206,9 +224,9 @@ class DefaultErrorWebExceptionHandlerIntegrationTests { ...@@ -206,9 +224,9 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
} }
@Test @Test
void neverIncludeDetails() { void neverIncludeMessage() {
this.contextRunner 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) -> { .run((context) -> {
WebTestClient client = getWebClient(context); WebTestClient client = getWebClient(context);
client.get().uri("/?trace=true").exchange().expectStatus() client.get().uri("/?trace=true").exchange().expectStatus()
...@@ -235,7 +253,7 @@ class DefaultErrorWebExceptionHandlerIntegrationTests { ...@@ -235,7 +253,7 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
void defaultErrorView() { void defaultErrorView() {
this.contextRunner this.contextRunner
.withPropertyValues("spring.mustache.prefix=classpath:/unknown/", .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) -> { .run((context) -> {
WebTestClient client = getWebClient(context); WebTestClient client = getWebClient(context);
String body = client.get().uri("/").accept(MediaType.TEXT_HTML).exchange().expectStatus() String body = client.get().uri("/").accept(MediaType.TEXT_HTML).exchange().expectStatus()
...@@ -250,7 +268,7 @@ class DefaultErrorWebExceptionHandlerIntegrationTests { ...@@ -250,7 +268,7 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
@Test @Test
void escapeHtmlInDefaultErrorView() { void escapeHtmlInDefaultErrorView() {
this.contextRunner 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) -> { .run((context) -> {
WebTestClient client = getWebClient(context); WebTestClient client = getWebClient(context);
String body = client.get().uri("/html").accept(MediaType.TEXT_HTML).exchange().expectStatus() String body = client.get().uri("/html").accept(MediaType.TEXT_HTML).exchange().expectStatus()
......
...@@ -66,7 +66,8 @@ class DefaultErrorWebExceptionHandlerTests { ...@@ -66,7 +66,8 @@ class DefaultErrorWebExceptionHandlerTests {
ResourceProperties resourceProperties = new ResourceProperties(); ResourceProperties resourceProperties = new ResourceProperties();
ErrorProperties errorProperties = new ErrorProperties(); ErrorProperties errorProperties = new ErrorProperties();
ApplicationContext context = new AnnotationConfigReactiveWebApplicationContext(); 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, DefaultErrorWebExceptionHandler exceptionHandler = new DefaultErrorWebExceptionHandler(errorAttributes,
resourceProperties, errorProperties, context); resourceProperties, errorProperties, context);
setupViewResolver(exceptionHandler); setupViewResolver(exceptionHandler);
......
...@@ -88,57 +88,63 @@ class BasicErrorControllerIntegrationTests { ...@@ -88,57 +88,63 @@ class BasicErrorControllerIntegrationTests {
void testErrorForMachineClientDefault() { void testErrorForMachineClientDefault() {
load(); load();
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("?trace=true"), Map.class); ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("?trace=true"), Map.class);
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", null, assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", null, "", "/");
"An error occurred while processing the request", "/");
assertThat(entity.getBody().containsKey("exception")).isFalse(); assertThat(entity.getBody().containsKey("exception")).isFalse();
assertThat(entity.getBody().containsKey("trace")).isFalse(); assertThat(entity.getBody().containsKey("trace")).isFalse();
} }
@Test @Test
void testErrorForMachineClientWithParamsTrue() { void testErrorForMachineClientWithTraceParamsTrue() {
load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-trace-param", load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-trace-param",
"--server.error.include-details=on-details-param"); "--server.error.include-message=on-param");
exceptionWithStackTraceAndDetails("?trace=true&details=true"); 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 @Test
void testErrorForMachineClientWithParamsFalse() { void testErrorForMachineClientWithParamsFalse() {
load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-trace-param", load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-param",
"--server.error.include-details=on-details-param"); "--server.error.include-message=on-param");
exceptionWithoutStackTraceAndDetails("?trace=false&details=false"); exceptionWithoutStackTraceAndMessage("?trace=false&message=false");
} }
@Test @Test
void testErrorForMachineClientWithParamsAbsent() { void testErrorForMachineClientWithParamsAbsent() {
load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-trace-param", load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-param",
"--server.error.include-details=on-details-param"); "--server.error.include-message=on-param");
exceptionWithoutStackTraceAndDetails(""); exceptionWithoutStackTraceAndMessage("");
} }
@Test @Test
void testErrorForMachineClientNeverParams() { void testErrorForMachineClientNeverParams() {
load("--server.error.include-exception=true", "--server.error.include-stacktrace=never", load("--server.error.include-exception=true", "--server.error.include-stacktrace=never",
"--server.error.include-details=never"); "--server.error.include-message=never");
exceptionWithoutStackTraceAndDetails("?trace=true&details=true"); exceptionWithoutStackTraceAndMessage("?trace=true&message=true");
} }
@Test @Test
void testErrorForMachineClientAlwaysParams() { void testErrorForMachineClientAlwaysParams() {
load("--server.error.include-exception=true", "--server.error.include-stacktrace=always", load("--server.error.include-exception=true", "--server.error.include-stacktrace=always",
"--server.error.include-details=always"); "--server.error.include-message=always");
exceptionWithStackTraceAndDetails("?trace=false&details=false"); exceptionWithStackTraceAndMessage("?trace=false&message=false");
} }
@Test @Test
void testErrorForMachineClientAlwaysParamsWithoutMessage() { 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<Map> entity = new TestRestTemplate().getForEntity(createUrl("/noMessage"), Map.class); ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/noMessage"), Map.class);
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", IllegalStateException.class, assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", IllegalStateException.class,
"No message available", "/noMessage"); "No message available", "/noMessage");
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
private void exceptionWithStackTraceAndDetails(String path) { private void exceptionWithStackTraceAndMessage(String path) {
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl(path), Map.class); ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl(path), Map.class);
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", IllegalStateException.class, assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", IllegalStateException.class,
"Expected!", "/"); "Expected!", "/");
...@@ -146,26 +152,25 @@ class BasicErrorControllerIntegrationTests { ...@@ -146,26 +152,25 @@ class BasicErrorControllerIntegrationTests {
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
private void exceptionWithoutStackTraceAndDetails(String path) { private void exceptionWithoutStackTraceAndMessage(String path) {
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl(path), Map.class); ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl(path), Map.class);
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", IllegalStateException.class, assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", IllegalStateException.class, "", "/");
"An error occurred while processing the request", "/");
assertThat(entity.getBody().containsKey("trace")).isFalse(); assertThat(entity.getBody().containsKey("trace")).isFalse();
} }
@Test @Test
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
void testErrorForAnnotatedExceptionWithoutDetails() { void testErrorForAnnotatedExceptionWithoutMessage() {
load("--server.error.include-exception=true"); load("--server.error.include-exception=true");
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotated"), Map.class); ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotated"), Map.class);
assertErrorAttributes(entity.getBody(), "400", "Bad Request", TestConfiguration.Errors.ExpectedException.class, assertErrorAttributes(entity.getBody(), "400", "Bad Request", TestConfiguration.Errors.ExpectedException.class,
"An error occurred while processing the request", "/annotated"); "", "/annotated");
} }
@Test @Test
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
void testErrorForAnnotatedExceptionWithDetails() { void testErrorForAnnotatedExceptionWithMessage() {
load("--server.error.include-exception=true", "--server.error.include-details=always"); load("--server.error.include-exception=true", "--server.error.include-message=always");
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotated"), Map.class); ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotated"), Map.class);
assertErrorAttributes(entity.getBody(), "400", "Bad Request", TestConfiguration.Errors.ExpectedException.class, assertErrorAttributes(entity.getBody(), "400", "Bad Request", TestConfiguration.Errors.ExpectedException.class,
"Expected!", "/annotated"); "Expected!", "/annotated");
...@@ -173,18 +178,17 @@ class BasicErrorControllerIntegrationTests { ...@@ -173,18 +178,17 @@ class BasicErrorControllerIntegrationTests {
@Test @Test
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
void testErrorForAnnotatedNoReasonExceptionWithoutDetails() { void testErrorForAnnotatedNoReasonExceptionWithoutMessage() {
load("--server.error.include-exception=true"); load("--server.error.include-exception=true");
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotatedNoReason"), Map.class); ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotatedNoReason"), Map.class);
assertErrorAttributes(entity.getBody(), "406", "Not Acceptable", assertErrorAttributes(entity.getBody(), "406", "Not Acceptable",
TestConfiguration.Errors.NoReasonExpectedException.class, TestConfiguration.Errors.NoReasonExpectedException.class, "", "/annotatedNoReason");
"An error occurred while processing the request", "/annotatedNoReason");
} }
@Test @Test
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
void testErrorForAnnotatedNoReasonExceptionWithDetails() { void testErrorForAnnotatedNoReasonExceptionWithMessage() {
load("--server.error.include-exception=true", "--server.error.include-details=always"); load("--server.error.include-exception=true", "--server.error.include-message=always");
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotatedNoReason"), Map.class); ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotatedNoReason"), Map.class);
assertErrorAttributes(entity.getBody(), "406", "Not Acceptable", assertErrorAttributes(entity.getBody(), "406", "Not Acceptable",
TestConfiguration.Errors.NoReasonExpectedException.class, "Expected message", "/annotatedNoReason"); TestConfiguration.Errors.NoReasonExpectedException.class, "Expected message", "/annotatedNoReason");
...@@ -192,8 +196,8 @@ class BasicErrorControllerIntegrationTests { ...@@ -192,8 +196,8 @@ class BasicErrorControllerIntegrationTests {
@Test @Test
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
void testErrorForAnnotatedNoMessageExceptionWithDetails() { void testErrorForAnnotatedNoMessageExceptionWithMessage() {
load("--server.error.include-exception=true", "--server.error.include-details=always"); load("--server.error.include-exception=true", "--server.error.include-message=always");
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotatedNoMessage"), Map.class); ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotatedNoMessage"), Map.class);
assertErrorAttributes(entity.getBody(), "406", "Not Acceptable", assertErrorAttributes(entity.getBody(), "406", "Not Acceptable",
TestConfiguration.Errors.NoReasonExpectedException.class, "No message available", TestConfiguration.Errors.NoReasonExpectedException.class, "No message available",
...@@ -201,37 +205,42 @@ class BasicErrorControllerIntegrationTests { ...@@ -201,37 +205,42 @@ class BasicErrorControllerIntegrationTests {
} }
@Test @Test
void testBindingExceptionForMachineClientWithDetailsParamTrue() { void testBindingExceptionForMachineClientWithMessageAndErrorsParamTrue() {
load("--server.error.include-exception=true", "--server.error.include-details=on-details-param"); load("--server.error.include-exception=true", "--server.error.include-message=on-param",
bindingExceptionWithDetails("?details=true"); "--server.error.include-binding-errors=on-param");
bindingExceptionWithMessageAndErrors("?message=true&errors=true");
} }
@Test @Test
void testBindingExceptionForMachineClientWithDetailsParamFalse() { void testBindingExceptionForMachineClientWithMessageAndErrorsParamFalse() {
load("--server.error.include-exception=true", "--server.error.include-details=on-details-param"); load("--server.error.include-exception=true", "--server.error.include-message=on-param",
bindingExceptionWithoutDetails("?details=false"); "--server.error.include-binding-errors=on-param");
bindingExceptionWithoutMessageAndErrors("?message=false&errors=false");
} }
@Test @Test
void testBindingExceptionForMachineClientWithDetailsParamAbsent() { void testBindingExceptionForMachineClientWithMessageAndErrorsParamAbsent() {
load("--server.error.include-exception=true", "--server.error.include-details=on-details-param"); load("--server.error.include-exception=true", "--server.error.include-message=on-param",
bindingExceptionWithoutDetails(""); "--server.error.include-binding-errors=on-param");
bindingExceptionWithoutMessageAndErrors("");
} }
@Test @Test
void testBindingExceptionForMachineClientAlwaysDetails() { void testBindingExceptionForMachineClientAlwaysMessageAndErrors() {
load("--server.error.include-exception=true", "--server.error.include-details=always"); load("--server.error.include-exception=true", "--server.error.include-message=always",
bindingExceptionWithDetails("?details=false"); "--server.error.include-binding-errors=always");
bindingExceptionWithMessageAndErrors("?message=false&errors=false");
} }
@Test @Test
void testBindingExceptionForMachineClientNeverDetails() { void testBindingExceptionForMachineClientNeverMessageAndErrors() {
load("--server.error.include-exception=true", "--server.error.include-details=never"); load("--server.error.include-exception=true", "--server.error.include-message=never",
bindingExceptionWithoutDetails("?details=true"); "--server.error.include-binding-errors=never");
bindingExceptionWithoutMessageAndErrors("?message=true&errors=true");
} }
@SuppressWarnings({ "rawtypes" }) @SuppressWarnings({ "rawtypes" })
private void bindingExceptionWithDetails(String param) { private void bindingExceptionWithMessageAndErrors(String param) {
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/bind" + param), Map.class); ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/bind" + param), Map.class);
assertErrorAttributes(entity.getBody(), "400", "Bad Request", BindException.class, assertErrorAttributes(entity.getBody(), "400", "Bad Request", BindException.class,
"Validation failed for object='test'. Error count: 1", "/bind"); "Validation failed for object='test'. Error count: 1", "/bind");
...@@ -239,10 +248,9 @@ class BasicErrorControllerIntegrationTests { ...@@ -239,10 +248,9 @@ class BasicErrorControllerIntegrationTests {
} }
@SuppressWarnings({ "rawtypes" }) @SuppressWarnings({ "rawtypes" })
private void bindingExceptionWithoutDetails(String param) { private void bindingExceptionWithoutMessageAndErrors(String param) {
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/bind" + param), Map.class); ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/bind" + param), Map.class);
assertErrorAttributes(entity.getBody(), "400", "Bad Request", BindException.class, "Validation failed", assertErrorAttributes(entity.getBody(), "400", "Bad Request", BindException.class, "", "/bind");
"/bind");
assertThat(entity.getBody().containsKey("errors")).isFalse(); assertThat(entity.getBody().containsKey("errors")).isFalse();
} }
...@@ -253,8 +261,8 @@ class BasicErrorControllerIntegrationTests { ...@@ -253,8 +261,8 @@ class BasicErrorControllerIntegrationTests {
RequestEntity request = RequestEntity.post(URI.create(createUrl("/bodyValidation"))) RequestEntity request = RequestEntity.post(URI.create(createUrl("/bodyValidation")))
.accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON).body("{}"); .accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON).body("{}");
ResponseEntity<Map> entity = new TestRestTemplate().exchange(request, Map.class); ResponseEntity<Map> entity = new TestRestTemplate().exchange(request, Map.class);
assertErrorAttributes(entity.getBody(), "400", "Bad Request", MethodArgumentNotValidException.class, assertErrorAttributes(entity.getBody(), "400", "Bad Request", MethodArgumentNotValidException.class, "",
"Validation failed", "/bodyValidation"); "/bodyValidation");
assertThat(entity.getBody().containsKey("errors")).isFalse(); assertThat(entity.getBody().containsKey("errors")).isFalse();
} }
...@@ -262,7 +270,7 @@ class BasicErrorControllerIntegrationTests { ...@@ -262,7 +270,7 @@ class BasicErrorControllerIntegrationTests {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
void testBindingExceptionForMachineClientDefault() { void testBindingExceptionForMachineClientDefault() {
load(); 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(); .accept(MediaType.APPLICATION_JSON).build();
ResponseEntity<Map> entity = new TestRestTemplate().exchange(request, Map.class); ResponseEntity<Map> entity = new TestRestTemplate().exchange(request, Map.class);
assertThat(entity.getBody().containsKey("exception")).isFalse(); assertThat(entity.getBody().containsKey("exception")).isFalse();
......
...@@ -69,7 +69,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. ...@@ -69,7 +69,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Dave Syer * @author Dave Syer
* @author Scott Frederick * @author Scott Frederick
*/ */
@SpringBootTest(properties = { "server.error.include-details=always" }) @SpringBootTest(properties = { "server.error.include-message=always" })
@DirtiesContext @DirtiesContext
class BasicErrorControllerMockMvcTests { class BasicErrorControllerMockMvcTests {
......
...@@ -52,7 +52,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. ...@@ -52,7 +52,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Dave Syer * @author Dave Syer
* @author Scott Frederick * @author Scott Frederick
*/ */
@SpringBootTest(properties = { "server.error.include-details=always" }) @SpringBootTest(properties = { "server.error.include-message=always" })
@DirtiesContext @DirtiesContext
class DefaultErrorViewIntegrationTests { class DefaultErrorViewIntegrationTests {
......
...@@ -51,7 +51,7 @@ class ErrorMvcAutoConfigurationTests { ...@@ -51,7 +51,7 @@ class ErrorMvcAutoConfigurationTests {
ErrorAttributes errorAttributes = context.getBean(ErrorAttributes.class); ErrorAttributes errorAttributes = context.getBean(ErrorAttributes.class);
DispatcherServletWebRequest webRequest = createWebRequest(new IllegalStateException("Exception message"), DispatcherServletWebRequest webRequest = createWebRequest(new IllegalStateException("Exception message"),
false); false);
errorView.render(errorAttributes.getErrorAttributes(webRequest, true, true), webRequest.getRequest(), errorView.render(errorAttributes.getErrorAttributes(webRequest, true, true, true), webRequest.getRequest(),
webRequest.getResponse()); webRequest.getResponse());
assertThat(webRequest.getResponse().getContentType()).isEqualTo("text/html;charset=UTF-8"); assertThat(webRequest.getResponse().getContentType()).isEqualTo("text/html;charset=UTF-8");
String responseString = ((MockHttpServletResponse) webRequest.getResponse()).getContentAsString(); String responseString = ((MockHttpServletResponse) webRequest.getResponse()).getContentAsString();
...@@ -69,7 +69,7 @@ class ErrorMvcAutoConfigurationTests { ...@@ -69,7 +69,7 @@ class ErrorMvcAutoConfigurationTests {
ErrorAttributes errorAttributes = context.getBean(ErrorAttributes.class); ErrorAttributes errorAttributes = context.getBean(ErrorAttributes.class);
DispatcherServletWebRequest webRequest = createWebRequest(new IllegalStateException("Exception message"), DispatcherServletWebRequest webRequest = createWebRequest(new IllegalStateException("Exception message"),
true); true);
errorView.render(errorAttributes.getErrorAttributes(webRequest, true, true), webRequest.getRequest(), errorView.render(errorAttributes.getErrorAttributes(webRequest, true, true, true), webRequest.getRequest(),
webRequest.getResponse()); webRequest.getResponse());
assertThat(output).contains("Cannot render error page for request [/path] " assertThat(output).contains("Cannot render error page for request [/path] "
+ "and exception [Exception message] as the response has " + "and exception [Exception message] as the response has "
......
...@@ -71,7 +71,8 @@ public class DevToolsPropertyDefaultsPostProcessor implements EnvironmentPostPro ...@@ -71,7 +71,8 @@ public class DevToolsPropertyDefaultsPostProcessor implements EnvironmentPostPro
properties.put("spring.resources.chain.cache", "false"); properties.put("spring.resources.chain.cache", "false");
properties.put("spring.template.provider.cache", "false"); properties.put("spring.template.provider.cache", "false");
properties.put("spring.mvc.log-resolved-exception", "true"); 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.error.include-stacktrace", "ALWAYS");
properties.put("server.servlet.jsp.init-parameters.development", "true"); properties.put("server.servlet.jsp.init-parameters.development", "true");
properties.put("spring.reactor.debug", "true"); properties.put("spring.reactor.debug", "true");
......
...@@ -108,8 +108,8 @@ class DevToolPropertiesIntegrationTests { ...@@ -108,8 +108,8 @@ class DevToolPropertiesIntegrationTests {
ConfigurableEnvironment environment = this.context.getEnvironment(); ConfigurableEnvironment environment = this.context.getEnvironment();
String includeStackTrace = environment.getProperty("server.error.include-stacktrace"); String includeStackTrace = environment.getProperty("server.error.include-stacktrace");
assertThat(includeStackTrace).isEqualTo(ErrorProperties.IncludeStacktrace.ALWAYS.toString()); assertThat(includeStackTrace).isEqualTo(ErrorProperties.IncludeStacktrace.ALWAYS.toString());
String includeDetails = environment.getProperty("server.error.include-details"); String includeMessage = environment.getProperty("server.error.include-message");
assertThat(includeDetails).isEqualTo(ErrorProperties.IncludeDetails.ALWAYS.toString()); assertThat(includeMessage).isEqualTo(ErrorProperties.IncludeAttribute.ALWAYS.toString());
} }
protected ConfigurableApplicationContext getContext(Supplier<ConfigurableApplicationContext> supplier) protected ConfigurableApplicationContext getContext(Supplier<ConfigurableApplicationContext> supplier)
......
...@@ -30,7 +30,6 @@ import org.springframework.util.StringUtils; ...@@ -30,7 +30,6 @@ import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError; import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.ResponseStatus; 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.reactive.function.server.ServerRequest;
import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
...@@ -82,12 +81,12 @@ public class DefaultErrorAttributes implements ErrorAttributes { ...@@ -82,12 +81,12 @@ public class DefaultErrorAttributes implements ErrorAttributes {
@Override @Override
@Deprecated @Deprecated
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) { public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
return getErrorAttributes(request, includeStackTrace, false); return getErrorAttributes(request, includeStackTrace, false, false);
} }
@Override @Override
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace, public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace,
boolean includeDetails) { boolean includeMessage, boolean includeBindingErrors) {
Map<String, Object> errorAttributes = new LinkedHashMap<>(); Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date()); errorAttributes.put("timestamp", new Date());
errorAttributes.put("path", request.path()); errorAttributes.put("path", request.path());
...@@ -97,9 +96,9 @@ public class DefaultErrorAttributes implements ErrorAttributes { ...@@ -97,9 +96,9 @@ public class DefaultErrorAttributes implements ErrorAttributes {
HttpStatus errorStatus = determineHttpStatus(error, responseStatusAnnotation); HttpStatus errorStatus = determineHttpStatus(error, responseStatusAnnotation);
errorAttributes.put("status", errorStatus.value()); errorAttributes.put("status", errorStatus.value());
errorAttributes.put("error", errorStatus.getReasonPhrase()); 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()); errorAttributes.put("requestId", request.exchange().getRequest().getId());
handleException(errorAttributes, determineException(error), includeStackTrace, includeDetails); handleException(errorAttributes, determineException(error), includeStackTrace, includeBindingErrors);
return errorAttributes; return errorAttributes;
} }
...@@ -111,13 +110,13 @@ public class DefaultErrorAttributes implements ErrorAttributes { ...@@ -111,13 +110,13 @@ public class DefaultErrorAttributes implements ErrorAttributes {
} }
private String determineMessage(Throwable error, MergedAnnotation<ResponseStatus> responseStatusAnnotation, private String determineMessage(Throwable error, MergedAnnotation<ResponseStatus> responseStatusAnnotation,
boolean includeDetails) { boolean includeMessage) {
if (error instanceof WebExchangeBindException) { if (!includeMessage) {
return includeDetails ? error.getMessage() : "Validation failed";
}
if (!includeDetails) {
return ""; return "";
} }
if (error instanceof BindingResult) {
return error.getMessage();
}
if (error instanceof ResponseStatusException) { if (error instanceof ResponseStatusException) {
return ((ResponseStatusException) error).getReason(); return ((ResponseStatusException) error).getReason();
} }
...@@ -143,14 +142,14 @@ public class DefaultErrorAttributes implements ErrorAttributes { ...@@ -143,14 +142,14 @@ public class DefaultErrorAttributes implements ErrorAttributes {
} }
private void handleException(Map<String, Object> errorAttributes, Throwable error, boolean includeStackTrace, private void handleException(Map<String, Object> errorAttributes, Throwable error, boolean includeStackTrace,
boolean includeDetails) { boolean includeBindingErrors) {
if (this.includeException) { if (this.includeException) {
errorAttributes.put("exception", error.getClass().getName()); errorAttributes.put("exception", error.getClass().getName());
} }
if (includeStackTrace) { if (includeStackTrace) {
addStackTrace(errorAttributes, error); addStackTrace(errorAttributes, error);
} }
if (includeDetails && (error instanceof BindingResult)) { if (includeBindingErrors && (error instanceof BindingResult)) {
BindingResult result = (BindingResult) error; BindingResult result = (BindingResult) error;
if (result.hasErrors()) { if (result.hasErrors()) {
errorAttributes.put("errors", result.getAllErrors()); errorAttributes.put("errors", result.getAllErrors());
......
...@@ -36,10 +36,10 @@ public interface ErrorAttributes { ...@@ -36,10 +36,10 @@ public interface ErrorAttributes {
* Return a {@link Map} of the error attributes. The map can be used as the model of * 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. * an error page, or returned as a {@link ServerResponse} body.
* @param request the source request * @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 * @return a map of error attributes
* @deprecated since 2.3.0 in favor of * @deprecated since 2.3.0 in favor of
* {@link #getErrorAttributes(ServerRequest, boolean, boolean)} * {@link #getErrorAttributes(ServerRequest, boolean, boolean, boolean)}
*/ */
Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace); Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace);
...@@ -47,11 +47,13 @@ public interface ErrorAttributes { ...@@ -47,11 +47,13 @@ public interface ErrorAttributes {
* Return a {@link Map} of the error attributes. The map can be used as the model of * 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. * an error page, or returned as a {@link ServerResponse} body.
* @param request the source request * @param request the source request
* @param includeStackTrace if stack trace elements should be included * @param includeStackTrace if stack trace attribute should be included
* @param includeDetails if message and errors elements 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 * @return a map of error attributes
*/ */
Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace, boolean includeDetails); Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace, boolean includeMessage,
boolean includeBindingErrors);
/** /**
* Return the underlying cause of the error or {@code null} if the error cannot be * Return the underlying cause of the error or {@code null} if the error cannot be
......
...@@ -104,16 +104,16 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException ...@@ -104,16 +104,16 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException
@Override @Override
@Deprecated @Deprecated
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
return getErrorAttributes(webRequest, includeStackTrace, false); return getErrorAttributes(webRequest, includeStackTrace, false, false);
} }
@Override @Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace, public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace,
boolean includeDetails) { boolean includeMessage, boolean includeBindingErrors) {
Map<String, Object> errorAttributes = new LinkedHashMap<>(); Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date()); errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, webRequest); addStatus(errorAttributes, webRequest);
addErrorDetails(errorAttributes, webRequest, includeStackTrace, includeDetails); addErrorDetails(errorAttributes, webRequest, includeStackTrace, includeMessage, includeBindingErrors);
addPath(errorAttributes, webRequest); addPath(errorAttributes, webRequest);
return errorAttributes; return errorAttributes;
} }
...@@ -136,7 +136,7 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException ...@@ -136,7 +136,7 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException
} }
private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest, boolean includeStackTrace, private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest, boolean includeStackTrace,
boolean includeDetails) { boolean includeMessage, boolean includeBindingErrors) {
Throwable error = getError(webRequest); Throwable error = getError(webRequest);
if (error != null) { if (error != null) {
while (error instanceof ServletException && error.getCause() != null) { while (error instanceof ServletException && error.getCause() != null) {
...@@ -149,24 +149,24 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException ...@@ -149,24 +149,24 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException
addStackTrace(errorAttributes, error); addStackTrace(errorAttributes, error);
} }
} }
addErrorMessage(errorAttributes, webRequest, error, includeDetails); addErrorMessage(errorAttributes, webRequest, error, includeMessage, includeBindingErrors);
} }
private void addErrorMessage(Map<String, Object> errorAttributes, WebRequest webRequest, Throwable error, private void addErrorMessage(Map<String, Object> errorAttributes, WebRequest webRequest, Throwable error,
boolean includeDetails) { boolean includeMessage, boolean includeBindingErrors) {
BindingResult result = extractBindingResult(error); BindingResult result = extractBindingResult(error);
if (result == null) { if (result == null) {
addExceptionErrorMessage(errorAttributes, webRequest, error, includeDetails); addExceptionErrorMessage(errorAttributes, webRequest, error, includeMessage);
} }
else { else {
addBindingResultErrorMessage(errorAttributes, result, includeDetails); addBindingResultErrorMessage(errorAttributes, result, includeMessage, includeBindingErrors);
} }
} }
private void addExceptionErrorMessage(Map<String, Object> errorAttributes, WebRequest webRequest, Throwable error, private void addExceptionErrorMessage(Map<String, Object> errorAttributes, WebRequest webRequest, Throwable error,
boolean includeDetails) { boolean includeMessage) {
if (!includeDetails) { if (!includeMessage) {
errorAttributes.put("message", "An error occurred while processing the request"); errorAttributes.put("message", "");
return; return;
} }
Object message = getAttribute(webRequest, RequestDispatcher.ERROR_MESSAGE); Object message = getAttribute(webRequest, RequestDispatcher.ERROR_MESSAGE);
...@@ -180,16 +180,12 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException ...@@ -180,16 +180,12 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException
} }
private void addBindingResultErrorMessage(Map<String, Object> errorAttributes, BindingResult result, private void addBindingResultErrorMessage(Map<String, Object> errorAttributes, BindingResult result,
boolean includeDetails) { boolean includeMessage, boolean includeBindingErrors) {
if (!includeDetails) { errorAttributes.put("message", (includeMessage) ? "Validation failed for object='" + result.getObjectName()
errorAttributes.put("message", "Validation failed"); + "'. " + "Error count: " + result.getErrorCount() : "");
return; if (includeBindingErrors && result.hasErrors()) {
}
if (result.hasErrors()) {
errorAttributes.put("errors", result.getAllErrors()); errorAttributes.put("errors", result.getAllErrors());
} }
errorAttributes.put("message", "Validation failed for object='" + result.getObjectName() + "'. "
+ "Error count: " + result.getErrorCount());
} }
private BindingResult extractBindingResult(Throwable error) { private BindingResult extractBindingResult(Throwable error) {
......
...@@ -37,10 +37,10 @@ public interface ErrorAttributes { ...@@ -37,10 +37,10 @@ public interface ErrorAttributes {
* an error page {@link ModelAndView}, or returned as a * an error page {@link ModelAndView}, or returned as a
* {@link ResponseBody @ResponseBody}. * {@link ResponseBody @ResponseBody}.
* @param webRequest the source request * @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 * @return a map of error attributes
* @deprecated since 2.3.0 in favor of * @deprecated since 2.3.0 in favor of
* {@link #getErrorAttributes(WebRequest, boolean, boolean)} * {@link #getErrorAttributes(WebRequest, boolean, boolean, boolean)}
*/ */
@Deprecated @Deprecated
Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace); Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace);
...@@ -50,12 +50,14 @@ public interface ErrorAttributes { ...@@ -50,12 +50,14 @@ public interface ErrorAttributes {
* an error page {@link ModelAndView}, or returned as a * an error page {@link ModelAndView}, or returned as a
* {@link ResponseBody @ResponseBody}. * {@link ResponseBody @ResponseBody}.
* @param webRequest the source request * @param webRequest the source request
* @param includeStackTrace if stack trace elements should be included * @param includeStackTrace if stack trace element should be included
* @param includeDetails if message and errors elements 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 * @return a map of error attributes
* @since 2.3.0 * @since 2.3.0
*/ */
Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace, boolean includeDetails); Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace, boolean includeMessage,
boolean includeBindingErrors);
/** /**
* Return the underlying cause of the error or {@code null} if the error cannot be * Return the underlying cause of the error or {@code null} if the error cannot be
......
...@@ -62,7 +62,7 @@ class DefaultErrorAttributesTests { ...@@ -62,7 +62,7 @@ class DefaultErrorAttributesTests {
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test").build()); MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test").build());
ServerRequest request = ServerRequest.create(exchange, this.readers); ServerRequest request = ServerRequest.create(exchange, this.readers);
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(() -> this.errorAttributes.getErrorAttributes(request, false, false)) .isThrownBy(() -> this.errorAttributes.getErrorAttributes(request, false, false, false))
.withMessageContaining("Missing exception attribute in ServerWebExchange"); .withMessageContaining("Missing exception attribute in ServerWebExchange");
} }
...@@ -70,7 +70,7 @@ class DefaultErrorAttributesTests { ...@@ -70,7 +70,7 @@ class DefaultErrorAttributesTests {
void includeTimeStamp() { void includeTimeStamp() {
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, NOT_FOUND), Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, NOT_FOUND),
false, false); false, false, false);
assertThat(attributes.get("timestamp")).isInstanceOf(Date.class); assertThat(attributes.get("timestamp")).isInstanceOf(Date.class);
} }
...@@ -79,7 +79,7 @@ class DefaultErrorAttributesTests { ...@@ -79,7 +79,7 @@ class DefaultErrorAttributesTests {
Error error = new OutOfMemoryError("Test error"); Error error = new OutOfMemoryError("Test error");
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, error), Map<String, Object> 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("error")).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
assertThat(attributes.get("status")).isEqualTo(500); assertThat(attributes.get("status")).isEqualTo(500);
} }
...@@ -89,7 +89,7 @@ class DefaultErrorAttributesTests { ...@@ -89,7 +89,7 @@ class DefaultErrorAttributesTests {
Exception error = new CustomException(); Exception error = new CustomException();
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, error), Map<String, Object> 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("error")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.getReasonPhrase());
assertThat(attributes.get("message")).isEqualTo(""); assertThat(attributes.get("message")).isEqualTo("");
assertThat(attributes.get("status")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.value()); assertThat(attributes.get("status")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.value());
...@@ -100,7 +100,7 @@ class DefaultErrorAttributesTests { ...@@ -100,7 +100,7 @@ class DefaultErrorAttributesTests {
Exception error = new CustomException("Test Message"); Exception error = new CustomException("Test Message");
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, error), Map<String, Object> 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("error")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.getReasonPhrase());
assertThat(attributes.get("message")).isEqualTo("Test Message"); assertThat(attributes.get("message")).isEqualTo("Test Message");
assertThat(attributes.get("status")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.value()); assertThat(attributes.get("status")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.value());
...@@ -111,7 +111,7 @@ class DefaultErrorAttributesTests { ...@@ -111,7 +111,7 @@ class DefaultErrorAttributesTests {
Exception error = new Custom2Exception(); Exception error = new Custom2Exception();
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, error), Map<String, Object> 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("error")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.getReasonPhrase());
assertThat(attributes.get("status")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.value()); assertThat(attributes.get("status")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.value());
assertThat(attributes.get("message")).isEqualTo("Nope!"); assertThat(attributes.get("message")).isEqualTo("Nope!");
...@@ -121,7 +121,7 @@ class DefaultErrorAttributesTests { ...@@ -121,7 +121,7 @@ class DefaultErrorAttributesTests {
void includeStatusCode() { void includeStatusCode() {
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, NOT_FOUND), Map<String, Object> 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("error")).isEqualTo(HttpStatus.NOT_FOUND.getReasonPhrase());
assertThat(attributes.get("status")).isEqualTo(404); assertThat(attributes.get("status")).isEqualTo(404);
} }
...@@ -131,18 +131,18 @@ class DefaultErrorAttributesTests { ...@@ -131,18 +131,18 @@ class DefaultErrorAttributesTests {
Error error = new OutOfMemoryError("Test error"); Error error = new OutOfMemoryError("Test error");
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
ServerRequest serverRequest = buildServerRequest(request, error); ServerRequest serverRequest = buildServerRequest(request, error);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true); Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true, false);
assertThat(this.errorAttributes.getError(serverRequest)).isSameAs(error); assertThat(this.errorAttributes.getError(serverRequest)).isSameAs(error);
assertThat(attributes.get("exception")).isNull(); assertThat(attributes.get("exception")).isNull();
assertThat(attributes.get("message")).isEqualTo("Test error"); assertThat(attributes.get("message")).isEqualTo("Test error");
} }
@Test @Test
void excludeDetails() { void excludeMessage() {
Error error = new OutOfMemoryError("Test error"); Error error = new OutOfMemoryError("Test error");
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
ServerRequest serverRequest = buildServerRequest(request, error); ServerRequest serverRequest = buildServerRequest(request, error);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, false); Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, false, false);
assertThat(this.errorAttributes.getError(serverRequest)).isSameAs(error); assertThat(this.errorAttributes.getError(serverRequest)).isSameAs(error);
assertThat(attributes.get("message")).isEqualTo(""); assertThat(attributes.get("message")).isEqualTo("");
} }
...@@ -153,7 +153,7 @@ class DefaultErrorAttributesTests { ...@@ -153,7 +153,7 @@ class DefaultErrorAttributesTests {
this.errorAttributes = new DefaultErrorAttributes(true); this.errorAttributes = new DefaultErrorAttributes(true);
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
ServerRequest serverRequest = buildServerRequest(request, error); ServerRequest serverRequest = buildServerRequest(request, error);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true); Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true, false);
assertThat(this.errorAttributes.getError(serverRequest)).isSameAs(error); assertThat(this.errorAttributes.getError(serverRequest)).isSameAs(error);
assertThat(attributes.get("exception")).isEqualTo(RuntimeException.class.getName()); assertThat(attributes.get("exception")).isEqualTo(RuntimeException.class.getName());
assertThat(attributes.get("message")).isEqualTo("Test"); assertThat(attributes.get("message")).isEqualTo("Test");
...@@ -166,7 +166,7 @@ class DefaultErrorAttributesTests { ...@@ -166,7 +166,7 @@ class DefaultErrorAttributesTests {
this.errorAttributes = new DefaultErrorAttributes(true); this.errorAttributes = new DefaultErrorAttributes(true);
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
ServerRequest serverRequest = buildServerRequest(request, error); ServerRequest serverRequest = buildServerRequest(request, error);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true); Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true, false);
assertThat(attributes.get("status")).isEqualTo(400); assertThat(attributes.get("status")).isEqualTo(400);
assertThat(attributes.get("message")).isEqualTo("invalid request"); assertThat(attributes.get("message")).isEqualTo("invalid request");
assertThat(attributes.get("exception")).isEqualTo(RuntimeException.class.getName()); assertThat(attributes.get("exception")).isEqualTo(RuntimeException.class.getName());
...@@ -180,7 +180,7 @@ class DefaultErrorAttributesTests { ...@@ -180,7 +180,7 @@ class DefaultErrorAttributesTests {
this.errorAttributes = new DefaultErrorAttributes(true); this.errorAttributes = new DefaultErrorAttributes(true);
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
ServerRequest serverRequest = buildServerRequest(request, error); ServerRequest serverRequest = buildServerRequest(request, error);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true); Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true, false);
assertThat(attributes.get("status")).isEqualTo(406); assertThat(attributes.get("status")).isEqualTo(406);
assertThat(attributes.get("message")).isEqualTo("could not process request"); assertThat(attributes.get("message")).isEqualTo("could not process request");
assertThat(attributes.get("exception")).isEqualTo(ResponseStatusException.class.getName()); assertThat(attributes.get("exception")).isEqualTo(ResponseStatusException.class.getName());
...@@ -192,7 +192,7 @@ class DefaultErrorAttributesTests { ...@@ -192,7 +192,7 @@ class DefaultErrorAttributesTests {
RuntimeException ex = new RuntimeException("Test"); RuntimeException ex = new RuntimeException("Test");
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), false, Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), false,
false); false, false);
assertThat(attributes.get("trace")).isNull(); assertThat(attributes.get("trace")).isNull();
} }
...@@ -201,7 +201,7 @@ class DefaultErrorAttributesTests { ...@@ -201,7 +201,7 @@ class DefaultErrorAttributesTests {
RuntimeException ex = new RuntimeException("Test"); RuntimeException ex = new RuntimeException("Test");
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), true, Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), true,
false); false, false);
assertThat(attributes.get("trace").toString()).startsWith("java.lang"); assertThat(attributes.get("trace").toString()).startsWith("java.lang");
} }
...@@ -209,7 +209,7 @@ class DefaultErrorAttributesTests { ...@@ -209,7 +209,7 @@ class DefaultErrorAttributesTests {
void includePath() { void includePath() {
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, NOT_FOUND), Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, NOT_FOUND),
false, false); false, false, false);
assertThat(attributes.get("path")).isEqualTo("/test"); assertThat(attributes.get("path")).isEqualTo("/test");
} }
...@@ -217,7 +217,7 @@ class DefaultErrorAttributesTests { ...@@ -217,7 +217,7 @@ class DefaultErrorAttributesTests {
void includeLogPrefix() { void includeLogPrefix() {
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
ServerRequest serverRequest = buildServerRequest(request, NOT_FOUND); ServerRequest serverRequest = buildServerRequest(request, NOT_FOUND);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, false); Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, false, false);
assertThat(attributes.get("requestId")).isEqualTo(serverRequest.exchange().getRequest().getId()); assertThat(attributes.get("requestId")).isEqualTo(serverRequest.exchange().getRequest().getId());
} }
...@@ -230,7 +230,7 @@ class DefaultErrorAttributesTests { ...@@ -230,7 +230,7 @@ class DefaultErrorAttributesTests {
Exception ex = new WebExchangeBindException(stringParam, bindingResult); Exception ex = new WebExchangeBindException(stringParam, bindingResult);
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), false, Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), false,
true); true, true);
assertThat(attributes.get("message")).asString() assertThat(attributes.get("message")).asString()
.startsWith("Validation failed for argument at index 0 in method: " .startsWith("Validation failed for argument at index 0 in method: "
+ "int org.springframework.boot.web.reactive.error.DefaultErrorAttributesTests" + "int org.springframework.boot.web.reactive.error.DefaultErrorAttributesTests"
...@@ -239,7 +239,7 @@ class DefaultErrorAttributesTests { ...@@ -239,7 +239,7 @@ class DefaultErrorAttributesTests {
} }
@Test @Test
void extractBindingResultErrorsExcludeDetails() throws Exception { void extractBindingResultErrorsExcludeMessageAndErrors() throws Exception {
Method method = getClass().getDeclaredMethod("method", String.class); Method method = getClass().getDeclaredMethod("method", String.class);
MethodParameter stringParam = new MethodParameter(method, 0); MethodParameter stringParam = new MethodParameter(method, 0);
BindingResult bindingResult = new MapBindingResult(Collections.singletonMap("a", "b"), "objectName"); BindingResult bindingResult = new MapBindingResult(Collections.singletonMap("a", "b"), "objectName");
...@@ -247,8 +247,8 @@ class DefaultErrorAttributesTests { ...@@ -247,8 +247,8 @@ class DefaultErrorAttributesTests {
Exception ex = new WebExchangeBindException(stringParam, bindingResult); Exception ex = new WebExchangeBindException(stringParam, bindingResult);
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), false, Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), false,
false); false, false);
assertThat(attributes.get("message")).isEqualTo("Validation failed"); assertThat(attributes.get("message")).isEqualTo("");
assertThat(attributes.containsKey("errors")).isFalse(); assertThat(attributes.containsKey("errors")).isFalse();
} }
......
...@@ -54,21 +54,21 @@ class DefaultErrorAttributesTests { ...@@ -54,21 +54,21 @@ class DefaultErrorAttributesTests {
@Test @Test
void includeTimeStamp() { void includeTimeStamp() {
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false); Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false, false);
assertThat(attributes.get("timestamp")).isInstanceOf(Date.class); assertThat(attributes.get("timestamp")).isInstanceOf(Date.class);
} }
@Test @Test
void specificStatusCode() { void specificStatusCode() {
this.request.setAttribute("javax.servlet.error.status_code", 404); this.request.setAttribute("javax.servlet.error.status_code", 404);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false); Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false, false);
assertThat(attributes.get("error")).isEqualTo(HttpStatus.NOT_FOUND.getReasonPhrase()); assertThat(attributes.get("error")).isEqualTo(HttpStatus.NOT_FOUND.getReasonPhrase());
assertThat(attributes.get("status")).isEqualTo(404); assertThat(attributes.get("status")).isEqualTo(404);
} }
@Test @Test
void missingStatusCode() { void missingStatusCode() {
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false); Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false, false);
assertThat(attributes.get("error")).isEqualTo("None"); assertThat(attributes.get("error")).isEqualTo("None");
assertThat(attributes.get("status")).isEqualTo(999); assertThat(attributes.get("status")).isEqualTo(999);
} }
...@@ -78,7 +78,7 @@ class DefaultErrorAttributesTests { ...@@ -78,7 +78,7 @@ class DefaultErrorAttributesTests {
RuntimeException ex = new RuntimeException("Test"); RuntimeException ex = new RuntimeException("Test");
ModelAndView modelAndView = this.errorAttributes.resolveException(this.request, null, null, ex); ModelAndView modelAndView = this.errorAttributes.resolveException(this.request, null, null, ex);
this.request.setAttribute("javax.servlet.error.exception", new RuntimeException("Ignored")); this.request.setAttribute("javax.servlet.error.exception", new RuntimeException("Ignored"));
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true); Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true, false);
assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(ex); assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(ex);
assertThat(modelAndView).isNull(); assertThat(modelAndView).isNull();
assertThat(attributes.containsKey("exception")).isFalse(); assertThat(attributes.containsKey("exception")).isFalse();
...@@ -86,46 +86,46 @@ class DefaultErrorAttributesTests { ...@@ -86,46 +86,46 @@ class DefaultErrorAttributesTests {
} }
@Test @Test
void servletErrorWithDetail() { void servletErrorWithMessage() {
RuntimeException ex = new RuntimeException("Test"); RuntimeException ex = new RuntimeException("Test");
this.request.setAttribute("javax.servlet.error.exception", ex); this.request.setAttribute("javax.servlet.error.exception", ex);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true); Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true, false);
assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(ex); assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(ex);
assertThat(attributes.containsKey("exception")).isFalse(); assertThat(attributes.containsKey("exception")).isFalse();
assertThat(attributes.get("message")).isEqualTo("Test"); assertThat(attributes.get("message")).isEqualTo("Test");
} }
@Test @Test
void servletErrorWithoutDetail() { void servletErrorWithoutMessage() {
RuntimeException ex = new RuntimeException("Test"); RuntimeException ex = new RuntimeException("Test");
this.request.setAttribute("javax.servlet.error.exception", ex); this.request.setAttribute("javax.servlet.error.exception", ex);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false); Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false, false);
assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(ex); assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(ex);
assertThat(attributes.containsKey("exception")).isFalse(); assertThat(attributes.containsKey("exception")).isFalse();
assertThat(attributes.get("message").toString()).contains("An error occurred"); assertThat(attributes.get("message").toString()).contains("");
} }
@Test @Test
void servletMessageWithDetail() { void servletMessageWithMessage() {
this.request.setAttribute("javax.servlet.error.message", "Test"); this.request.setAttribute("javax.servlet.error.message", "Test");
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true); Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true, false);
assertThat(attributes.containsKey("exception")).isFalse(); assertThat(attributes.containsKey("exception")).isFalse();
assertThat(attributes.get("message")).isEqualTo("Test"); assertThat(attributes.get("message")).isEqualTo("Test");
} }
@Test @Test
void servletMessageWithoutDetail() { void servletMessageWithoutMessage() {
this.request.setAttribute("javax.servlet.error.message", "Test"); this.request.setAttribute("javax.servlet.error.message", "Test");
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false); Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false, false);
assertThat(attributes.containsKey("exception")).isFalse(); assertThat(attributes.containsKey("exception")).isFalse();
assertThat(attributes.get("message")).asString().contains("An error occurred"); assertThat(attributes.get("message")).asString().contains("");
} }
@Test @Test
void nullExceptionMessage() { void nullExceptionMessage() {
this.request.setAttribute("javax.servlet.error.exception", new RuntimeException()); this.request.setAttribute("javax.servlet.error.exception", new RuntimeException());
this.request.setAttribute("javax.servlet.error.message", "Test"); this.request.setAttribute("javax.servlet.error.message", "Test");
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true); Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true, false);
assertThat(attributes.containsKey("exception")).isFalse(); assertThat(attributes.containsKey("exception")).isFalse();
assertThat(attributes.get("message")).isEqualTo("Test"); assertThat(attributes.get("message")).isEqualTo("Test");
} }
...@@ -133,7 +133,7 @@ class DefaultErrorAttributesTests { ...@@ -133,7 +133,7 @@ class DefaultErrorAttributesTests {
@Test @Test
void nullExceptionMessageAndServletMessage() { void nullExceptionMessageAndServletMessage() {
this.request.setAttribute("javax.servlet.error.exception", new RuntimeException()); this.request.setAttribute("javax.servlet.error.exception", new RuntimeException());
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true); Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true, false);
assertThat(attributes.containsKey("exception")).isFalse(); assertThat(attributes.containsKey("exception")).isFalse();
assertThat(attributes.get("message")).isEqualTo("No message available"); assertThat(attributes.get("message")).isEqualTo("No message available");
} }
...@@ -143,7 +143,7 @@ class DefaultErrorAttributesTests { ...@@ -143,7 +143,7 @@ class DefaultErrorAttributesTests {
RuntimeException ex = new RuntimeException("Test"); RuntimeException ex = new RuntimeException("Test");
ServletException wrapped = new ServletException(new ServletException(ex)); ServletException wrapped = new ServletException(new ServletException(ex));
this.request.setAttribute("javax.servlet.error.exception", wrapped); this.request.setAttribute("javax.servlet.error.exception", wrapped);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true); Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true, false);
assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(wrapped); assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(wrapped);
assertThat(attributes.containsKey("exception")).isFalse(); assertThat(attributes.containsKey("exception")).isFalse();
assertThat(attributes.get("message")).isEqualTo("Test"); assertThat(attributes.get("message")).isEqualTo("Test");
...@@ -153,7 +153,7 @@ class DefaultErrorAttributesTests { ...@@ -153,7 +153,7 @@ class DefaultErrorAttributesTests {
void getError() { void getError() {
Error error = new OutOfMemoryError("Test error"); Error error = new OutOfMemoryError("Test error");
this.request.setAttribute("javax.servlet.error.exception", error); this.request.setAttribute("javax.servlet.error.exception", error);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true); Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true, false);
assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(error); assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(error);
assertThat(attributes.containsKey("exception")).isFalse(); assertThat(attributes.containsKey("exception")).isFalse();
assertThat(attributes.get("message")).isEqualTo("Test error"); assertThat(attributes.get("message")).isEqualTo("Test error");
...@@ -183,17 +183,17 @@ class DefaultErrorAttributesTests { ...@@ -183,17 +183,17 @@ class DefaultErrorAttributesTests {
testBindingResult(bindingResult, ex, true); 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); this.request.setAttribute("javax.servlet.error.exception", ex);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false,
includeDetails); includeMessageAndErrors, includeMessageAndErrors);
if (includeDetails) { if (includeMessageAndErrors) {
assertThat(attributes.get("message")) assertThat(attributes.get("message"))
.isEqualTo("Validation failed for object='objectName'. Error count: 1"); .isEqualTo("Validation failed for object='objectName'. Error count: 1");
assertThat(attributes.get("errors")).isEqualTo(bindingResult.getAllErrors()); assertThat(attributes.get("errors")).isEqualTo(bindingResult.getAllErrors());
} }
else { else {
assertThat(attributes.get("message")).isEqualTo("Validation failed"); assertThat(attributes.get("message")).isEqualTo("");
assertThat(attributes.containsKey("errors")).isFalse(); assertThat(attributes.containsKey("errors")).isFalse();
} }
} }
...@@ -203,7 +203,7 @@ class DefaultErrorAttributesTests { ...@@ -203,7 +203,7 @@ class DefaultErrorAttributesTests {
DefaultErrorAttributes errorAttributes = new DefaultErrorAttributes(true); DefaultErrorAttributes errorAttributes = new DefaultErrorAttributes(true);
RuntimeException ex = new RuntimeException("Test"); RuntimeException ex = new RuntimeException("Test");
this.request.setAttribute("javax.servlet.error.exception", ex); this.request.setAttribute("javax.servlet.error.exception", ex);
Map<String, Object> attributes = errorAttributes.getErrorAttributes(this.webRequest, false, true); Map<String, Object> attributes = errorAttributes.getErrorAttributes(this.webRequest, false, true, false);
assertThat(attributes.get("exception")).isEqualTo(RuntimeException.class.getName()); assertThat(attributes.get("exception")).isEqualTo(RuntimeException.class.getName());
assertThat(attributes.get("message")).isEqualTo("Test"); assertThat(attributes.get("message")).isEqualTo("Test");
} }
...@@ -212,7 +212,7 @@ class DefaultErrorAttributesTests { ...@@ -212,7 +212,7 @@ class DefaultErrorAttributesTests {
void withStackTraceAttribute() { void withStackTraceAttribute() {
RuntimeException ex = new RuntimeException("Test"); RuntimeException ex = new RuntimeException("Test");
this.request.setAttribute("javax.servlet.error.exception", ex); this.request.setAttribute("javax.servlet.error.exception", ex);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, true, false); Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, true, false, false);
assertThat(attributes.get("trace").toString()).startsWith("java.lang"); assertThat(attributes.get("trace").toString()).startsWith("java.lang");
} }
...@@ -220,14 +220,14 @@ class DefaultErrorAttributesTests { ...@@ -220,14 +220,14 @@ class DefaultErrorAttributesTests {
void withoutStackTraceAttribute() { void withoutStackTraceAttribute() {
RuntimeException ex = new RuntimeException("Test"); RuntimeException ex = new RuntimeException("Test");
this.request.setAttribute("javax.servlet.error.exception", ex); this.request.setAttribute("javax.servlet.error.exception", ex);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false); Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false, false);
assertThat(attributes.containsKey("trace")).isFalse(); assertThat(attributes.containsKey("trace")).isFalse();
} }
@Test @Test
void path() { void path() {
this.request.setAttribute("javax.servlet.error.request_uri", "path"); this.request.setAttribute("javax.servlet.error.request_uri", "path");
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false); Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false, false);
assertThat(attributes.get("path")).isEqualTo("path"); assertThat(attributes.get("path")).isEqualTo("path");
} }
......
...@@ -37,7 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -37,7 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Scott Frederick * @author Scott Frederick
*/ */
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = { "server.error.include-details=always" }) properties = { "server.error.include-message=always" })
class SampleActuatorCustomSecurityApplicationTests extends AbstractSampleActuatorCustomSecurityTests { class SampleActuatorCustomSecurityApplicationTests extends AbstractSampleActuatorCustomSecurityTests {
@LocalServerPort @LocalServerPort
......
...@@ -39,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -39,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Dave Syer * @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 { class SampleActuatorUiApplicationTests {
@Autowired @Autowired
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment