Skip to content

Commit 158933c

Browse files
Improve API of ErrorAttributes and DefaultErrorAttributes
This commit improves the backward-compatibility of the ErrorAttributes interfaces by providing a default implementation of a new method. It also encapsulates several parameters that control the inclusion or exclusion of error attributes into a new ErrorAttributeOptions type to make it easier and less intrusive to add additional options in the future. This encapsulation also makes the handling of the includeException option more similar to other options. Fixes gh-21324
1 parent c3c7fc0 commit 158933c

File tree

20 files changed

+586
-175
lines changed

20 files changed

+586
-175
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ManagementErrorEndpoint.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Map;
2020

2121
import org.springframework.boot.autoconfigure.web.ErrorProperties;
22+
import org.springframework.boot.web.error.ErrorAttributeOptions;
2223
import org.springframework.boot.web.servlet.error.ErrorAttributes;
2324
import org.springframework.boot.web.servlet.error.ErrorController;
2425
import org.springframework.stereotype.Controller;
@@ -53,10 +54,27 @@ public ManagementErrorEndpoint(ErrorAttributes errorAttributes, ErrorProperties
5354
@RequestMapping("${server.error.path:${error.path:/error}}")
5455
@ResponseBody
5556
public Map<String, Object> invoke(ServletWebRequest request) {
56-
return this.errorAttributes.getErrorAttributes(request, includeStackTrace(request), includeMessage(request),
57-
includeBindingErrors(request));
57+
return this.errorAttributes.getErrorAttributes(request, getErrorAttributeOptions(request));
5858
}
5959

60+
private ErrorAttributeOptions getErrorAttributeOptions(ServletWebRequest request) {
61+
ErrorAttributeOptions options = ErrorAttributeOptions.defaults();
62+
if (this.errorProperties.isIncludeException()) {
63+
options = options.including(ErrorAttributeOptions.Include.EXCEPTION);
64+
}
65+
if (includeStackTrace(request)) {
66+
options = options.including(ErrorAttributeOptions.Include.STACK_TRACE);
67+
}
68+
if (includeMessage(request)) {
69+
options = options.including(ErrorAttributeOptions.Include.MESSAGE);
70+
}
71+
if (includeBindingErrors(request)) {
72+
options = options.including(ErrorAttributeOptions.Include.BINDING_ERRORS);
73+
}
74+
return options;
75+
}
76+
77+
@SuppressWarnings("deprecation")
6078
private boolean includeStackTrace(ServletWebRequest request) {
6179
switch (this.errorProperties.getIncludeStacktrace()) {
6280
case ALWAYS:

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ManagementErrorEndpointTests.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.web.servlet;
1818

19+
import java.util.HashMap;
1920
import java.util.Map;
2021

2122
import org.junit.jupiter.api.BeforeEach;
@@ -26,6 +27,7 @@
2627
import org.springframework.boot.web.servlet.error.ErrorAttributes;
2728
import org.springframework.mock.web.MockHttpServletRequest;
2829
import org.springframework.web.context.request.ServletWebRequest;
30+
import org.springframework.web.context.request.WebRequest;
2931

3032
import static org.assertj.core.api.Assertions.assertThat;
3133

@@ -103,4 +105,63 @@ void errorResponseParamsFalse() {
103105
assertThat(response).doesNotContainKey("trace");
104106
}
105107

108+
@Test
109+
void errorResponseWithCustomErrorAttributesUsingDeprecatedApi() {
110+
ErrorAttributes attributes = new ErrorAttributes() {
111+
@Override
112+
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
113+
Map<String, Object> response = new HashMap<>();
114+
response.put("message", "An error occurred");
115+
return response;
116+
}
117+
118+
@Override
119+
public Throwable getError(WebRequest webRequest) {
120+
return null;
121+
}
122+
};
123+
ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(attributes, this.errorProperties);
124+
Map<String, Object> response = endpoint.invoke(new ServletWebRequest(new MockHttpServletRequest()));
125+
assertThat(response).hasSize(1);
126+
assertThat(response).containsEntry("message", "An error occurred");
127+
}
128+
129+
@Test
130+
void errorResponseWithDefaultErrorAttributesSubclassUsingDeprecatedApiAndDelegation() {
131+
ErrorAttributes attributes = new DefaultErrorAttributes() {
132+
@Override
133+
@SuppressWarnings("deprecation")
134+
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
135+
Map<String, Object> response = super.getErrorAttributes(webRequest, includeStackTrace);
136+
response.put("error", "custom error");
137+
response.put("custom", "value");
138+
response.remove("path");
139+
return response;
140+
}
141+
};
142+
ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(attributes, this.errorProperties);
143+
Map<String, Object> response = endpoint.invoke(new ServletWebRequest(new MockHttpServletRequest()));
144+
assertThat(response).containsEntry("error", "custom error");
145+
assertThat(response).containsEntry("custom", "value");
146+
assertThat(response).doesNotContainKey("path");
147+
assertThat(response).containsKey("timestamp");
148+
}
149+
150+
@Test
151+
void errorResponseWithDefaultErrorAttributesSubclassUsingDeprecatedApiWithoutDelegation() {
152+
ErrorAttributes attributes = new DefaultErrorAttributes() {
153+
@Override
154+
@SuppressWarnings("deprecation")
155+
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
156+
Map<String, Object> response = new HashMap<>();
157+
response.put("error", "custom error");
158+
return response;
159+
}
160+
};
161+
ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(attributes, this.errorProperties);
162+
Map<String, Object> response = endpoint.invoke(new ServletWebRequest(new MockHttpServletRequest()));
163+
assertThat(response).hasSize(1);
164+
assertThat(response).containsEntry("error", "custom error");
165+
}
166+
106167
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import org.springframework.beans.factory.InitializingBean;
3030
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
3131
import org.springframework.boot.autoconfigure.web.ResourceProperties;
32+
import org.springframework.boot.web.error.ErrorAttributeOptions;
33+
import org.springframework.boot.web.error.ErrorAttributeOptions.Include;
3234
import org.springframework.boot.web.reactive.error.ErrorAttributes;
3335
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
3436
import org.springframework.context.ApplicationContext;
@@ -134,26 +136,23 @@ public void setViewResolvers(List<ViewResolver> viewResolvers) {
134136
* @param includeStackTrace whether to include the error stacktrace information
135137
* @return the error attributes as a Map
136138
* @deprecated since 2.3.0 in favor of
137-
* {@link #getErrorAttributes(ServerRequest, boolean, boolean, boolean)}
139+
* {@link #getErrorAttributes(ServerRequest, ErrorAttributeOptions)}
138140
*/
139141
@Deprecated
140142
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
141-
return this.errorAttributes.getErrorAttributes(request, includeStackTrace, false, false);
143+
return getErrorAttributes(request,
144+
(includeStackTrace) ? ErrorAttributeOptions.of(Include.STACK_TRACE) : ErrorAttributeOptions.defaults());
142145
}
143146

144147
/**
145148
* Extract the error attributes from the current request, to be used to populate error
146149
* views or JSON payloads.
147150
* @param request the source request
148-
* @param includeStackTrace whether to include the stacktrace attribute
149-
* @param includeMessage whether to include the message attribute
150-
* @param includeBindingErrors whether to include the errors attribute
151+
* @param options options to control error attributes
151152
* @return the error attributes as a Map
152153
*/
153-
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace,
154-
boolean includeMessage, boolean includeBindingErrors) {
155-
return this.errorAttributes.getErrorAttributes(request, includeStackTrace, includeMessage,
156-
includeBindingErrors);
154+
protected Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
155+
return this.errorAttributes.getErrorAttributes(request, options);
157156
}
158157

159158
/**

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandler.java

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828

2929
import org.springframework.boot.autoconfigure.web.ErrorProperties;
3030
import org.springframework.boot.autoconfigure.web.ResourceProperties;
31+
import org.springframework.boot.web.error.ErrorAttributeOptions;
32+
import org.springframework.boot.web.error.ErrorAttributeOptions.Include;
3133
import org.springframework.boot.web.reactive.error.ErrorAttributes;
3234
import org.springframework.context.ApplicationContext;
3335
import org.springframework.http.HttpStatus;
@@ -113,11 +115,7 @@ protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes erro
113115
* @return a {@code Publisher} of the HTTP response
114116
*/
115117
protected Mono<ServerResponse> renderErrorView(ServerRequest request) {
116-
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.TEXT_HTML);
117-
boolean includeMessage = isIncludeMessage(request, MediaType.TEXT_HTML);
118-
boolean includeBindingErrors = isIncludeBindingErrors(request, MediaType.TEXT_HTML);
119-
Map<String, Object> error = getErrorAttributes(request, includeStackTrace, includeMessage,
120-
includeBindingErrors);
118+
Map<String, Object> error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML));
121119
int errorStatus = getHttpStatus(error);
122120
ServerResponse.BodyBuilder responseBody = ServerResponse.status(errorStatus).contentType(TEXT_HTML_UTF8);
123121
return Flux.just(getData(errorStatus).toArray(new String[] {}))
@@ -144,15 +142,28 @@ private List<String> getData(int errorStatus) {
144142
* @return a {@code Publisher} of the HTTP response
145143
*/
146144
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
147-
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.ALL);
148-
boolean includeMessage = isIncludeMessage(request, MediaType.ALL);
149-
boolean includeBindingErrors = isIncludeBindingErrors(request, MediaType.ALL);
150-
Map<String, Object> error = getErrorAttributes(request, includeStackTrace, includeMessage,
151-
includeBindingErrors);
145+
Map<String, Object> error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
152146
return ServerResponse.status(getHttpStatus(error)).contentType(MediaType.APPLICATION_JSON)
153147
.body(BodyInserters.fromValue(error));
154148
}
155149

150+
protected ErrorAttributeOptions getErrorAttributeOptions(ServerRequest request, MediaType mediaType) {
151+
ErrorAttributeOptions options = ErrorAttributeOptions.defaults();
152+
if (this.errorProperties.isIncludeException()) {
153+
options = options.including(Include.EXCEPTION);
154+
}
155+
if (isIncludeStackTrace(request, mediaType)) {
156+
options = options.including(Include.STACK_TRACE);
157+
}
158+
if (isIncludeMessage(request, mediaType)) {
159+
options = options.including(Include.MESSAGE);
160+
}
161+
if (isIncludeBindingErrors(request, mediaType)) {
162+
options = options.including(Include.BINDING_ERRORS);
163+
}
164+
return options;
165+
}
166+
156167
/**
157168
* Determine if the stacktrace attribute should be included.
158169
* @param request the source request

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/ErrorWebFluxAutoConfiguration.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -45,6 +45,7 @@
4545
* {@link org.springframework.web.server.WebExceptionHandler}.
4646
*
4747
* @author Brian Clozel
48+
* @author Scott Frederick
4849
* @since 2.0.0
4950
*/
5051
@Configuration(proxyBeanMethods = false)
@@ -77,7 +78,7 @@ public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAt
7778
@Bean
7879
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
7980
public DefaultErrorAttributes errorAttributes() {
80-
return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
81+
return new DefaultErrorAttributes();
8182
}
8283

8384
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/AbstractErrorController.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import javax.servlet.http.HttpServletRequest;
2525
import javax.servlet.http.HttpServletResponse;
2626

27+
import org.springframework.boot.web.error.ErrorAttributeOptions;
28+
import org.springframework.boot.web.error.ErrorAttributeOptions.Include;
2729
import org.springframework.boot.web.servlet.error.ErrorAttributes;
2830
import org.springframework.boot.web.servlet.error.ErrorController;
2931
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
@@ -74,18 +76,17 @@ private List<ErrorViewResolver> sortErrorViewResolvers(List<ErrorViewResolver> r
7476
* @param includeStackTrace if stack trace elements should be included
7577
* @return the error attributes
7678
* @deprecated since 2.3.0 in favor of
77-
* {@link #getErrorAttributes(HttpServletRequest, boolean, boolean, boolean)}
79+
* {@link #getErrorAttributes(HttpServletRequest, ErrorAttributeOptions)}
7880
*/
7981
@Deprecated
8082
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
81-
return getErrorAttributes(request, includeStackTrace, false, false);
83+
return getErrorAttributes(request,
84+
(includeStackTrace) ? ErrorAttributeOptions.of(Include.STACK_TRACE) : ErrorAttributeOptions.defaults());
8285
}
8386

84-
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace,
85-
boolean includeMessage, boolean includeBindingErrors) {
87+
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, ErrorAttributeOptions options) {
8688
WebRequest webRequest = new ServletWebRequest(request);
87-
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace, includeMessage,
88-
includeBindingErrors);
89+
return this.errorAttributes.getErrorAttributes(webRequest, options);
8990
}
9091

9192
protected boolean getTraceParameter(HttpServletRequest request) {

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorController.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import javax.servlet.http.HttpServletResponse;
2525

2626
import org.springframework.boot.autoconfigure.web.ErrorProperties;
27+
import org.springframework.boot.web.error.ErrorAttributeOptions;
28+
import org.springframework.boot.web.error.ErrorAttributeOptions.Include;
2729
import org.springframework.boot.web.servlet.error.ErrorAttributes;
2830
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory;
2931
import org.springframework.http.HttpStatus;
@@ -87,9 +89,8 @@ public String getErrorPath() {
8789
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
8890
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
8991
HttpStatus status = getStatus(request);
90-
Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(request,
91-
isIncludeStackTrace(request, MediaType.TEXT_HTML), isIncludeMessage(request, MediaType.TEXT_HTML),
92-
isIncludeBindingErrors(request, MediaType.TEXT_HTML)));
92+
Map<String, Object> model = Collections
93+
.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
9394
response.setStatus(status.value());
9495
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
9596
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
@@ -101,8 +102,7 @@ public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
101102
if (status == HttpStatus.NO_CONTENT) {
102103
return new ResponseEntity<>(status);
103104
}
104-
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL),
105-
isIncludeMessage(request, MediaType.ALL), isIncludeBindingErrors(request, MediaType.TEXT_HTML));
105+
Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
106106
return new ResponseEntity<>(body, status);
107107
}
108108

@@ -112,6 +112,23 @@ public ResponseEntity<String> mediaTypeNotAcceptable(HttpServletRequest request)
112112
return ResponseEntity.status(status).build();
113113
}
114114

115+
protected ErrorAttributeOptions getErrorAttributeOptions(HttpServletRequest request, MediaType mediaType) {
116+
ErrorAttributeOptions options = ErrorAttributeOptions.defaults();
117+
if (this.errorProperties.isIncludeException()) {
118+
options = options.including(Include.EXCEPTION);
119+
}
120+
if (isIncludeStackTrace(request, mediaType)) {
121+
options = options.including(Include.STACK_TRACE);
122+
}
123+
if (isIncludeMessage(request, mediaType)) {
124+
options = options.including(Include.MESSAGE);
125+
}
126+
if (isIncludeBindingErrors(request, mediaType)) {
127+
options = options.including(Include.BINDING_ERRORS);
128+
}
129+
return options;
130+
}
131+
115132
/**
116133
* Determine if the stacktrace attribute should be included.
117134
* @param request the source request

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -81,6 +81,7 @@
8181
* @author Andy Wilkinson
8282
* @author Stephane Nicoll
8383
* @author Brian Clozel
84+
* @author Scott Frederick
8485
* @since 1.0.0
8586
*/
8687
@Configuration(proxyBeanMethods = false)
@@ -100,7 +101,7 @@ public ErrorMvcAutoConfiguration(ServerProperties serverProperties) {
100101
@Bean
101102
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
102103
public DefaultErrorAttributes errorAttributes() {
103-
return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
104+
return new DefaultErrorAttributes();
104105
}
105106

106107
@Bean

0 commit comments

Comments
 (0)