From 61eaa9333b2d561aa6131fbf502542e5fa175f80 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Wed, 14 Jun 2023 16:13:21 +0100 Subject: [PATCH] Polishing in ErrorResponse See gh-30566 --- .../springframework/web/ErrorResponse.java | 94 ++++++++++--------- .../ResponseEntityExceptionHandlerTests.java | 30 +++--- .../ResponseEntityExceptionHandlerTests.java | 38 ++++---- 3 files changed, 81 insertions(+), 81 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/ErrorResponse.java b/spring-web/src/main/java/org/springframework/web/ErrorResponse.java index 7ea1f06e459..27123f1bb19 100644 --- a/spring-web/src/main/java/org/springframework/web/ErrorResponse.java +++ b/spring-web/src/main/java/org/springframework/web/ErrorResponse.java @@ -64,6 +64,9 @@ public interface ErrorResponse { */ ProblemDetail getBody(); + + // MessageSource codes and arguments + /** * Return a code to use to resolve the problem "type" for this exception * through a {@link MessageSource}. The type resolved through the @@ -76,6 +79,15 @@ public interface ErrorResponse { return getDefaultTypeMessageCode(getClass()); } + /** + * Return a code to use to resolve the problem "title" for this exception + * through a {@link MessageSource}. + *

By default this is initialized via {@link #getDefaultTitleMessageCode(Class)}. + */ + default String getTitleMessageCode() { + return getDefaultTitleMessageCode(getClass()); + } + /** * Return a code to use to resolve the problem "detail" for this exception * through a {@link MessageSource}. @@ -111,15 +123,6 @@ public interface ErrorResponse { return getDetailMessageArguments(); } - /** - * Return a code to use to resolve the problem "title" for this exception - * through a {@link MessageSource}. - *

By default this is initialized via {@link #getDefaultTitleMessageCode(Class)}. - */ - default String getTitleMessageCode() { - return getDefaultTitleMessageCode(getClass()); - } - /** * Use the given {@link MessageSource} to resolve the * {@link #getTypeMessageCode() type}, {@link #getTitleMessageCode() title}, @@ -159,6 +162,16 @@ public interface ErrorResponse { return "problemDetail.type." + exceptionType.getName(); } + /** + * 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 fully qualified + * {@link Class#getName() class name} + */ + static String getDefaultTitleMessageCode(Class exceptionType) { + return "problemDetail.title." + exceptionType.getName(); + } + /** * Build a message code for the "detail" field, for the given exception type. * @param exceptionType the exception type associated with the problem @@ -171,15 +184,6 @@ public interface ErrorResponse { 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 fully qualified - * {@link Class#getName() class name} - */ - static String getDefaultTitleMessageCode(Class exceptionType) { - return "problemDetail.title." + exceptionType.getName(); - } /** * Static factory method to build an instance via @@ -229,33 +233,6 @@ public interface ErrorResponse { */ Builder headers(Consumer headersConsumer); - /** - * Set the underlying {@link ProblemDetail#setDetail(String) detail}. - * @return the same builder instance - */ - Builder detail(String detail); - - /** - * Customize the {@link MessageSource} code for looking up the value for - * the underlying {@link #detail(String) detail}. - *

By default, this is set to - * {@link ErrorResponse#getDefaultDetailMessageCode(Class, String)} with the - * associated Exception type. - * @param messageCode the message code to use - * @return the same builder instance - * @see ErrorResponse#getDetailMessageCode() - */ - Builder detailMessageCode(String messageCode); - - /** - * Set the arguments to provide to the {@link MessageSource} lookup for - * {@link #detailMessageCode(String)}. - * @param messageArguments the arguments to provide - * @return the same builder instance - * @see ErrorResponse#getDetailMessageArguments() - */ - Builder detailMessageArguments(Object... messageArguments); - /** * Set the underlying {@link ProblemDetail#setType(URI) type} field. * @return the same builder instance @@ -286,6 +263,33 @@ public interface ErrorResponse { */ Builder instance(@Nullable URI instance); + /** + * Set the underlying {@link ProblemDetail#setDetail(String) detail}. + * @return the same builder instance + */ + Builder detail(String detail); + + /** + * Customize the {@link MessageSource} code for looking up the value for + * the underlying {@link #detail(String) detail}. + *

By default, this is set to + * {@link ErrorResponse#getDefaultDetailMessageCode(Class, String)} with the + * associated Exception type. + * @param messageCode the message code to use + * @return the same builder instance + * @see ErrorResponse#getDetailMessageCode() + */ + Builder detailMessageCode(String messageCode); + + /** + * Set the arguments to provide to the {@link MessageSource} lookup for + * {@link #detailMessageCode(String)}. + * @param messageArguments the arguments to provide + * @return the same builder instance + * @see ErrorResponse#getDetailMessageArguments() + */ + Builder detailMessageArguments(Object... messageArguments); + /** * Set a "dynamic" {@link ProblemDetail#setProperty(String, Object) * property} on the underlying {@code ProblemDetail}. diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityExceptionHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityExceptionHandlerTests.java index bb5ef4fb962..c098c2f088c 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityExceptionHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityExceptionHandlerTests.java @@ -127,37 +127,35 @@ public class ResponseEntityExceptionHandlerTests { @Test void errorResponseProblemDetailViaMessageSource() { - Locale locale = Locale.UK; - LocaleContextHolder.setLocale(locale); String type = "https://example.com/probs/unsupported-content"; String title = "Media type is not valid or not supported"; + Class exceptionType = UnsupportedMediaTypeStatusException.class; - StaticMessageSource messageSource = new StaticMessageSource(); - messageSource.addMessage( - ErrorResponse.getDefaultDetailMessageCode(UnsupportedMediaTypeStatusException.class, null), locale, + StaticMessageSource source = new StaticMessageSource(); + source.addMessage(ErrorResponse.getDefaultTypeMessageCode(exceptionType), locale, type); + source.addMessage(ErrorResponse.getDefaultTitleMessageCode(exceptionType), locale, title); + source.addMessage(ErrorResponse.getDefaultDetailMessageCode(exceptionType, null), locale, "Content-Type {0} not supported. Supported: {1}"); - messageSource.addMessage( - ErrorResponse.getDefaultTitleMessageCode(UnsupportedMediaTypeStatusException.class), locale, title); - messageSource.addMessage( - ErrorResponse.getDefaultTypeMessageCode(UnsupportedMediaTypeStatusException.class), locale, type); - this.exceptionHandler.setMessageSource(messageSource); + this.exceptionHandler.setMessageSource(source); - Exception ex = new UnsupportedMediaTypeStatusException(MediaType.APPLICATION_JSON, - List.of(MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_XML)); + Exception ex = new UnsupportedMediaTypeStatusException( + MediaType.APPLICATION_JSON, List.of(MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_XML)); MockServerWebExchange exchange = MockServerWebExchange.from( MockServerHttpRequest.get("/").acceptLanguageAsLocales(locale).build()); ResponseEntity responseEntity = this.exceptionHandler.handleException(ex, exchange).block(); + assertThat(responseEntity).isNotNull(); - ProblemDetail body = (ProblemDetail) responseEntity.getBody(); - assertThat(body.getDetail()).isEqualTo( + ProblemDetail problem = (ProblemDetail) responseEntity.getBody(); + assertThat(problem).isNotNull(); + assertThat(problem.getType()).isEqualTo(URI.create(type)); + assertThat(problem.getTitle()).isEqualTo(title); + assertThat(problem.getDetail()).isEqualTo( "Content-Type application/json not supported. Supported: [application/atom+xml, application/xml]"); - assertThat(body.getTitle()).isEqualTo(title); - assertThat(body.getType()).isEqualTo(URI.create(type)); } @Test diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java index daaf53e6336..ea8beabd82a 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java @@ -161,33 +161,31 @@ public class ResponseEntityExceptionHandlerTests { @Test public void errorResponseProblemDetailViaMessageSource() { - - Locale locale = Locale.UK; - LocaleContextHolder.setLocale(locale); - - String type = "https://example.com/probs/unsupported-content"; - String title = "Media type is not valid or not supported"; - try { - StaticMessageSource messageSource = new StaticMessageSource(); - messageSource.addMessage( - ErrorResponse.getDefaultDetailMessageCode(HttpMediaTypeNotSupportedException.class, null), locale, - "Content-Type {0} not supported. Supported: {1}"); - messageSource.addMessage( - ErrorResponse.getDefaultTitleMessageCode(HttpMediaTypeNotSupportedException.class), locale, title); - messageSource.addMessage( - ErrorResponse.getDefaultTypeMessageCode(HttpMediaTypeNotSupportedException.class), locale, type); + Locale locale = Locale.UK; + LocaleContextHolder.setLocale(locale); - this.exceptionHandler.setMessageSource(messageSource); + String type = "https://example.com/probs/unsupported-content"; + String title = "Media type is not valid or not supported"; + Class exceptionType = HttpMediaTypeNotSupportedException.class; + + StaticMessageSource source = new StaticMessageSource(); + source.addMessage(ErrorResponse.getDefaultTypeMessageCode(exceptionType), locale, type); + source.addMessage(ErrorResponse.getDefaultTitleMessageCode(exceptionType), locale, title); + source.addMessage(ErrorResponse.getDefaultDetailMessageCode(exceptionType, null), locale, + "Content-Type {0} not supported. Supported: {1}"); + + this.exceptionHandler.setMessageSource(source); ResponseEntity entity = testException(new HttpMediaTypeNotSupportedException( MediaType.APPLICATION_JSON, List.of(MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_XML))); - ProblemDetail body = (ProblemDetail) entity.getBody(); - assertThat(body.getDetail()).isEqualTo( + ProblemDetail problem = (ProblemDetail) entity.getBody(); + assertThat(problem).isNotNull(); + assertThat(problem.getType()).isEqualTo(URI.create(type)); + assertThat(problem.getTitle()).isEqualTo(title); + assertThat(problem.getDetail()).isEqualTo( "Content-Type application/json not supported. Supported: [application/atom+xml, application/xml]"); - assertThat(body.getTitle()).isEqualTo(title); - assertThat(body.getType()).isEqualTo(URI.create(type)); } finally { LocaleContextHolder.resetLocaleContext();