Merge branch '3.2.x'

Closes gh-41141
This commit is contained in:
Phillip Webb
2024-06-17 16:51:53 -07:00
9 changed files with 199 additions and 59 deletions

View File

@@ -20,6 +20,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
/**
@@ -79,6 +80,19 @@ public final class ErrorAttributeOptions {
return new ErrorAttributeOptions(Collections.unmodifiableSet(updated));
}
/**
* Remove elements from the given map if they are not included in this set of options.
* @param map the map to update
* @since 3.2.7
*/
public void retainIncluded(Map<String, Object> map) {
for (Include candidate : Include.values()) {
if (!this.includes.contains(candidate)) {
map.remove(candidate.key);
}
}
}
private EnumSet<Include> copyIncludes() {
return (this.includes.isEmpty()) ? EnumSet.noneOf(Include.class) : EnumSet.copyOf(this.includes);
}
@@ -88,7 +102,7 @@ public final class ErrorAttributeOptions {
* @return an {@code ErrorAttributeOptions}
*/
public static ErrorAttributeOptions defaults() {
return of(Include.PATH);
return of(Include.PATH, Include.STATUS, Include.ERROR);
}
/**
@@ -120,28 +134,46 @@ public final class ErrorAttributeOptions {
/**
* Include the exception class name attribute.
*/
EXCEPTION,
EXCEPTION("exception"),
/**
* Include the stack trace attribute.
*/
STACK_TRACE,
STACK_TRACE("trace"),
/**
* Include the message attribute.
*/
MESSAGE,
MESSAGE("message"),
/**
* Include the binding errors attribute.
*/
BINDING_ERRORS,
BINDING_ERRORS("errors"),
/**
* Include the HTTP status code.
* @since 3.2.7
*/
STATUS("status"),
/**
* Include the HTTP status code.
* @since 3.2.7
*/
ERROR("error"),
/**
* Include the request path.
* @since 3.3.0
*/
PATH
PATH("path");
private final String key;
Include(String key) {
this.key = key;
}
}

View File

@@ -71,21 +71,7 @@ public class DefaultErrorAttributes implements ErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = getErrorAttributes(request, options.isIncluded(Include.STACK_TRACE));
if (!options.isIncluded(Include.EXCEPTION)) {
errorAttributes.remove("exception");
}
if (!options.isIncluded(Include.STACK_TRACE)) {
errorAttributes.remove("trace");
}
if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get("message") != null) {
errorAttributes.remove("message");
}
if (!options.isIncluded(Include.BINDING_ERRORS)) {
errorAttributes.remove("errors");
}
if (!options.isIncluded(Include.PATH)) {
errorAttributes.remove("path");
}
options.retainIncluded(errorAttributes);
return errorAttributes;
}

View File

@@ -92,21 +92,7 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE));
if (!options.isIncluded(Include.EXCEPTION)) {
errorAttributes.remove("exception");
}
if (!options.isIncluded(Include.STACK_TRACE)) {
errorAttributes.remove("trace");
}
if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get("message") != null) {
errorAttributes.remove("message");
}
if (!options.isIncluded(Include.BINDING_ERRORS)) {
errorAttributes.remove("errors");
}
if (!options.isIncluded(Include.PATH)) {
errorAttributes.remove("path");
}
options.retainIncluded(errorAttributes);
return errorAttributes;
}

View File

@@ -107,7 +107,7 @@ class DefaultErrorAttributesTests {
Exception error = new CustomException("Test Message");
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, error),
ErrorAttributeOptions.of(Include.MESSAGE));
ErrorAttributeOptions.of(Include.MESSAGE, Include.STATUS, Include.ERROR));
assertThat(attributes).containsEntry("error", HttpStatus.I_AM_A_TEAPOT.getReasonPhrase());
assertThat(attributes).containsEntry("message", "Test Message");
assertThat(attributes).containsEntry("status", HttpStatus.I_AM_A_TEAPOT.value());
@@ -118,7 +118,7 @@ class DefaultErrorAttributesTests {
Exception error = new Custom2Exception();
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, error),
ErrorAttributeOptions.of(Include.MESSAGE));
ErrorAttributeOptions.of(Include.MESSAGE, Include.STATUS, Include.ERROR));
assertThat(attributes).containsEntry("error", HttpStatus.I_AM_A_TEAPOT.getReasonPhrase());
assertThat(attributes).containsEntry("status", HttpStatus.I_AM_A_TEAPOT.value());
assertThat(attributes).containsEntry("message", "Nope!");
@@ -177,7 +177,7 @@ class DefaultErrorAttributesTests {
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
ServerRequest serverRequest = buildServerRequest(request, error);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest,
ErrorAttributeOptions.of(Include.EXCEPTION, Include.MESSAGE));
ErrorAttributeOptions.of(Include.EXCEPTION, Include.MESSAGE, Include.STATUS));
assertThat(attributes).containsEntry("status", 400);
assertThat(attributes).containsEntry("message", "invalid request");
assertThat(attributes).containsEntry("exception", RuntimeException.class.getName());
@@ -192,7 +192,7 @@ class DefaultErrorAttributesTests {
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
ServerRequest serverRequest = buildServerRequest(request, error);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest,
ErrorAttributeOptions.of(Include.EXCEPTION, Include.MESSAGE));
ErrorAttributeOptions.of(Include.EXCEPTION, Include.MESSAGE, Include.STATUS));
assertThat(attributes).containsEntry("status", 406);
assertThat(attributes).containsEntry("message", "could not process request");
assertThat(attributes).containsEntry("exception", ResponseStatusException.class.getName());
@@ -308,6 +308,30 @@ class DefaultErrorAttributesTests {
assertThat(attributes).doesNotContainKey("errors");
}
@Test
void excludeStatus() {
ResponseStatusException error = new ResponseStatusException(HttpStatus.NOT_ACCEPTABLE,
"could not process request");
this.errorAttributes = new DefaultErrorAttributes();
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
ServerRequest serverRequest = buildServerRequest(request, error);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest,
ErrorAttributeOptions.defaults().excluding(Include.STATUS));
assertThat(attributes).doesNotContainKey("status");
}
@Test
void excludeError() {
ResponseStatusException error = new ResponseStatusException(HttpStatus.NOT_ACCEPTABLE,
"could not process request");
this.errorAttributes = new DefaultErrorAttributes();
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
ServerRequest serverRequest = buildServerRequest(request, error);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest,
ErrorAttributeOptions.defaults().excluding(Include.ERROR));
assertThat(attributes).doesNotContainKey("error");
}
private ServerRequest buildServerRequest(MockServerHttpRequest request, Throwable error) {
ServerWebExchange exchange = MockServerWebExchange.from(request);
this.errorAttributes.storeErrorInformation(error, exchange);

View File

@@ -311,4 +311,20 @@ class DefaultErrorAttributesTests {
assertThat(attributes).containsEntry("message", "custom message");
}
@Test
void excludeStatus() {
this.request.setAttribute("jakarta.servlet.error.status_code", 404);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest,
ErrorAttributeOptions.defaults().excluding(Include.STATUS));
assertThat(attributes).doesNotContainKey("status");
}
@Test
void excludeError() {
this.request.setAttribute("jakarta.servlet.error.status_code", 404);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest,
ErrorAttributeOptions.defaults().excluding(Include.ERROR));
assertThat(attributes).doesNotContainKey("error");
}
}