Support i8n of ProblemDetail "title" field
Closes gh-29407
This commit is contained in:
parent
506fbe5243
commit
e71057dca9
|
|
@ -95,8 +95,19 @@ public interface ErrorResponse {
|
|||
}
|
||||
|
||||
/**
|
||||
* Resolve the {@link #getDetailMessageCode() detailMessageCode} through the
|
||||
* given {@link MessageSource}, and if found, update the "detail" field.
|
||||
* Return a code to use to resolve the problem "detail" for this exception
|
||||
* through a {@link MessageSource}.
|
||||
* <p>By default this is initialized via
|
||||
* {@link #getDefaultDetailMessageCode(Class, String)}.
|
||||
*/
|
||||
default String getTitleCode() {
|
||||
return getDefaultTitleMessageCode(getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the {@link #getDetailMessageCode() detailMessageCode} and the
|
||||
* {@link #getTitleCode() titleCode} through the given {@link MessageSource},
|
||||
* and if found, update the "detail" and "title!" fields respectively.
|
||||
* @param messageSource the {@code MessageSource} to use for the lookup
|
||||
* @param locale the {@code Locale} to use for the lookup
|
||||
*/
|
||||
|
|
@ -107,22 +118,35 @@ public interface ErrorResponse {
|
|||
if (detail != null) {
|
||||
getBody().setDetail(detail);
|
||||
}
|
||||
String title = messageSource.getMessage(getTitleCode(), null, null, locale);
|
||||
if (title != null) {
|
||||
getBody().setTitle(title);
|
||||
}
|
||||
}
|
||||
return getBody();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build a message code for the given exception type, which consists of
|
||||
* {@code "problemDetail."} followed by the full {@link Class#getName() class name}.
|
||||
* @param exceptionType the exception type for which to build a code
|
||||
* Build a message code for the "detail" field, for the given exception type.
|
||||
* @param exceptionType the exception type associated with the problem
|
||||
* @param suffix an optional suffix, e.g. for exceptions that may have multiple
|
||||
* error message with different arguments.
|
||||
* @return {@code "problemDetail."} followed by the full {@link Class#getName() class name}
|
||||
*/
|
||||
static String getDefaultDetailMessageCode(Class<?> exceptionType, @Nullable String suffix) {
|
||||
return "problemDetail." + exceptionType.getName() + (suffix != null ? "." + suffix : "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a message code for the "title" field, for the given exception type.
|
||||
* @param exceptionType the exception type associated with the problem
|
||||
* @return {@code "problemDetail.title."} followed by the full {@link Class#getName() class name}
|
||||
*/
|
||||
static String getDefaultTitleMessageCode(Class<?> exceptionType) {
|
||||
return "problemDetail.title." + exceptionType.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the given Exception to an {@link ErrorResponse}.
|
||||
* @param ex the Exception, mostly to derive message codes, if not provided
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import org.springframework.http.ProblemDetail;
|
|||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.validation.BeanPropertyBindingResult;
|
||||
import org.springframework.web.ErrorResponse;
|
||||
import org.springframework.web.ErrorResponseException;
|
||||
import org.springframework.web.bind.support.WebExchangeBindException;
|
||||
import org.springframework.web.server.MethodNotAllowedException;
|
||||
|
|
@ -131,8 +132,11 @@ public class ResponseEntityExceptionHandlerTests {
|
|||
|
||||
StaticMessageSource messageSource = new StaticMessageSource();
|
||||
messageSource.addMessage(
|
||||
"problemDetail." + UnsupportedMediaTypeStatusException.class.getName(), locale,
|
||||
ErrorResponse.getDefaultDetailMessageCode(UnsupportedMediaTypeStatusException.class, null), locale,
|
||||
"Content-Type {0} not supported. Supported: {1}");
|
||||
messageSource.addMessage(
|
||||
ErrorResponse.getDefaultTitleMessageCode(UnsupportedMediaTypeStatusException.class), locale,
|
||||
"Media type is not valid or not supported");
|
||||
|
||||
this.exceptionHandler.setMessageSource(messageSource);
|
||||
|
||||
|
|
@ -147,6 +151,8 @@ public class ResponseEntityExceptionHandlerTests {
|
|||
ProblemDetail body = (ProblemDetail) responseEntity.getBody();
|
||||
assertThat(body.getDetail()).isEqualTo(
|
||||
"Content-Type application/json not supported. Supported: [application/atom+xml, application/xml]");
|
||||
assertThat(body.getTitle()).isEqualTo(
|
||||
"Media type is not valid or not supported");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import org.springframework.http.converter.HttpMessageNotWritableException;
|
|||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.MapBindingResult;
|
||||
import org.springframework.web.ErrorResponse;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
|
|
@ -166,8 +167,11 @@ public class ResponseEntityExceptionHandlerTests {
|
|||
try {
|
||||
StaticMessageSource messageSource = new StaticMessageSource();
|
||||
messageSource.addMessage(
|
||||
"problemDetail." + HttpMediaTypeNotSupportedException.class.getName(), locale,
|
||||
ErrorResponse.getDefaultDetailMessageCode(HttpMediaTypeNotSupportedException.class, null), locale,
|
||||
"Content-Type {0} not supported. Supported: {1}");
|
||||
messageSource.addMessage(
|
||||
ErrorResponse.getDefaultTitleMessageCode(HttpMediaTypeNotSupportedException.class), locale,
|
||||
"Media type is not valid or not supported");
|
||||
|
||||
this.exceptionHandler.setMessageSource(messageSource);
|
||||
|
||||
|
|
@ -177,6 +181,8 @@ public class ResponseEntityExceptionHandlerTests {
|
|||
ProblemDetail body = (ProblemDetail) entity.getBody();
|
||||
assertThat(body.getDetail()).isEqualTo(
|
||||
"Content-Type application/json not supported. Supported: [application/atom+xml, application/xml]");
|
||||
assertThat(body.getTitle()).isEqualTo(
|
||||
"Media type is not valid or not supported");
|
||||
}
|
||||
finally {
|
||||
LocaleContextHolder.resetLocaleContext();
|
||||
|
|
@ -201,7 +207,7 @@ public class ResponseEntityExceptionHandlerTests {
|
|||
try {
|
||||
StaticMessageSource messageSource = new StaticMessageSource();
|
||||
messageSource.addMessage(
|
||||
"problemDetail." + TypeMismatchException.class.getName(), locale,
|
||||
ErrorResponse.getDefaultDetailMessageCode(TypeMismatchException.class, null), locale,
|
||||
"Failed to set {0} to value: {1}");
|
||||
|
||||
this.exceptionHandler.setMessageSource(messageSource);
|
||||
|
|
|
|||
|
|
@ -3614,7 +3614,7 @@ and any `ErrorResponseException`, and renders an error response with a body.
|
|||
[.small]#<<webmvc.adoc#mvc-ann-rest-exceptions-render, Web MVC>>#
|
||||
|
||||
You can return `ProblemDetail` or `ErrorResponse` from any `@ExceptionHandler` or from
|
||||
any `@RequestMapping` method to render an RFC 7807 response as follows:
|
||||
any `@RequestMapping` method to render an RFC 7807 response. This is processed as follows:
|
||||
|
||||
- The `status` property of `ProblemDetail` determines the HTTP status.
|
||||
- The `instance` property of `ProblemDetail` is set from the current URL path, if not
|
||||
|
|
@ -3626,8 +3626,9 @@ and also falls back on it if no compatible media type is found.
|
|||
To enable RFC 7807 responses for Spring WebFlux exceptions and for any
|
||||
`ErrorResponseException`, extend `ResponseEntityExceptionHandler` and declare it as an
|
||||
<<webflux-ann-controller-advice,@ControllerAdvice>> in Spring configuration. The handler
|
||||
obtains HTTP status, headers, and error details from each exception and prepares a
|
||||
`ResponseEntity`.
|
||||
has an `@ExceptionHandler` method that handles any `ErrorResponse` exception, which
|
||||
includes all built-in web exceptions. You can add more exception handling methods, and
|
||||
use a protected method to map any exception to a `ProblemDetail`.
|
||||
|
||||
|
||||
|
||||
|
|
@ -3644,7 +3645,7 @@ response, and likewise any unknown property during deserialization is inserted i
|
|||
this `Map`.
|
||||
|
||||
You can also extend `ProblemDetail` to add dedicated non-standard properties.
|
||||
The copy constructor in `ProblemDetail` allows a sub-class to make it easy to be created
|
||||
The copy constructor in `ProblemDetail` allows a subclass to make it easy to be created
|
||||
from an existing `ProblemDetail`. This could be done centrally, e.g. from an
|
||||
`@ControllerAdvice` such as `ResponseEntityExceptionHandler` that re-creates the
|
||||
`ProblemDetail` of an exception into a subclass with the additional non-standard fields.
|
||||
|
|
@ -3658,17 +3659,18 @@ from an existing `ProblemDetail`. This could be done centrally, e.g. from an
|
|||
It is a common requirement to internationalize error response details, and good practice
|
||||
to customize the problem details for Spring WebFlux exceptions. This is supported as follows:
|
||||
|
||||
- Each `ErrorResponse` exposes a message code and message code arguments to resolve the
|
||||
problem "detail" field through a <<core.adoc#context-functionality-messagesource,MessageSource>>.
|
||||
- Each `ErrorResponse` exposes a message code and arguments to resolve the "detail" field
|
||||
through a <<core.adoc#context-functionality-messagesource,MessageSource>>.
|
||||
The actual message code value is parameterized with placeholders, e.g.
|
||||
`"HTTP method {0} not supported"` to be expanded from the arguments.
|
||||
- `ResponseEntityExceptionHandler` uses the message code and the message arguments
|
||||
to resolve the problem "detail" field.
|
||||
- Each `ErrorResponse` also exposes a message code to resolve the "title" field.
|
||||
- `ResponseEntityExceptionHandler` uses the message code and arguments to resolve the
|
||||
"detail" and the "title" fields.
|
||||
|
||||
Message codes default to "problemDetail." + the fully qualified exception class name. Some
|
||||
exceptions may expose additional message codes in which case a suffix is added to
|
||||
the default message code. The table below lists message arguments and codes for Spring
|
||||
WebFlux exceptions:
|
||||
By default, the message code for the "detail" field is "problemDetail." + the fully
|
||||
qualified exception class name. Some exceptions may expose additional message codes in
|
||||
which case a suffix is added to the default message code. The table below lists message
|
||||
arguments and codes for Spring WebFlux exceptions:
|
||||
|
||||
[[webflux-ann-rest-exceptions-codes]]
|
||||
[cols="1,1,2", options="header"]
|
||||
|
|
@ -3715,6 +3717,10 @@ via `MessageSource`.
|
|||
|
||||
|===
|
||||
|
||||
By default, the message code for the "title" field is "problemDetail.title." + the fully
|
||||
qualified exception class name.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-ann-rest-exceptions-client]]
|
||||
|
|
|
|||
|
|
@ -4910,7 +4910,7 @@ and any `ErrorResponseException`, and renders an error response with a body.
|
|||
[.small]#<<web-reactive.adoc#webflux-ann-rest-exceptions-render, WebFlux>>#
|
||||
|
||||
You can return `ProblemDetail` or `ErrorResponse` from any `@ExceptionHandler` or from
|
||||
any `@RequestMapping` method to render an RFC 7807 response as follows:
|
||||
any `@RequestMapping` method to render an RFC 7807 response. This is processed as follows:
|
||||
|
||||
- The `status` property of `ProblemDetail` determines the HTTP status.
|
||||
- The `instance` property of `ProblemDetail` is set from the current URL path, if not
|
||||
|
|
@ -4919,11 +4919,12 @@ already set.
|
|||
"application/problem+json" over "application/json" when rendering a `ProblemDetail`,
|
||||
and also falls back on it if no compatible media type is found.
|
||||
|
||||
To enable RFC 7807 responses for Spring MVC exceptions and for any
|
||||
To enable RFC 7807 responses for Spring WebFlux exceptions and for any
|
||||
`ErrorResponseException`, extend `ResponseEntityExceptionHandler` and declare it as an
|
||||
<<mvc-ann-controller-advice,@ControllerAdvice>> in Spring configuration. The handler
|
||||
obtains HTTP status, headers, and error details from each exception and prepares a
|
||||
`ResponseEntity`.
|
||||
has an `@ExceptionHandler` method that handles any `ErrorResponse` exception, which
|
||||
includes all built-in web exceptions. You can add more exception handling methods, and
|
||||
use a protected method to map any exception to a `ProblemDetail`.
|
||||
|
||||
|
||||
|
||||
|
|
@ -4940,7 +4941,7 @@ response, and likewise any unknown property during deserialization is inserted i
|
|||
this `Map`.
|
||||
|
||||
You can also extend `ProblemDetail` to add dedicated non-standard properties.
|
||||
The copy constructor in `ProblemDetail` allows a sub-class to make it easy to be created
|
||||
The copy constructor in `ProblemDetail` allows a subclass to make it easy to be created
|
||||
from an existing `ProblemDetail`. This could be done centrally, e.g. from an
|
||||
`@ControllerAdvice` such as `ResponseEntityExceptionHandler` that re-creates the
|
||||
`ProblemDetail` of an exception into a subclass with the additional non-standard fields.
|
||||
|
|
@ -4954,20 +4955,18 @@ from an existing `ProblemDetail`. This could be done centrally, e.g. from an
|
|||
It is a common requirement to internationalize error response details, and good practice
|
||||
to customize the problem details for Spring MVC exceptions. This is supported as follows:
|
||||
|
||||
- Each `ErrorResponse` exposes a message code and message code arguments to resolve the
|
||||
problem "detail" field through a <<core.adoc#context-functionality-messagesource,MessageSource>>.
|
||||
- Each `ErrorResponse` exposes a message code and arguments to resolve the "detail" field
|
||||
through a <<core.adoc#context-functionality-messagesource,MessageSource>>.
|
||||
The actual message code value is parameterized with placeholders, e.g.
|
||||
`"HTTP method {0} not supported"` to be expanded from the arguments.
|
||||
- `ResponseEntityExceptionHandler` uses the message code and the message arguments
|
||||
to resolve the problem "detail" field.
|
||||
- Lower level exceptions that cannot implement `ErrorResponse`, e.g. `TypeMismatchException`,
|
||||
have their problem detail, including message code and arguments set in
|
||||
`ResponseEntityExceptionHandler`.
|
||||
- Each `ErrorResponse` also exposes a message code to resolve the "title" field.
|
||||
- `ResponseEntityExceptionHandler` uses the message code and arguments to resolve the
|
||||
"detail" and the "title" fields.
|
||||
|
||||
Message codes default to "problemDetail." + the fully qualified exception class name. Some
|
||||
exceptions may expose additional message codes in which case a suffix is added to
|
||||
the default message code. The table below lists message arguments and codes for Spring
|
||||
MVC exceptions:
|
||||
By default, the message code for the "detail" field is "problemDetail." + the fully
|
||||
qualified exception class name. Some exceptions may expose additional message codes in
|
||||
which case a suffix is added to the default message code. The table below lists message
|
||||
arguments and codes for Spring MVC exceptions:
|
||||
|
||||
[[mvc-ann-rest-exceptions-codes]]
|
||||
[cols="1,1,2", options="header"]
|
||||
|
|
@ -5054,6 +5053,9 @@ MVC exceptions:
|
|||
|
||||
|===
|
||||
|
||||
By default, the message code for the "title" field is "problemDetail.title." + the fully
|
||||
qualified exception class name.
|
||||
|
||||
|
||||
|
||||
[[mvc-ann-rest-exceptions-client]]
|
||||
|
|
|
|||
Loading…
Reference in New Issue