Add ErrorResponse and ErrorResponseException
ErrorResponse represents a complete error response with status, headers, and an RFC 7807 ProblemDetail body. ErrorResponseException implements ErrorResponse and is usable on its own or as a base class. ResponseStatusException extends ErrorResponseException and now also supports RFC 7807 and so does its sub-hierarchy. ErrorResponse can be returned from `@ExceptionHandler` methods and is mapped to ResponseEntity. See gh-27052
This commit is contained in:
@@ -37,6 +37,7 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.ErrorResponse;
|
||||
import org.springframework.web.reactive.HandlerResult;
|
||||
import org.springframework.web.reactive.HandlerResultHandler;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
@@ -44,7 +45,7 @@ import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* Handles return values of type {@link HttpEntity}, {@link ResponseEntity},
|
||||
* {@link HttpHeaders}, and {@link ProblemDetail}.
|
||||
* {@link HttpHeaders}, {@link ErrorResponse}, and {@link ProblemDetail}.
|
||||
*
|
||||
* <p>By default the order for this result handler is set to 0. It is generally
|
||||
* safe to place it early in the order as it looks for a concrete return type.
|
||||
@@ -108,7 +109,8 @@ public class ResponseEntityResultHandler extends AbstractMessageWriterResultHand
|
||||
return false;
|
||||
}
|
||||
return ((HttpEntity.class.isAssignableFrom(type) && !RequestEntity.class.isAssignableFrom(type)) ||
|
||||
HttpHeaders.class.isAssignableFrom(type) || ProblemDetail.class.isAssignableFrom(type));
|
||||
ErrorResponse.class.isAssignableFrom(type) || ProblemDetail.class.isAssignableFrom(type) ||
|
||||
HttpHeaders.class.isAssignableFrom(type));
|
||||
}
|
||||
|
||||
|
||||
@@ -138,12 +140,15 @@ public class ResponseEntityResultHandler extends AbstractMessageWriterResultHand
|
||||
if (returnValue instanceof HttpEntity) {
|
||||
httpEntity = (HttpEntity<?>) returnValue;
|
||||
}
|
||||
else if (returnValue instanceof HttpHeaders) {
|
||||
httpEntity = new ResponseEntity<>((HttpHeaders) returnValue, HttpStatus.OK);
|
||||
else if (returnValue instanceof ErrorResponse response) {
|
||||
httpEntity = new ResponseEntity<>(response.getBody(), response.getHeaders(), response.getRawStatusCode());
|
||||
}
|
||||
else if (returnValue instanceof ProblemDetail detail) {
|
||||
httpEntity = new ResponseEntity<>(returnValue, HttpHeaders.EMPTY, detail.getStatus());
|
||||
}
|
||||
else if (returnValue instanceof HttpHeaders) {
|
||||
httpEntity = new ResponseEntity<>((HttpHeaders) returnValue, HttpStatus.OK);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException(
|
||||
"HttpEntity or HttpHeaders expected but got: " + returnValue.getClass());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -330,7 +330,7 @@ public class RequestMappingInfoHandlerMappingTests {
|
||||
UnsupportedMediaTypeStatusException umtse = (UnsupportedMediaTypeStatusException) ex;
|
||||
MediaType mediaType = new MediaType("foo", "bar");
|
||||
assertThat(umtse.getSupportedMediaTypes()).containsExactly(mediaType);
|
||||
assertThat(umtse.getResponseHeaders().getAcceptPatch()).containsExactly(mediaType);
|
||||
assertThat(umtse.getHeaders().getAcceptPatch()).containsExactly(mediaType);
|
||||
})
|
||||
.verify();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -55,6 +55,8 @@ import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.ErrorResponse;
|
||||
import org.springframework.web.ErrorResponseException;
|
||||
import org.springframework.web.reactive.HandlerResult;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
|
||||
@@ -130,6 +132,9 @@ public class ResponseEntityResultHandlerTests {
|
||||
returnType = on(TestController.class).resolveReturnType(HttpHeaders.class);
|
||||
assertThat(this.resultHandler.supports(handlerResult(value, returnType))).isTrue();
|
||||
|
||||
returnType = on(TestController.class).resolveReturnType(ErrorResponse.class);
|
||||
assertThat(this.resultHandler.supports(handlerResult(value, returnType))).isTrue();
|
||||
|
||||
returnType = on(TestController.class).resolveReturnType(ProblemDetail.class);
|
||||
assertThat(this.resultHandler.supports(handlerResult(value, returnType))).isTrue();
|
||||
|
||||
@@ -236,6 +241,28 @@ public class ResponseEntityResultHandlerTests {
|
||||
testHandle(returnValue, returnType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleErrorResponse() {
|
||||
ErrorResponseException ex = new ErrorResponseException(HttpStatus.BAD_REQUEST);
|
||||
ex.getHeaders().add("foo", "bar");
|
||||
MethodParameter returnType = on(TestController.class).resolveReturnType(ErrorResponse.class);
|
||||
HandlerResult result = handlerResult(ex, returnType);
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(get("/path"));
|
||||
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_PROBLEM_JSON);
|
||||
this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5));
|
||||
|
||||
assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
assertThat(exchange.getResponse().getHeaders()).hasSize(3);
|
||||
assertThat(exchange.getResponse().getHeaders().get("foo")).containsExactly("bar");
|
||||
assertThat(exchange.getResponse().getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON);
|
||||
assertResponseBody(exchange,
|
||||
"{\"type\":\"about:blank\"," +
|
||||
"\"title\":\"Bad Request\"," +
|
||||
"\"status\":400," +
|
||||
"\"detail\":null," +
|
||||
"\"instance\":\"/path\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleProblemDetail() {
|
||||
ProblemDetail problemDetail = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
|
||||
@@ -529,6 +556,8 @@ public class ResponseEntityResultHandlerTests {
|
||||
|
||||
ResponseEntity<Person> responseEntityPerson() { return null; }
|
||||
|
||||
ErrorResponse errorResponse() { return null; }
|
||||
|
||||
ProblemDetail problemDetail() { return null; }
|
||||
|
||||
HttpHeaders httpHeaders() { return null; }
|
||||
|
||||
Reference in New Issue
Block a user