diff --git a/belgif-rest-problem-it/belgif-rest-problem-it-common/src/main/java/io/github/belgif/rest/problem/it/AbstractRestProblemSpringBootIT.java b/belgif-rest-problem-it/belgif-rest-problem-it-common/src/main/java/io/github/belgif/rest/problem/it/AbstractRestProblemSpringBootIT.java index 1c424bad..7b85e05b 100644 --- a/belgif-rest-problem-it/belgif-rest-problem-it-common/src/main/java/io/github/belgif/rest/problem/it/AbstractRestProblemSpringBootIT.java +++ b/belgif-rest-problem-it/belgif-rest-problem-it-common/src/main/java/io/github/belgif/rest/problem/it/AbstractRestProblemSpringBootIT.java @@ -72,7 +72,7 @@ public void notFound() { getSpec().when().get("/not/found").then().assertThat() .statusCode(404) .body("type", equalTo("urn:problem-type:belgif:resourceNotFound")) - .body("detail", equalTo("No resource /frontend/not/found found")); + .body("detail", stringContainsInOrder("No resource", "/not/found")); } } diff --git a/belgif-rest-problem-it/belgif-rest-problem-spring-boot-3-it/src/main/java/io/github/belgif/rest/problem/ControllerHttpInterface.java b/belgif-rest-problem-it/belgif-rest-problem-spring-boot-3-it/src/main/java/io/github/belgif/rest/problem/ControllerHttpInterface.java new file mode 100644 index 00000000..6564e904 --- /dev/null +++ b/belgif-rest-problem-it/belgif-rest-problem-spring-boot-3-it/src/main/java/io/github/belgif/rest/problem/ControllerHttpInterface.java @@ -0,0 +1,24 @@ +package io.github.belgif.rest.problem; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; + +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.service.annotation.GetExchange; + +@Validated +@Controller +public interface ControllerHttpInterface { + + @GetExchange("/beanValidation/pathParameter/inherited/{param}") + ResponseEntity beanValidationPathParameterInherited( + @PathVariable("param") @Positive @NotNull Integer p); + + @GetExchange("/beanValidation/pathParameter/overridden/{param}") + ResponseEntity beanValidationPathParameterOverridden( + @PathVariable("param") @Positive @NotNull Integer p); + +} diff --git a/belgif-rest-problem-it/belgif-rest-problem-spring-boot-3-it/src/main/java/io/github/belgif/rest/problem/FrontendHttpController.java b/belgif-rest-problem-it/belgif-rest-problem-spring-boot-3-it/src/main/java/io/github/belgif/rest/problem/FrontendHttpController.java new file mode 100644 index 00000000..04ec065a --- /dev/null +++ b/belgif-rest-problem-it/belgif-rest-problem-spring-boot-3-it/src/main/java/io/github/belgif/rest/problem/FrontendHttpController.java @@ -0,0 +1,280 @@ +package io.github.belgif.rest.problem; + +import java.net.URI; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Size; + +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.service.annotation.GetExchange; +import org.springframework.web.service.annotation.HttpExchange; +import org.springframework.web.service.annotation.PostExchange; + +import com.acme.custom.CustomProblem; + +import io.github.belgif.rest.problem.api.Input; +import io.github.belgif.rest.problem.api.Problem; +import io.github.belgif.rest.problem.i18n.I18N; +import io.github.belgif.rest.problem.it.model.ChildModel; +import io.github.belgif.rest.problem.it.model.JacksonModel; +import io.github.belgif.rest.problem.it.model.Model; +import io.github.belgif.rest.problem.it.model.NestedModel; +import io.github.belgif.rest.problem.validation.RequestValidator; + +@RestController +@HttpExchange("/frontend-http") +@Validated +public class FrontendHttpController implements ControllerHttpInterface { + + private static final String DETAIL_MESSAGE_SUFFIX = " (caught successfully by frontend)"; + private static final String ILLEGAL_STATE_MESSAGE_PREFIX = "Unsupported client "; + + private final RestTemplateBuilder restTemplateBuilder; + + private final WebClient.Builder webClientBuilder; + + private final RestClient.Builder restClientBuilder; + + private RestTemplate restTemplate; + + private WebClient webClient; + + private RestClient restClient; + + public FrontendHttpController(RestTemplateBuilder restTemplateBuilder, + WebClient.Builder webClientBuilder, + RestClient.Builder restClientBuilder) { + this.restTemplateBuilder = restTemplateBuilder; + this.webClientBuilder = webClientBuilder; + this.restClientBuilder = restClientBuilder; + } + + @EventListener + public void onApplicationEvent(ServletWebServerInitializedEvent event) { + this.restTemplate = restTemplateBuilder + .rootUri("http://localhost:" + event.getWebServer().getPort() + "/spring/backend") + .build(); + this.webClient = webClientBuilder + .baseUrl("http://localhost:" + event.getWebServer().getPort() + "/spring/backend") + .build(); + this.restClient = restClientBuilder + .baseUrl("http://localhost:" + event.getWebServer().getPort() + "/spring/backend") + .build(); + } + + @GetExchange("/ping") + public String ping() { + return "pong"; + } + + @GetExchange("/badRequest") + public void badRequest() { + BadRequestProblem problem = new BadRequestProblem(); + problem.setDetail("Bad Request from frontend"); + throw problem; + } + + @GetExchange("/custom") + public void custom() { + throw new CustomProblem("value from frontend"); + } + + @GetExchange("/runtime") + public void runtime() { + throw new RuntimeException("oops"); + } + + @GetExchange("/unmapped") + public void unmapped() { + Problem unmapped = new Problem(URI.create("urn:problem-type:belgif:test:unmapped"), "Unmapped problem", 400) { + }; + unmapped.setDetail("Unmapped problem from frontend"); + throw unmapped; + } + + @GetExchange("/retryAfter") + public void retryAfter() { + ServiceUnavailableProblem problem = new ServiceUnavailableProblem(); + problem.setRetryAfterSec(10000L); + throw problem; + } + + @GetExchange(value = "/okFromBackend", accept = "application/json") + public ResponseEntity okFromBackend(@RequestParam("client") Client client) { + String result = null; + if (client == Client.REST_TEMPLATE) { + result = restTemplate.getForObject("/ok", String.class); + } else if (client == Client.WEB_CLIENT) { + result = webClient.get().uri("/ok").retrieve().toEntity(String.class).block().getBody(); + } else if (client == Client.REST_CLIENT) { + result = restClient.get().uri("/ok").retrieve().toEntity(String.class).getBody(); + } + return ResponseEntity.ok(result); + } + + @GetExchange("/badRequestFromBackend") + public void badRequestFromBackend(@RequestParam("client") Client client) { + try { + if (client == Client.REST_TEMPLATE) { + 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) { + e.setDetail(e.getDetail() + DETAIL_MESSAGE_SUFFIX); + throw e; + } + } + + @GetExchange("/customFromBackend") + public void customFromBackend(@RequestParam("client") Client client) { + try { + if (client == Client.REST_TEMPLATE) { + 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) { + e.setCustomField(e.getCustomField() + DETAIL_MESSAGE_SUFFIX); + throw e; + } + } + + @GetExchange("/unmappedFromBackend") + public void unmappedFromBackend(@RequestParam("client") Client client) { + try { + if (client == Client.REST_TEMPLATE) { + 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) { + e.setDetail(e.getDetail() + DETAIL_MESSAGE_SUFFIX); + throw e; + } + } + + @GetExchange("/applicationJsonProblemFromBackend") + public void applicationJsonProblemFromBackend(@RequestParam("client") Client client) { + try { + if (client == Client.REST_TEMPLATE) { + restTemplate.getForObject("/applicationJsonProblem", String.class); + } else if (client == Client.WEB_CLIENT) { + webClient.get().uri("/applicationJsonProblem").retrieve().toEntity(String.class).block(); + } else if (client == Client.REST_CLIENT) { + restClient.get().uri("/applicationJsonProblem").retrieve().toEntity(String.class); + } + throw new IllegalStateException(ILLEGAL_STATE_MESSAGE_PREFIX + client); + } catch (BadRequestProblem e) { + e.setDetail(e.getDetail() + DETAIL_MESSAGE_SUFFIX); + throw e; + } + } + + @GetExchange("/jacksonMismatchedInputFromBackend") + public void jacksonMismatchedInputFromBackend(@RequestParam("client") Client client) { + if (client == Client.REST_TEMPLATE) { + restTemplate.getForObject("/jacksonMismatchedInput", JacksonModel.class); + } else if (client == Client.WEB_CLIENT) { + webClient.get().uri("/jacksonMismatchedInput").retrieve().toEntity(JacksonModel.class).block(); + } else if (client == Client.REST_CLIENT) { + restClient.get().uri("/jacksonMismatchedInput").retrieve().toEntity(JacksonModel.class); + } + throw new IllegalStateException(ILLEGAL_STATE_MESSAGE_PREFIX + client); + } + + @GetExchange("/beanValidation/queryParameter") + public ResponseEntity beanValidationQueryParameter( + @RequestParam("param") @Positive @NotNull Integer p, + @RequestParam @Size(max = 5) String other) { + return ResponseEntity.ok("param: " + p + ", other: " + other); + } + + @GetExchange("/beanValidation/headerParameter") + public ResponseEntity beanValidationHeaderParameter( + @RequestHeader("param") @Positive @NotNull Integer p) { + return ResponseEntity.ok("param: " + p); + } + + @GetExchange("/beanValidation/pathParameter/class/{param}") + public ResponseEntity beanValidationPathParameter( + @PathVariable("param") @Positive @NotNull Integer p) { + return ResponseEntity.ok("param: " + p); + } + + @Override + public ResponseEntity beanValidationPathParameterInherited(Integer p) { + return ResponseEntity.ok("param: " + p); + } + + @Override + @GetExchange("/beanValidation/pathParameter/overridden") + public ResponseEntity beanValidationPathParameterOverridden(@RequestParam("param") Integer p) { + return ResponseEntity.ok("param: " + p); + } + + @PostExchange("/beanValidation/body") + public ResponseEntity beanValidationBody(@Valid @RequestBody Model body) { + return ResponseEntity.ok("body: " + body); + } + + @PostExchange("/beanValidation/body/nested") + public ResponseEntity beanValidationBodyNested(@Valid @RequestBody NestedModel body) { + return ResponseEntity.ok("body: " + body); + } + + @PostExchange("/beanValidation/body/inheritance") + public ResponseEntity beanValidationBodyInheritance(@Valid @RequestBody ChildModel body) { + return ResponseEntity.ok("body: " + body); + } + + @PostExchange("/beanValidation/queryParameter/nested") + public ResponseEntity beanValidationQueryParameterNested(@Valid Model p) { + return ResponseEntity.ok("param: " + p); + } + + @PostExchange("/jackson/mismatchedInputException") + public ResponseEntity jacksonMismatchedInputException(@Valid @RequestBody JacksonModel p) { + return ResponseEntity.ok("param: " + p); + } + + @PostExchange("/i18n") + public ResponseEntity i18n(@RequestParam("enabled") boolean enabled) { + I18N.setEnabled(enabled); + return ResponseEntity.ok().build(); + } + + @GetExchange("/requestValidator") + public ResponseEntity requestValidator(@RequestParam("ssin") String ssin, + @RequestParam(name = "a", required = false) String a, + @RequestParam(name = "b", required = false) String b) { + new RequestValidator().ssin(Input.query("ssin", ssin)) + .zeroOrAllOf(Input.query("a", a), Input.query("b", b)) + .validate(); + return ResponseEntity.ok().build(); + } + +} diff --git a/belgif-rest-problem-it/belgif-rest-problem-spring-boot-3-it/src/test/java/io/github/belgif/rest/problem/RestProblemSpringBoot3HttpExchangeIT.java b/belgif-rest-problem-it/belgif-rest-problem-spring-boot-3-it/src/test/java/io/github/belgif/rest/problem/RestProblemSpringBoot3HttpExchangeIT.java new file mode 100644 index 00000000..fb944708 --- /dev/null +++ b/belgif-rest-problem-it/belgif-rest-problem-spring-boot-3-it/src/test/java/io/github/belgif/rest/problem/RestProblemSpringBoot3HttpExchangeIT.java @@ -0,0 +1,31 @@ +package io.github.belgif.rest.problem; + +import java.util.Arrays; +import java.util.stream.Stream; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.annotation.DirtiesContext; + +import io.github.belgif.rest.problem.it.AbstractRestProblemSpringBootIT; +import io.restassured.RestAssured; +import io.restassured.specification.RequestSpecification; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@DirtiesContext +class RestProblemSpringBoot3HttpExchangeIT extends AbstractRestProblemSpringBootIT { + + @LocalServerPort + private int port; + + @Override + protected RequestSpecification getSpec() { + return RestAssured.with().baseUri("http://localhost").port(port).basePath("/spring/frontend-http"); + } + + @Override + protected Stream getClients() { + return Arrays.stream(Client.values()).map(Client::name); + } + +} diff --git a/belgif-rest-problem-it/belgif-rest-problem-spring-boot-4-it/src/main/java/io/github/belgif/rest/problem/ControllerHttpInterface.java b/belgif-rest-problem-it/belgif-rest-problem-spring-boot-4-it/src/main/java/io/github/belgif/rest/problem/ControllerHttpInterface.java new file mode 100644 index 00000000..6564e904 --- /dev/null +++ b/belgif-rest-problem-it/belgif-rest-problem-spring-boot-4-it/src/main/java/io/github/belgif/rest/problem/ControllerHttpInterface.java @@ -0,0 +1,24 @@ +package io.github.belgif.rest.problem; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; + +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.service.annotation.GetExchange; + +@Validated +@Controller +public interface ControllerHttpInterface { + + @GetExchange("/beanValidation/pathParameter/inherited/{param}") + ResponseEntity beanValidationPathParameterInherited( + @PathVariable("param") @Positive @NotNull Integer p); + + @GetExchange("/beanValidation/pathParameter/overridden/{param}") + ResponseEntity beanValidationPathParameterOverridden( + @PathVariable("param") @Positive @NotNull Integer p); + +} diff --git a/belgif-rest-problem-it/belgif-rest-problem-spring-boot-4-it/src/main/java/io/github/belgif/rest/problem/FrontendHttpController.java b/belgif-rest-problem-it/belgif-rest-problem-spring-boot-4-it/src/main/java/io/github/belgif/rest/problem/FrontendHttpController.java new file mode 100644 index 00000000..9236bb8b --- /dev/null +++ b/belgif-rest-problem-it/belgif-rest-problem-spring-boot-4-it/src/main/java/io/github/belgif/rest/problem/FrontendHttpController.java @@ -0,0 +1,280 @@ +package io.github.belgif.rest.problem; + +import java.net.URI; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Size; + +import org.springframework.boot.restclient.RestTemplateBuilder; +import org.springframework.boot.web.server.servlet.context.ServletWebServerInitializedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.service.annotation.GetExchange; +import org.springframework.web.service.annotation.HttpExchange; +import org.springframework.web.service.annotation.PostExchange; + +import com.acme.custom.CustomProblem; + +import io.github.belgif.rest.problem.api.Input; +import io.github.belgif.rest.problem.api.Problem; +import io.github.belgif.rest.problem.i18n.I18N; +import io.github.belgif.rest.problem.it.model.ChildModel; +import io.github.belgif.rest.problem.it.model.JacksonModel; +import io.github.belgif.rest.problem.it.model.Model; +import io.github.belgif.rest.problem.it.model.NestedModel; +import io.github.belgif.rest.problem.validation.RequestValidator; + +@RestController +@HttpExchange("/frontend-http") +@Validated +public class FrontendHttpController implements ControllerHttpInterface { + + private static final String DETAIL_MESSAGE_SUFFIX = " (caught successfully by frontend)"; + private static final String ILLEGAL_STATE_MESSAGE_PREFIX = "Unsupported client "; + + private final RestTemplateBuilder restTemplateBuilder; + + private final WebClient.Builder webClientBuilder; + + private final RestClient.Builder restClientBuilder; + + private RestTemplate restTemplate; + + private WebClient webClient; + + private RestClient restClient; + + public FrontendHttpController(RestTemplateBuilder restTemplateBuilder, + WebClient.Builder webClientBuilder, + RestClient.Builder restClientBuilder) { + this.restTemplateBuilder = restTemplateBuilder; + this.webClientBuilder = webClientBuilder; + this.restClientBuilder = restClientBuilder; + } + + @EventListener + public void onApplicationEvent(ServletWebServerInitializedEvent event) { + this.restTemplate = restTemplateBuilder + .rootUri("http://localhost:" + event.getWebServer().getPort() + "/spring/backend") + .build(); + this.webClient = webClientBuilder + .baseUrl("http://localhost:" + event.getWebServer().getPort() + "/spring/backend") + .build(); + this.restClient = restClientBuilder + .baseUrl("http://localhost:" + event.getWebServer().getPort() + "/spring/backend") + .build(); + } + + @GetExchange("/ping") + public String ping() { + return "pong"; + } + + @GetExchange("/badRequest") + public void badRequest() { + BadRequestProblem problem = new BadRequestProblem(); + problem.setDetail("Bad Request from frontend"); + throw problem; + } + + @GetExchange("/custom") + public void custom() { + throw new CustomProblem("value from frontend"); + } + + @GetExchange("/runtime") + public void runtime() { + throw new RuntimeException("oops"); + } + + @GetExchange("/unmapped") + public void unmapped() { + Problem unmapped = new Problem(URI.create("urn:problem-type:belgif:test:unmapped"), "Unmapped problem", 400) { + }; + unmapped.setDetail("Unmapped problem from frontend"); + throw unmapped; + } + + @GetExchange("/retryAfter") + public void retryAfter() { + ServiceUnavailableProblem problem = new ServiceUnavailableProblem(); + problem.setRetryAfterSec(10000L); + throw problem; + } + + @GetExchange(value = "/okFromBackend", accept = "application/json") + public ResponseEntity okFromBackend(@RequestParam("client") Client client) { + String result = null; + if (client == Client.REST_TEMPLATE) { + result = restTemplate.getForObject("/ok", String.class); + } else if (client == Client.WEB_CLIENT) { + result = webClient.get().uri("/ok").retrieve().toEntity(String.class).block().getBody(); + } else if (client == Client.REST_CLIENT) { + result = restClient.get().uri("/ok").retrieve().toEntity(String.class).getBody(); + } + return ResponseEntity.ok(result); + } + + @GetExchange("/badRequestFromBackend") + public void badRequestFromBackend(@RequestParam("client") Client client) { + try { + if (client == Client.REST_TEMPLATE) { + 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) { + e.setDetail(e.getDetail() + DETAIL_MESSAGE_SUFFIX); + throw e; + } + } + + @GetExchange("/customFromBackend") + public void customFromBackend(@RequestParam("client") Client client) { + try { + if (client == Client.REST_TEMPLATE) { + 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) { + e.setCustomField(e.getCustomField() + DETAIL_MESSAGE_SUFFIX); + throw e; + } + } + + @GetExchange("/unmappedFromBackend") + public void unmappedFromBackend(@RequestParam("client") Client client) { + try { + if (client == Client.REST_TEMPLATE) { + 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) { + e.setDetail(e.getDetail() + DETAIL_MESSAGE_SUFFIX); + throw e; + } + } + + @GetExchange("/applicationJsonProblemFromBackend") + public void applicationJsonProblemFromBackend(@RequestParam("client") Client client) { + try { + if (client == Client.REST_TEMPLATE) { + restTemplate.getForObject("/applicationJsonProblem", String.class); + } else if (client == Client.WEB_CLIENT) { + webClient.get().uri("/applicationJsonProblem").retrieve().toEntity(String.class).block(); + } else if (client == Client.REST_CLIENT) { + restClient.get().uri("/applicationJsonProblem").retrieve().toEntity(String.class); + } + throw new IllegalStateException(ILLEGAL_STATE_MESSAGE_PREFIX + client); + } catch (BadRequestProblem e) { + e.setDetail(e.getDetail() + DETAIL_MESSAGE_SUFFIX); + throw e; + } + } + + @GetExchange("/jacksonMismatchedInputFromBackend") + public void jacksonMismatchedInputFromBackend(@RequestParam("client") Client client) { + if (client == Client.REST_TEMPLATE) { + restTemplate.getForObject("/jacksonMismatchedInput", JacksonModel.class); + } else if (client == Client.WEB_CLIENT) { + webClient.get().uri("/jacksonMismatchedInput").retrieve().toEntity(JacksonModel.class).block(); + } else if (client == Client.REST_CLIENT) { + restClient.get().uri("/jacksonMismatchedInput").retrieve().toEntity(JacksonModel.class); + } + throw new IllegalStateException(ILLEGAL_STATE_MESSAGE_PREFIX + client); + } + + @GetExchange("/beanValidation/queryParameter") + public ResponseEntity beanValidationQueryParameter( + @RequestParam("param") @Positive @NotNull Integer p, + @RequestParam @Size(max = 5) String other) { + return ResponseEntity.ok("param: " + p + ", other: " + other); + } + + @GetExchange("/beanValidation/headerParameter") + public ResponseEntity beanValidationHeaderParameter( + @RequestHeader("param") @Positive @NotNull Integer p) { + return ResponseEntity.ok("param: " + p); + } + + @GetExchange("/beanValidation/pathParameter/class/{param}") + public ResponseEntity beanValidationPathParameter( + @PathVariable("param") @Positive @NotNull Integer p) { + return ResponseEntity.ok("param: " + p); + } + + @Override + public ResponseEntity beanValidationPathParameterInherited(Integer p) { + return ResponseEntity.ok("param: " + p); + } + + @Override + @GetExchange("/beanValidation/pathParameter/overridden") + public ResponseEntity beanValidationPathParameterOverridden(@RequestParam("param") Integer p) { + return ResponseEntity.ok("param: " + p); + } + + @PostExchange("/beanValidation/body") + public ResponseEntity beanValidationBody(@Valid @RequestBody Model body) { + return ResponseEntity.ok("body: " + body); + } + + @PostExchange("/beanValidation/body/nested") + public ResponseEntity beanValidationBodyNested(@Valid @RequestBody NestedModel body) { + return ResponseEntity.ok("body: " + body); + } + + @PostExchange("/beanValidation/body/inheritance") + public ResponseEntity beanValidationBodyInheritance(@Valid @RequestBody ChildModel body) { + return ResponseEntity.ok("body: " + body); + } + + @PostExchange("/beanValidation/queryParameter/nested") + public ResponseEntity beanValidationQueryParameterNested(@Valid Model p) { + return ResponseEntity.ok("param: " + p); + } + + @PostExchange("/jackson/mismatchedInputException") + public ResponseEntity jacksonMismatchedInputException(@Valid @RequestBody JacksonModel p) { + return ResponseEntity.ok("param: " + p); + } + + @PostExchange("/i18n") + public ResponseEntity i18n(@RequestParam("enabled") boolean enabled) { + I18N.setEnabled(enabled); + return ResponseEntity.ok().build(); + } + + @GetExchange("/requestValidator") + public ResponseEntity requestValidator(@RequestParam("ssin") String ssin, + @RequestParam(name = "a", required = false) String a, + @RequestParam(name = "b", required = false) String b) { + new RequestValidator().ssin(Input.query("ssin", ssin)) + .zeroOrAllOf(Input.query("a", a), Input.query("b", b)) + .validate(); + return ResponseEntity.ok().build(); + } + +} diff --git a/belgif-rest-problem-it/belgif-rest-problem-spring-boot-4-it/src/test/java/io/github/belgif/rest/problem/RestProblemSpringBoot4HttpExchangeIT.java b/belgif-rest-problem-it/belgif-rest-problem-spring-boot-4-it/src/test/java/io/github/belgif/rest/problem/RestProblemSpringBoot4HttpExchangeIT.java new file mode 100644 index 00000000..22b220e3 --- /dev/null +++ b/belgif-rest-problem-it/belgif-rest-problem-spring-boot-4-it/src/test/java/io/github/belgif/rest/problem/RestProblemSpringBoot4HttpExchangeIT.java @@ -0,0 +1,31 @@ +package io.github.belgif.rest.problem; + +import java.util.Arrays; +import java.util.stream.Stream; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.annotation.DirtiesContext; + +import io.github.belgif.rest.problem.it.AbstractRestProblemSpringBootIT; +import io.restassured.RestAssured; +import io.restassured.specification.RequestSpecification; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@DirtiesContext +class RestProblemSpringBoot4HttpExchangeIT extends AbstractRestProblemSpringBootIT { + + @LocalServerPort + private int port; + + @Override + protected RequestSpecification getSpec() { + return RestAssured.with().baseUri("http://localhost").port(port).basePath("/spring/frontend-http"); + } + + @Override + protected Stream getClients() { + return Arrays.stream(Client.values()).map(Client::name); + } + +}