From 45ee7913bff4543dd3b4d701d969743aee258b20 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Tue, 21 Jun 2022 11:55:21 +0100 Subject: [PATCH] Polishing ProblemDetail support See gh-28665 --- .../org/springframework/http/ProblemDetail.java | 13 ++++++++----- .../org/springframework/http/ResponseEntity.java | 9 +++++---- .../springframework/web/ErrorResponseException.java | 2 +- .../AbstractMessageWriterResultHandler.java | 2 +- .../annotation/ResponseEntityResultHandler.java | 2 +- .../AbstractMessageConverterMethodProcessor.java | 2 +- .../annotation/HttpEntityMethodProcessor.java | 2 +- 7 files changed, 18 insertions(+), 14 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/ProblemDetail.java b/spring-web/src/main/java/org/springframework/http/ProblemDetail.java index 4f83fb491d..102abfd81d 100644 --- a/spring-web/src/main/java/org/springframework/http/ProblemDetail.java +++ b/spring-web/src/main/java/org/springframework/http/ProblemDetail.java @@ -23,8 +23,11 @@ import org.springframework.util.Assert; /** * Representation of an RFC 7807 problem detail, including all RFC-defined - * fields. For an extended response with more fields, create a subclass that - * exposes the additional fields. + * properties. + * + *

For an extended response, create a subclass with additional properties. + * A subclass can use the {@link ProblemDetail#ProblemDetail(ProblemDetail)} + * copy constructor to extend an existing {@code ProblemDetail} instance. * * @author Rossen Stoyanchev * @since 6.0 @@ -63,8 +66,8 @@ public class ProblemDetail { } /** - * Copy constructor that could be used from a subclass to re-create a - * {@code ProblemDetail} in order to extend it with more fields. + * Copy constructor that a subclass can use to re-create and extend a + * {@code ProblemDetail} with additional properties. */ protected ProblemDetail(ProblemDetail other) { this.type = other.type; @@ -75,7 +78,7 @@ public class ProblemDetail { } /** - * For deserialization. + * No-arg constructor, for deserialization. */ protected ProblemDetail() { } diff --git a/spring-web/src/main/java/org/springframework/http/ResponseEntity.java b/spring-web/src/main/java/org/springframework/http/ResponseEntity.java index 95c1071b21..e9f4d87d69 100644 --- a/spring-web/src/main/java/org/springframework/http/ResponseEntity.java +++ b/spring-web/src/main/java/org/springframework/http/ResponseEntity.java @@ -264,10 +264,11 @@ public class ResponseEntity extends HttpEntity { /** * Create a builder for a {@code ResponseEntity} with the given - * {@link ProblemDetail} as the body, also matching to its - * {@link ProblemDetail#getStatus() status}. An {@code @ExceptionHandler} - * method can use to add response headers, or otherwise it can return - * {@code ProblemDetail}. + * {@link ProblemDetail} as the body, and its + * {@link ProblemDetail#getStatus() status} as the status. + *

Note that {@code ProblemDetail} is supported as a return value from + * controller methods and from {@code @ExceptionHandler} methods. The method + * here is convenient to also add response headers. * @param body the details for an HTTP error response * @return the created builder * @since 6.0 diff --git a/spring-web/src/main/java/org/springframework/web/ErrorResponseException.java b/spring-web/src/main/java/org/springframework/web/ErrorResponseException.java index ea0b78b7c7..e0ba18b8c9 100644 --- a/spring-web/src/main/java/org/springframework/web/ErrorResponseException.java +++ b/spring-web/src/main/java/org/springframework/web/ErrorResponseException.java @@ -32,7 +32,7 @@ import org.springframework.lang.Nullable; *

The exception can be used as is, or it can be extended as a more specific * exception that populates the {@link ProblemDetail#setType(URI) type} or * {@link ProblemDetail#setDetail(String) detail} fields, or potentially adds - * other non-standard fields. + * other non-standard properties. * * @author Rossen Stoyanchev * @since 6.0 diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java index 96cea2d582..cb270970a6 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java @@ -167,7 +167,7 @@ public abstract class AbstractMessageWriterResultHandler extends HandlerResultHa throw ex; } - // Fall back on RFC 7807 format for ProblemDetail + // For ProblemDetail, fall back on RFC 7807 format if (bestMediaType == null && elementType.toClass().equals(ProblemDetail.class)) { bestMediaType = selectMediaType(exchange, () -> getMediaTypesFor(elementType), this.problemMediaTypes); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java index 3c785ed74a..90f97cc970 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java @@ -144,7 +144,7 @@ public class ResponseEntityResultHandler extends AbstractMessageWriterResultHand httpEntity = new ResponseEntity<>(response.getBody(), response.getHeaders(), response.getStatusCode()); } else if (returnValue instanceof ProblemDetail detail) { - httpEntity = new ResponseEntity<>(returnValue, HttpHeaders.EMPTY, detail.getStatus()); + httpEntity = ResponseEntity.of(detail).build(); } else if (returnValue instanceof HttpHeaders) { httpEntity = new ResponseEntity<>((HttpHeaders) returnValue, HttpStatus.OK); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java index 57334617f1..d00b084c0d 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java @@ -243,7 +243,7 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe List compatibleMediaTypes = new ArrayList<>(); determineCompatibleMediaTypes(acceptableTypes, producibleTypes, compatibleMediaTypes); - // Fall back on RFC 7807 format for ProblemDetail + // For ProblemDetail, fall back on RFC 7807 format if (compatibleMediaTypes.isEmpty() && ProblemDetail.class.isAssignableFrom(valueType)) { determineCompatibleMediaTypes(this.problemMediaTypes, producibleTypes, compatibleMediaTypes); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java index 81d8bc24ea..dcd2b4df64 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java @@ -185,7 +185,7 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro httpEntity = new ResponseEntity<>(response.getBody(), response.getHeaders(), response.getStatusCode()); } else if (returnValue instanceof ProblemDetail detail) { - httpEntity = new ResponseEntity<>(returnValue, HttpHeaders.EMPTY, detail.getStatus()); + httpEntity = ResponseEntity.of(detail).build(); } else { Assert.isInstanceOf(HttpEntity.class, returnValue);