diff --git a/belgif-rest-problem-it/belgif-rest-problem-spring-boot-3-it/src/main/java/io/github/belgif/rest/problem/Client.java b/belgif-rest-problem-it/belgif-rest-problem-spring-boot-3-it/src/main/java/io/github/belgif/rest/problem/Client.java index 5be571e9..0a4f64af 100644 --- a/belgif-rest-problem-it/belgif-rest-problem-spring-boot-3-it/src/main/java/io/github/belgif/rest/problem/Client.java +++ b/belgif-rest-problem-it/belgif-rest-problem-spring-boot-3-it/src/main/java/io/github/belgif/rest/problem/Client.java @@ -2,6 +2,6 @@ public enum Client { - REST_TEMPLATE, WEB_CLIENT + REST_TEMPLATE, WEB_CLIENT, REST_CLIENT } diff --git a/belgif-rest-problem-it/belgif-rest-problem-spring-boot-3-it/src/main/java/io/github/belgif/rest/problem/FrontendController.java b/belgif-rest-problem-it/belgif-rest-problem-spring-boot-3-it/src/main/java/io/github/belgif/rest/problem/FrontendController.java index e4d0cbf4..6761efd6 100644 --- a/belgif-rest-problem-it/belgif-rest-problem-spring-boot-3-it/src/main/java/io/github/belgif/rest/problem/FrontendController.java +++ b/belgif-rest-problem-it/belgif-rest-problem-spring-boot-3-it/src/main/java/io/github/belgif/rest/problem/FrontendController.java @@ -11,6 +11,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.springframework.web.client.RestClient; import org.springframework.web.client.RestTemplate; import org.springframework.web.reactive.function.client.WebClient; @@ -33,13 +34,20 @@ public class FrontendController implements ControllerInterface { private final WebClient.Builder webClientBuilder; + private final RestClient.Builder restClientBuilder; + private RestTemplate restTemplate; private WebClient webClient; - public FrontendController(RestTemplateBuilder restTemplateBuilder, WebClient.Builder webClientBuilder) { + private RestClient restClient; + + public FrontendController(RestTemplateBuilder restTemplateBuilder, + WebClient.Builder webClientBuilder, + RestClient.Builder restClientBuilder) { this.restTemplateBuilder = restTemplateBuilder; this.webClientBuilder = webClientBuilder; + this.restClientBuilder = restClientBuilder; } @EventListener @@ -48,7 +56,11 @@ public void onApplicationEvent(ServletWebServerInitializedEvent event) { .rootUri("http://localhost:" + event.getWebServer().getPort() + "/spring/backend") .build(); this.webClient = webClientBuilder - .baseUrl("http://localhost:" + event.getWebServer().getPort() + "/spring/backend").build(); + .baseUrl("http://localhost:" + event.getWebServer().getPort() + "/spring/backend") + .build(); + this.restClient = restClientBuilder + .baseUrl("http://localhost:" + event.getWebServer().getPort() + "/spring/backend") + .build(); } @GetMapping("/badRequest") @@ -90,6 +102,8 @@ public void badRequestFromBackend(@RequestParam("client") Client client) { restTemplate.getForObject("/badRequest", String.class); } else if (client == Client.WEB_CLIENT) { webClient.get().uri("/badRequest").retrieve().toEntity(String.class).block(); + } else if (client == Client.REST_CLIENT) { + restClient.get().uri("/badRequest").retrieve().toEntity(String.class); } throw new IllegalStateException(ILLEGAL_STATE_MESSAGE_PREFIX + client); } catch (BadRequestProblem e) { @@ -105,6 +119,8 @@ public void customFromBackend(@RequestParam("client") Client client) { restTemplate.getForObject("/custom", String.class); } else if (client == Client.WEB_CLIENT) { webClient.get().uri("/custom").retrieve().toEntity(String.class).block(); + } else if (client == Client.REST_CLIENT) { + restClient.get().uri("/custom").retrieve().toEntity(String.class); } throw new IllegalStateException(ILLEGAL_STATE_MESSAGE_PREFIX + client); } catch (CustomProblem e) { @@ -120,6 +136,8 @@ public void unmappedFromBackend(@RequestParam("client") Client client) { restTemplate.getForObject("/unmapped", String.class); } else if (client == Client.WEB_CLIENT) { webClient.get().uri("/unmapped").retrieve().toEntity(String.class).block(); + } else if (client == Client.REST_CLIENT) { + restClient.get().uri("/unmapped").retrieve().toEntity(String.class); } throw new IllegalStateException(ILLEGAL_STATE_MESSAGE_PREFIX + client); } catch (DefaultProblem e) { diff --git a/belgif-rest-problem-spring-boot-3/pom.xml b/belgif-rest-problem-spring-boot-3/pom.xml index defcddcc..00e9539b 100644 --- a/belgif-rest-problem-spring-boot-3/pom.xml +++ b/belgif-rest-problem-spring-boot-3/pom.xml @@ -46,6 +46,11 @@ spring-webmvc provided + + com.fasterxml.jackson.core + jackson-databind + provided + jakarta.platform jakarta.jakartaee-api diff --git a/belgif-rest-problem-spring-boot-3/src/main/java/io/github/belgif/rest/problem/spring/ProblemRestClientCustomizer.java b/belgif-rest-problem-spring-boot-3/src/main/java/io/github/belgif/rest/problem/spring/ProblemRestClientCustomizer.java new file mode 100644 index 00000000..0bda31cf --- /dev/null +++ b/belgif-rest-problem-spring-boot-3/src/main/java/io/github/belgif/rest/problem/spring/ProblemRestClientCustomizer.java @@ -0,0 +1,26 @@ +package io.github.belgif.rest.problem.spring; + +import org.springframework.boot.web.client.RestClientCustomizer; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestClient; + +/** + * RestClientCustomizer that registers the {@link ProblemResponseErrorHandler}. + * + * @see ProblemResponseErrorHandler + */ +@Component +public class ProblemRestClientCustomizer implements RestClientCustomizer { + + private final ProblemResponseErrorHandler errorHandler; + + public ProblemRestClientCustomizer(ProblemResponseErrorHandler errorHandler) { + this.errorHandler = errorHandler; + } + + @Override + public void customize(RestClient.Builder restClientBuilder) { + restClientBuilder.defaultStatusHandler(errorHandler); + } + +} diff --git a/belgif-rest-problem-spring-boot-3/src/test/java/io/github/belgif/rest/problem/spring/ProblemRestClientCustomizerTest.java b/belgif-rest-problem-spring-boot-3/src/test/java/io/github/belgif/rest/problem/spring/ProblemRestClientCustomizerTest.java new file mode 100644 index 00000000..5b6e387e --- /dev/null +++ b/belgif-rest-problem-spring-boot-3/src/test/java/io/github/belgif/rest/problem/spring/ProblemRestClientCustomizerTest.java @@ -0,0 +1,26 @@ +package io.github.belgif.rest.problem.spring; + +import static org.assertj.core.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestClient; + +import com.fasterxml.jackson.databind.ObjectMapper; + +class ProblemRestClientCustomizerTest { + + @Test + void customize() { + ProblemResponseErrorHandler handler = new ProblemResponseErrorHandler(new ObjectMapper()); + ProblemRestClientCustomizer customizer = new ProblemRestClientCustomizer(handler); + RestClient.Builder builder = RestClient.builder(); + customizer.customize(builder); + + List statusHandlers = (List) ReflectionTestUtils.getField(builder, "statusHandlers"); + assertThat(statusHandlers).hasSize(1); + } + +} diff --git a/src/main/asciidoc/index.adoc b/src/main/asciidoc/index.adoc index ce1630b8..4321c71d 100644 --- a/src/main/asciidoc/index.adoc +++ b/src/main/asciidoc/index.adoc @@ -39,6 +39,7 @@ To benefit from Spring Boot 2.x or 3.x specific features, replace dependencies t *belgif-rest-problem-spring-boot-3:* * Map NoResourceFoundException to 404 `urn:problem-type:belgif:resourceNotFound` +* Added support for https://docs.spring.io/spring-framework/reference/integration/rest-clients.html#rest-restclient[RestClient] API === Version 0.4 @@ -348,7 +349,7 @@ io.github.belgif.rest.problem.scan-additional-problem-packages=com.acme.custom * *BeanValidationExceptionsHandler:* an exception handler for RestControllers that converts bean validation related exceptions to HTTP 400 BadRequestProblem. * *RoutingExceptionsHandler:* an exception handler for RestControllers that converts routing related validation exceptions to HTTP 400 BadRequestProblem. * *ProblemResponseErrorHandler:* a RestTemplate error handler that converts problem responses to Problem exceptions. -* *ProblemRestTemplateCustomizer:* a RestTemplateCustomizer that registers the ProblemResponseErrorHandler +* *ProblemRestTemplateCustomizer:* a RestTemplateCustomizer that registers the ProblemResponseErrorHandler. * *ProblemWebClientCustomizer:* a WebClientCustomizer that registers a filter that converts problem responses to Problem exceptions. This handles integration with the https://docs.spring.io/spring-framework/reference/web/webflux-webclient.html[Reactive WebClient]. @@ -365,6 +366,7 @@ Rather than depending on <> directly, Spring Boot 2. Rather than depending on <> directly, Spring Boot 3.x users are recommended to depend on `belgif-rest-problem-spring-boot-3`, which adds some Spring Boot 3.x specific integrations: * *NoResourceFoundExceptionHandler:* an exception handler for RestControllers that converts NoResourceFoundException to HTTP 404 ResourceNotFoundProblem. +* *ProblemRestClientCustomizer:* a RestClientCustomizer that registers the ProblemResponseErrorHandler. [[code-generators]] == Code generators