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
This commit is contained in:
Scott Frederick
2020-04-29 17:56:47 -05:00
parent 18a9a229b1
commit a30740f8d2
26 changed files with 379 additions and 246 deletions

View File

@@ -46,9 +46,14 @@ public class ErrorProperties {
private IncludeStacktrace includeStacktrace = IncludeStacktrace.NEVER;
/**
* When to include "message" and "errors" attributes.
* When to include "message" attribute.
*/
private IncludeDetails includeDetails = IncludeDetails.NEVER;
private IncludeAttribute includeMessage = IncludeAttribute.NEVER;
/**
* When to include "errors" attribute.
*/
private IncludeAttribute includeBindingErrors = IncludeAttribute.NEVER;
private final Whitelabel whitelabel = new Whitelabel();
@@ -76,12 +81,20 @@ public class ErrorProperties {
this.includeStacktrace = includeStacktrace;
}
public IncludeDetails getIncludeDetails() {
return this.includeDetails;
public IncludeAttribute getIncludeMessage() {
return this.includeMessage;
}
public void setIncludeDetails(IncludeDetails includeDetails) {
this.includeDetails = includeDetails;
public void setIncludeMessage(IncludeAttribute includeMessage) {
this.includeMessage = includeMessage;
}
public IncludeAttribute getIncludeBindingErrors() {
return this.includeMessage;
}
public void setIncludeBindingErrors(IncludeAttribute includeMessage) {
this.includeMessage = includeMessage;
}
public Whitelabel getWhitelabel() {
@@ -96,39 +109,56 @@ public class ErrorProperties {
/**
* Never add stacktrace information.
*/
NEVER,
NEVER(IncludeAttribute.NEVER),
/**
* Always add stacktrace information.
*/
ALWAYS,
ALWAYS(IncludeAttribute.ALWAYS),
/**
* Add error attribute when the appropriate request parameter is "true".
*/
ON_PARAM(IncludeAttribute.ON_PARAM),
/**
* Add stacktrace information when the "trace" request parameter is "true".
* @deprecated since 2.3.0 in favor of {@link #ON_PARAM}
*/
ON_TRACE_PARAM
@Deprecated
ON_TRACE_PARAM(IncludeAttribute.ON_PARAM);
private final IncludeAttribute include;
IncludeStacktrace(IncludeAttribute include) {
this.include = include;
}
public IncludeAttribute getInclude() {
return this.include;
}
}
/**
* Include error details attributes options.
* Include error attributes options.
*/
public enum IncludeDetails {
public enum IncludeAttribute {
/**
* Never add error detail information.
* Never add error attribute.
*/
NEVER,
/**
* Always add error detail information.
* Always add error attribute.
*/
ALWAYS,
/**
* Add error details information when the "details" request parameter is "true".
* Add error attribute when the appropriate request parameter is "true".
*/
ON_DETAILS_PARAM
ON_PARAM
}

View File

@@ -134,24 +134,26 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept
* @param includeStackTrace whether to include the error stacktrace information
* @return the error attributes as a Map
* @deprecated since 2.3.0 in favor of
* {@link #getErrorAttributes(ServerRequest, boolean, boolean)}
* {@link #getErrorAttributes(ServerRequest, boolean, boolean, boolean)}
*/
@Deprecated
protected Map<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
* views or JSON payloads.
* @param request the source request
* @param includeStackTrace whether to include the error stacktrace information
* @param includeDetails whether to include message and errors attributes
* @param includeStackTrace whether to include the stacktrace attribute
* @param includeMessage whether to include the message attribute
* @param includeBindingErrors whether to include the errors attribute
* @return the error attributes as a Map
*/
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace,
boolean includeDetails) {
return this.errorAttributes.getErrorAttributes(request, includeStackTrace, includeDetails);
boolean includeMessage, boolean includeBindingErrors) {
return this.errorAttributes.getErrorAttributes(request, includeStackTrace, includeMessage,
includeBindingErrors);
}
/**
@@ -173,13 +175,23 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept
}
/**
* Check whether the details attribute has been set on the given request.
* Check whether the message attribute has been set on the given request.
* @param request the source request
* @return {@code true} if the error details have been requested, {@code false}
* @return {@code true} if the message attribute has been requested, {@code false}
* otherwise
*/
protected boolean isDetailsEnabled(ServerRequest request) {
return getBooleanParameter(request, "details");
protected boolean isMessageEnabled(ServerRequest request) {
return getBooleanParameter(request, "message");
}
/**
* Check whether the errors attribute has been set on the given request.
* @param request the source request
* @return {@code true} if the errors attribute has been requested, {@code false}
* otherwise
*/
protected boolean isBindingErrorsEnabled(ServerRequest request) {
return getBooleanParameter(request, "errors");
}
private boolean getBooleanParameter(ServerRequest request, String parameterName) {

View File

@@ -114,8 +114,10 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa
*/
protected Mono<ServerResponse> renderErrorView(ServerRequest request) {
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.TEXT_HTML);
boolean includeDetails = isIncludeDetails(request, MediaType.TEXT_HTML);
Map<String, Object> error = getErrorAttributes(request, includeStackTrace, includeDetails);
boolean includeMessage = isIncludeMessage(request, MediaType.TEXT_HTML);
boolean includeBindingErrors = isIncludeBindingErrors(request, MediaType.TEXT_HTML);
Map<String, Object> error = getErrorAttributes(request, includeStackTrace, includeMessage,
includeBindingErrors);
int errorStatus = getHttpStatus(error);
ServerResponse.BodyBuilder responseBody = ServerResponse.status(errorStatus).contentType(TEXT_HTML_UTF8);
return Flux.just(getData(errorStatus).toArray(new String[] {}))
@@ -143,8 +145,10 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa
*/
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.ALL);
boolean includeDetails = isIncludeDetails(request, MediaType.ALL);
Map<String, Object> error = getErrorAttributes(request, includeStackTrace, includeDetails);
boolean includeMessage = isIncludeMessage(request, MediaType.ALL);
boolean includeBindingErrors = isIncludeBindingErrors(request, MediaType.ALL);
Map<String, Object> error = getErrorAttributes(request, includeStackTrace, includeMessage,
includeBindingErrors);
return ServerResponse.status(getHttpStatus(error)).contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(error));
}
@@ -155,10 +159,12 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa
* @param produces the media type produced (or {@code MediaType.ALL})
* @return if the stacktrace attribute should be included
*/
@SuppressWarnings("deprecation")
protected boolean isIncludeStackTrace(ServerRequest request, MediaType produces) {
switch (this.errorProperties.getIncludeStacktrace()) {
case ALWAYS:
return true;
case ON_PARAM:
case ON_TRACE_PARAM:
return isTraceEnabled(request);
default:
@@ -167,17 +173,34 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa
}
/**
* Determine if the message and errors attributes should be included.
* Determine if the message attribute should be included.
* @param request the source request
* @param produces the media type produced (or {@code MediaType.ALL})
* @return if the message and errors attributes should be included
* @return if the message attribute should be included
*/
protected boolean isIncludeDetails(ServerRequest request, MediaType produces) {
switch (this.errorProperties.getIncludeDetails()) {
protected boolean isIncludeMessage(ServerRequest request, MediaType produces) {
switch (this.errorProperties.getIncludeMessage()) {
case ALWAYS:
return true;
case ON_DETAILS_PARAM:
return isDetailsEnabled(request);
case ON_PARAM:
return isMessageEnabled(request);
default:
return false;
}
}
/**
* Determine if the errors attribute should be included.
* @param request the source request
* @param produces the media type produced (or {@code MediaType.ALL})
* @return if the errors attribute should be included
*/
protected boolean isIncludeBindingErrors(ServerRequest request, MediaType produces) {
switch (this.errorProperties.getIncludeBindingErrors()) {
case ALWAYS:
return true;
case ON_PARAM:
return isBindingErrorsEnabled(request);
default:
return false;
}

View File

@@ -74,25 +74,30 @@ public abstract class AbstractErrorController implements ErrorController {
* @param includeStackTrace if stack trace elements should be included
* @return the error attributes
* @deprecated since 2.3.0 in favor of
* {@link #getErrorAttributes(HttpServletRequest, boolean, boolean)}
* {@link #getErrorAttributes(HttpServletRequest, boolean, boolean, boolean)}
*/
@Deprecated
protected Map<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,
boolean includeDetails) {
boolean includeMessage, boolean includeBindingErrors) {
WebRequest webRequest = new ServletWebRequest(request);
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace, includeDetails);
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace, includeMessage,
includeBindingErrors);
}
protected boolean getTraceParameter(HttpServletRequest request) {
return getBooleanParameter(request, "trace");
}
protected boolean getDetailsParameter(HttpServletRequest request) {
return getBooleanParameter(request, "details");
protected boolean getMessageParameter(HttpServletRequest request) {
return getBooleanParameter(request, "message");
}
protected boolean getErrorsParameter(HttpServletRequest request) {
return getBooleanParameter(request, "errors");
}
protected boolean getBooleanParameter(HttpServletRequest request, String parameterName) {

View File

@@ -88,7 +88,8 @@ public class BasicErrorController extends AbstractErrorController {
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(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());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
@@ -101,7 +102,7 @@ public class BasicErrorController extends AbstractErrorController {
return new ResponseEntity<>(status);
}
Map<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);
}
@@ -117,10 +118,12 @@ public class BasicErrorController extends AbstractErrorController {
* @param produces the media type produced (or {@code MediaType.ALL})
* @return if the stacktrace attribute should be included
*/
@SuppressWarnings("deprecation")
protected boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) {
switch (getErrorProperties().getIncludeStacktrace()) {
case ALWAYS:
return true;
case ON_PARAM:
case ON_TRACE_PARAM:
return getTraceParameter(request);
default:
@@ -129,17 +132,34 @@ public class BasicErrorController extends AbstractErrorController {
}
/**
* Determine if the error details attributes should be included.
* Determine if the message attribute should be included.
* @param request the source request
* @param produces the media type produced (or {@code MediaType.ALL})
* @return if the error details attributes should be included
* @return if the message attribute should be included
*/
protected boolean isIncludeDetails(HttpServletRequest request, MediaType produces) {
switch (getErrorProperties().getIncludeDetails()) {
protected boolean isIncludeMessage(HttpServletRequest request, MediaType produces) {
switch (getErrorProperties().getIncludeMessage()) {
case ALWAYS:
return true;
case ON_DETAILS_PARAM:
return getDetailsParameter(request);
case ON_PARAM:
return getMessageParameter(request);
default:
return false;
}
}
/**
* Determine if the errors attribute should be included.
* @param request the source request
* @param produces the media type produced (or {@code MediaType.ALL})
* @return if the errors attribute should be included
*/
protected boolean isIncludeBindingErrors(HttpServletRequest request, MediaType produces) {
switch (getErrorProperties().getIncludeMessage()) {
case ALWAYS:
return true;
case ON_PARAM:
return getErrorsParameter(request);
default:
return false;
}

View File

@@ -37,7 +37,11 @@
}
},
{
"name": "server.error.include-details",
"name": "server.error.include-binding-errors",
"defaultValue": "never"
},
{
"name": "server.error.include-message",
"defaultValue": "never"
},
{

View File

@@ -99,7 +99,7 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
@Test
void htmlError() {
this.contextRunner.withPropertyValues("server.error.include-details=always").run((context) -> {
this.contextRunner.withPropertyValues("server.error.include-message=always").run((context) -> {
WebTestClient client = getWebClient(context);
String body = client.get().uri("/").accept(MediaType.TEXT_HTML).exchange().expectStatus()
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectHeader().contentType(TEXT_HTML_UTF8)
@@ -116,26 +116,43 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
.isBadRequest().expectBody().jsonPath("status").isEqualTo("400").jsonPath("error")
.isEqualTo(HttpStatus.BAD_REQUEST.getReasonPhrase()).jsonPath("path").isEqualTo(("/bind"))
.jsonPath("exception").doesNotExist().jsonPath("errors").doesNotExist().jsonPath("message")
.isNotEmpty().jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId());
.isEmpty().jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId());
});
}
@Test
void bindingResultErrorIncludeDetails() {
this.contextRunner.withPropertyValues("server.error.include-details=on-details-param").run((context) -> {
WebTestClient client = getWebClient(context);
client.post().uri("/bind?details=true").contentType(MediaType.APPLICATION_JSON).bodyValue("{}").exchange()
.expectStatus().isBadRequest().expectBody().jsonPath("status").isEqualTo("400").jsonPath("error")
.isEqualTo(HttpStatus.BAD_REQUEST.getReasonPhrase()).jsonPath("path").isEqualTo(("/bind"))
.jsonPath("exception").doesNotExist().jsonPath("errors").isArray().jsonPath("message").isNotEmpty()
.jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId());
});
void bindingResultErrorIncludeMessageAndErrors() {
this.contextRunner.withPropertyValues("server.error.include-message=on-param",
"server.error.include-binding-errors=on-param").run((context) -> {
WebTestClient client = getWebClient(context);
client.post().uri("/bind?message=true&errors=true").contentType(MediaType.APPLICATION_JSON)
.bodyValue("{}").exchange().expectStatus().isBadRequest().expectBody().jsonPath("status")
.isEqualTo("400").jsonPath("error").isEqualTo(HttpStatus.BAD_REQUEST.getReasonPhrase())
.jsonPath("path").isEqualTo(("/bind")).jsonPath("exception").doesNotExist()
.jsonPath("errors").isArray().jsonPath("message").isNotEmpty().jsonPath("requestId")
.isEqualTo(this.logIdFilter.getLogId());
});
}
@Test
void includeStackTraceOnTraceParam() {
this.contextRunner.withPropertyValues("server.error.include-exception=true",
"server.error.include-stacktrace=on-trace-param").run((context) -> {
WebTestClient client = getWebClient(context);
client.get().uri("/?trace=true").exchange().expectStatus()
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectBody().jsonPath("status")
.isEqualTo("500").jsonPath("error")
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()).jsonPath("exception")
.isEqualTo(IllegalStateException.class.getName()).jsonPath("trace").exists()
.jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId());
});
}
@Test
void includeStackTraceOnParam() {
this.contextRunner.withPropertyValues("server.error.include-exception=true",
"server.error.include-stacktrace=on-trace-param").run((context) -> {
this.contextRunner
.withPropertyValues("server.error.include-exception=true", "server.error.include-stacktrace=on-param")
.run((context) -> {
WebTestClient client = getWebClient(context);
client.get().uri("/?trace=true").exchange().expectStatus()
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectBody().jsonPath("status")
@@ -177,11 +194,12 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
}
@Test
void includeDetailsOnParam() {
this.contextRunner.withPropertyValues("server.error.include-exception=true",
"server.error.include-details=on-details-param").run((context) -> {
void includeMessageOnParam() {
this.contextRunner
.withPropertyValues("server.error.include-exception=true", "server.error.include-message=on-param")
.run((context) -> {
WebTestClient client = getWebClient(context);
client.get().uri("/?details=true").exchange().expectStatus()
client.get().uri("/?message=true").exchange().expectStatus()
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectBody().jsonPath("status")
.isEqualTo("500").jsonPath("error")
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()).jsonPath("exception")
@@ -191,9 +209,9 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
}
@Test
void alwaysIncludeDetails() {
void alwaysIncludeMessage() {
this.contextRunner
.withPropertyValues("server.error.include-exception=true", "server.error.include-details=always")
.withPropertyValues("server.error.include-exception=true", "server.error.include-message=always")
.run((context) -> {
WebTestClient client = getWebClient(context);
client.get().uri("/?trace=false").exchange().expectStatus()
@@ -206,9 +224,9 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
}
@Test
void neverIncludeDetails() {
void neverIncludeMessage() {
this.contextRunner
.withPropertyValues("server.error.include-exception=true", "server.error.include-details=never")
.withPropertyValues("server.error.include-exception=true", "server.error.include-message=never")
.run((context) -> {
WebTestClient client = getWebClient(context);
client.get().uri("/?trace=true").exchange().expectStatus()
@@ -235,7 +253,7 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
void defaultErrorView() {
this.contextRunner
.withPropertyValues("spring.mustache.prefix=classpath:/unknown/",
"server.error.include-stacktrace=always", "server.error.include-details=always")
"server.error.include-stacktrace=always", "server.error.include-message=always")
.run((context) -> {
WebTestClient client = getWebClient(context);
String body = client.get().uri("/").accept(MediaType.TEXT_HTML).exchange().expectStatus()
@@ -250,7 +268,7 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
@Test
void escapeHtmlInDefaultErrorView() {
this.contextRunner
.withPropertyValues("spring.mustache.prefix=classpath:/unknown/", "server.error.include-details=always")
.withPropertyValues("spring.mustache.prefix=classpath:/unknown/", "server.error.include-message=always")
.run((context) -> {
WebTestClient client = getWebClient(context);
String body = client.get().uri("/html").accept(MediaType.TEXT_HTML).exchange().expectStatus()

View File

@@ -66,7 +66,8 @@ class DefaultErrorWebExceptionHandlerTests {
ResourceProperties resourceProperties = new ResourceProperties();
ErrorProperties errorProperties = new ErrorProperties();
ApplicationContext context = new AnnotationConfigReactiveWebApplicationContext();
given(errorAttributes.getErrorAttributes(any(), anyBoolean(), anyBoolean())).willReturn(getErrorAttributes());
given(errorAttributes.getErrorAttributes(any(), anyBoolean(), anyBoolean(), anyBoolean()))
.willReturn(getErrorAttributes());
DefaultErrorWebExceptionHandler exceptionHandler = new DefaultErrorWebExceptionHandler(errorAttributes,
resourceProperties, errorProperties, context);
setupViewResolver(exceptionHandler);

View File

@@ -88,57 +88,63 @@ class BasicErrorControllerIntegrationTests {
void testErrorForMachineClientDefault() {
load();
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("?trace=true"), Map.class);
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", null,
"An error occurred while processing the request", "/");
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", null, "", "/");
assertThat(entity.getBody().containsKey("exception")).isFalse();
assertThat(entity.getBody().containsKey("trace")).isFalse();
}
@Test
void testErrorForMachineClientWithParamsTrue() {
void testErrorForMachineClientWithTraceParamsTrue() {
load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-trace-param",
"--server.error.include-details=on-details-param");
exceptionWithStackTraceAndDetails("?trace=true&details=true");
"--server.error.include-message=on-param");
exceptionWithStackTraceAndMessage("?trace=true&message=true");
}
@Test
void testErrorForMachineClientWithParamsTrue() {
load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-param",
"--server.error.include-message=on-param");
exceptionWithStackTraceAndMessage("?trace=true&message=true");
}
@Test
void testErrorForMachineClientWithParamsFalse() {
load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-trace-param",
"--server.error.include-details=on-details-param");
exceptionWithoutStackTraceAndDetails("?trace=false&details=false");
load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-param",
"--server.error.include-message=on-param");
exceptionWithoutStackTraceAndMessage("?trace=false&message=false");
}
@Test
void testErrorForMachineClientWithParamsAbsent() {
load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-trace-param",
"--server.error.include-details=on-details-param");
exceptionWithoutStackTraceAndDetails("");
load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-param",
"--server.error.include-message=on-param");
exceptionWithoutStackTraceAndMessage("");
}
@Test
void testErrorForMachineClientNeverParams() {
load("--server.error.include-exception=true", "--server.error.include-stacktrace=never",
"--server.error.include-details=never");
exceptionWithoutStackTraceAndDetails("?trace=true&details=true");
"--server.error.include-message=never");
exceptionWithoutStackTraceAndMessage("?trace=true&message=true");
}
@Test
void testErrorForMachineClientAlwaysParams() {
load("--server.error.include-exception=true", "--server.error.include-stacktrace=always",
"--server.error.include-details=always");
exceptionWithStackTraceAndDetails("?trace=false&details=false");
"--server.error.include-message=always");
exceptionWithStackTraceAndMessage("?trace=false&message=false");
}
@Test
void testErrorForMachineClientAlwaysParamsWithoutMessage() {
load("--server.error.include-exception=true", "--server.error.include-details=always");
load("--server.error.include-exception=true", "--server.error.include-message=always");
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/noMessage"), Map.class);
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", IllegalStateException.class,
"No message available", "/noMessage");
}
@SuppressWarnings("rawtypes")
private void exceptionWithStackTraceAndDetails(String path) {
private void exceptionWithStackTraceAndMessage(String path) {
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl(path), Map.class);
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", IllegalStateException.class,
"Expected!", "/");
@@ -146,26 +152,25 @@ class BasicErrorControllerIntegrationTests {
}
@SuppressWarnings("rawtypes")
private void exceptionWithoutStackTraceAndDetails(String path) {
private void exceptionWithoutStackTraceAndMessage(String path) {
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl(path), Map.class);
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", IllegalStateException.class,
"An error occurred while processing the request", "/");
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", IllegalStateException.class, "", "/");
assertThat(entity.getBody().containsKey("trace")).isFalse();
}
@Test
@SuppressWarnings("rawtypes")
void testErrorForAnnotatedExceptionWithoutDetails() {
void testErrorForAnnotatedExceptionWithoutMessage() {
load("--server.error.include-exception=true");
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotated"), Map.class);
assertErrorAttributes(entity.getBody(), "400", "Bad Request", TestConfiguration.Errors.ExpectedException.class,
"An error occurred while processing the request", "/annotated");
"", "/annotated");
}
@Test
@SuppressWarnings("rawtypes")
void testErrorForAnnotatedExceptionWithDetails() {
load("--server.error.include-exception=true", "--server.error.include-details=always");
void testErrorForAnnotatedExceptionWithMessage() {
load("--server.error.include-exception=true", "--server.error.include-message=always");
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotated"), Map.class);
assertErrorAttributes(entity.getBody(), "400", "Bad Request", TestConfiguration.Errors.ExpectedException.class,
"Expected!", "/annotated");
@@ -173,18 +178,17 @@ class BasicErrorControllerIntegrationTests {
@Test
@SuppressWarnings("rawtypes")
void testErrorForAnnotatedNoReasonExceptionWithoutDetails() {
void testErrorForAnnotatedNoReasonExceptionWithoutMessage() {
load("--server.error.include-exception=true");
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotatedNoReason"), Map.class);
assertErrorAttributes(entity.getBody(), "406", "Not Acceptable",
TestConfiguration.Errors.NoReasonExpectedException.class,
"An error occurred while processing the request", "/annotatedNoReason");
TestConfiguration.Errors.NoReasonExpectedException.class, "", "/annotatedNoReason");
}
@Test
@SuppressWarnings("rawtypes")
void testErrorForAnnotatedNoReasonExceptionWithDetails() {
load("--server.error.include-exception=true", "--server.error.include-details=always");
void testErrorForAnnotatedNoReasonExceptionWithMessage() {
load("--server.error.include-exception=true", "--server.error.include-message=always");
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotatedNoReason"), Map.class);
assertErrorAttributes(entity.getBody(), "406", "Not Acceptable",
TestConfiguration.Errors.NoReasonExpectedException.class, "Expected message", "/annotatedNoReason");
@@ -192,8 +196,8 @@ class BasicErrorControllerIntegrationTests {
@Test
@SuppressWarnings("rawtypes")
void testErrorForAnnotatedNoMessageExceptionWithDetails() {
load("--server.error.include-exception=true", "--server.error.include-details=always");
void testErrorForAnnotatedNoMessageExceptionWithMessage() {
load("--server.error.include-exception=true", "--server.error.include-message=always");
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotatedNoMessage"), Map.class);
assertErrorAttributes(entity.getBody(), "406", "Not Acceptable",
TestConfiguration.Errors.NoReasonExpectedException.class, "No message available",
@@ -201,37 +205,42 @@ class BasicErrorControllerIntegrationTests {
}
@Test
void testBindingExceptionForMachineClientWithDetailsParamTrue() {
load("--server.error.include-exception=true", "--server.error.include-details=on-details-param");
bindingExceptionWithDetails("?details=true");
void testBindingExceptionForMachineClientWithMessageAndErrorsParamTrue() {
load("--server.error.include-exception=true", "--server.error.include-message=on-param",
"--server.error.include-binding-errors=on-param");
bindingExceptionWithMessageAndErrors("?message=true&errors=true");
}
@Test
void testBindingExceptionForMachineClientWithDetailsParamFalse() {
load("--server.error.include-exception=true", "--server.error.include-details=on-details-param");
bindingExceptionWithoutDetails("?details=false");
void testBindingExceptionForMachineClientWithMessageAndErrorsParamFalse() {
load("--server.error.include-exception=true", "--server.error.include-message=on-param",
"--server.error.include-binding-errors=on-param");
bindingExceptionWithoutMessageAndErrors("?message=false&errors=false");
}
@Test
void testBindingExceptionForMachineClientWithDetailsParamAbsent() {
load("--server.error.include-exception=true", "--server.error.include-details=on-details-param");
bindingExceptionWithoutDetails("");
void testBindingExceptionForMachineClientWithMessageAndErrorsParamAbsent() {
load("--server.error.include-exception=true", "--server.error.include-message=on-param",
"--server.error.include-binding-errors=on-param");
bindingExceptionWithoutMessageAndErrors("");
}
@Test
void testBindingExceptionForMachineClientAlwaysDetails() {
load("--server.error.include-exception=true", "--server.error.include-details=always");
bindingExceptionWithDetails("?details=false");
void testBindingExceptionForMachineClientAlwaysMessageAndErrors() {
load("--server.error.include-exception=true", "--server.error.include-message=always",
"--server.error.include-binding-errors=always");
bindingExceptionWithMessageAndErrors("?message=false&errors=false");
}
@Test
void testBindingExceptionForMachineClientNeverDetails() {
load("--server.error.include-exception=true", "--server.error.include-details=never");
bindingExceptionWithoutDetails("?details=true");
void testBindingExceptionForMachineClientNeverMessageAndErrors() {
load("--server.error.include-exception=true", "--server.error.include-message=never",
"--server.error.include-binding-errors=never");
bindingExceptionWithoutMessageAndErrors("?message=true&errors=true");
}
@SuppressWarnings({ "rawtypes" })
private void bindingExceptionWithDetails(String param) {
private void bindingExceptionWithMessageAndErrors(String param) {
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/bind" + param), Map.class);
assertErrorAttributes(entity.getBody(), "400", "Bad Request", BindException.class,
"Validation failed for object='test'. Error count: 1", "/bind");
@@ -239,10 +248,9 @@ class BasicErrorControllerIntegrationTests {
}
@SuppressWarnings({ "rawtypes" })
private void bindingExceptionWithoutDetails(String param) {
private void bindingExceptionWithoutMessageAndErrors(String param) {
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/bind" + param), Map.class);
assertErrorAttributes(entity.getBody(), "400", "Bad Request", BindException.class, "Validation failed",
"/bind");
assertErrorAttributes(entity.getBody(), "400", "Bad Request", BindException.class, "", "/bind");
assertThat(entity.getBody().containsKey("errors")).isFalse();
}
@@ -253,8 +261,8 @@ class BasicErrorControllerIntegrationTests {
RequestEntity request = RequestEntity.post(URI.create(createUrl("/bodyValidation")))
.accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON).body("{}");
ResponseEntity<Map> entity = new TestRestTemplate().exchange(request, Map.class);
assertErrorAttributes(entity.getBody(), "400", "Bad Request", MethodArgumentNotValidException.class,
"Validation failed", "/bodyValidation");
assertErrorAttributes(entity.getBody(), "400", "Bad Request", MethodArgumentNotValidException.class, "",
"/bodyValidation");
assertThat(entity.getBody().containsKey("errors")).isFalse();
}
@@ -262,7 +270,7 @@ class BasicErrorControllerIntegrationTests {
@SuppressWarnings("rawtypes")
void testBindingExceptionForMachineClientDefault() {
load();
RequestEntity request = RequestEntity.get(URI.create(createUrl("/bind?trace=true,details=true")))
RequestEntity request = RequestEntity.get(URI.create(createUrl("/bind?trace=true,message=true")))
.accept(MediaType.APPLICATION_JSON).build();
ResponseEntity<Map> entity = new TestRestTemplate().exchange(request, Map.class);
assertThat(entity.getBody().containsKey("exception")).isFalse();

View File

@@ -69,7 +69,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Dave Syer
* @author Scott Frederick
*/
@SpringBootTest(properties = { "server.error.include-details=always" })
@SpringBootTest(properties = { "server.error.include-message=always" })
@DirtiesContext
class BasicErrorControllerMockMvcTests {

View File

@@ -52,7 +52,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Dave Syer
* @author Scott Frederick
*/
@SpringBootTest(properties = { "server.error.include-details=always" })
@SpringBootTest(properties = { "server.error.include-message=always" })
@DirtiesContext
class DefaultErrorViewIntegrationTests {

View File

@@ -51,7 +51,7 @@ class ErrorMvcAutoConfigurationTests {
ErrorAttributes errorAttributes = context.getBean(ErrorAttributes.class);
DispatcherServletWebRequest webRequest = createWebRequest(new IllegalStateException("Exception message"),
false);
errorView.render(errorAttributes.getErrorAttributes(webRequest, true, true), webRequest.getRequest(),
errorView.render(errorAttributes.getErrorAttributes(webRequest, true, true, true), webRequest.getRequest(),
webRequest.getResponse());
assertThat(webRequest.getResponse().getContentType()).isEqualTo("text/html;charset=UTF-8");
String responseString = ((MockHttpServletResponse) webRequest.getResponse()).getContentAsString();
@@ -69,7 +69,7 @@ class ErrorMvcAutoConfigurationTests {
ErrorAttributes errorAttributes = context.getBean(ErrorAttributes.class);
DispatcherServletWebRequest webRequest = createWebRequest(new IllegalStateException("Exception message"),
true);
errorView.render(errorAttributes.getErrorAttributes(webRequest, true, true), webRequest.getRequest(),
errorView.render(errorAttributes.getErrorAttributes(webRequest, true, true, true), webRequest.getRequest(),
webRequest.getResponse());
assertThat(output).contains("Cannot render error page for request [/path] "
+ "and exception [Exception message] as the response has "