From dd3cb1f034b82729e96c8f17c09842408373d7e8 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Fri, 31 Jan 2025 09:10:45 +1100 Subject: [PATCH] OpenAPI Auto add Bad Request response for operation input Signed-off-by: Phillip Kruger --- .../deployment/SmallRyeOpenApiConfig.java | 6 +++ .../deployment/SmallRyeOpenApiProcessor.java | 3 +- .../deployment/filter/OperationFilter.java | 39 +++++++++++++----- .../filter/SecurityConfigFilterTest.java | 5 +++ .../openapi/test/jaxrs/Auto400Resource.java | 32 +++++++++++++++ .../openapi/test/jaxrs/Auto400TestCase.java | 40 +++++++++++++++++++ 6 files changed, 113 insertions(+), 12 deletions(-) create mode 100644 extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/Auto400Resource.java create mode 100644 extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/Auto400TestCase.java diff --git a/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java b/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java index a41556b76fb34..5171ad76eb286 100644 --- a/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java +++ b/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java @@ -94,6 +94,12 @@ public interface SmallRyeOpenApiConfig { @WithDefault("true") boolean autoAddTags(); + /** + * This will automatically add BadRequest (400 Http status) API response to operation that has an input. + */ + @WithDefault("true") + boolean autoAddBadRequestResponse(); + /** * This will automatically add a summary to operations based on the Java method name. */ diff --git a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java index 5a66d258d7661..0249beee80fa9 100644 --- a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java +++ b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java @@ -561,7 +561,8 @@ private OASFilter getOperationFilter(OpenApiFilteredIndexViewBuildItem indexView if (!classNamesMethods.isEmpty() || !rolesAllowedMethods.isEmpty() || !authenticatedMethods.isEmpty()) { return new OperationFilter(classNamesMethods, rolesAllowedMethods, authenticatedMethods, config.securitySchemeName(), - config.autoAddTags(), config.autoAddOperationSummary(), isOpenApi_3_1_0_OrGreater(config)); + config.autoAddTags(), config.autoAddOperationSummary(), config.autoAddBadRequestResponse(), + isOpenApi_3_1_0_OrGreater(config)); } return null; diff --git a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/filter/OperationFilter.java b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/filter/OperationFilter.java index 296a2c4746e5a..d17064931b656 100644 --- a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/filter/OperationFilter.java +++ b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/filter/OperationFilter.java @@ -23,13 +23,14 @@ import org.eclipse.microprofile.openapi.models.security.SecurityScheme; /** - * This filter replaces the former AutoTagFilter and AutoRolesAllowedFilter and has three functions: + * This filter has the following functions: * */ public class OperationFilter implements OASFilter { @@ -42,13 +43,14 @@ public class OperationFilter implements OASFilter { private final String defaultSecuritySchemeName; private final boolean doAutoTag; private final boolean doAutoOperation; + private final boolean doAutoBadRequest; private final boolean alwaysIncludeScopesValidForScheme; public OperationFilter(Map classNameMap, Map> rolesAllowedMethodReferences, List authenticatedMethodReferences, String defaultSecuritySchemeName, - boolean doAutoTag, boolean doAutoOperation, boolean alwaysIncludeScopesValidForScheme) { + boolean doAutoTag, boolean doAutoOperation, boolean doAutoBadRequest, boolean alwaysIncludeScopesValidForScheme) { this.classNameMap = Objects.requireNonNull(classNameMap); this.rolesAllowedMethodReferences = Objects.requireNonNull(rolesAllowedMethodReferences); @@ -56,6 +58,7 @@ public OperationFilter(Map classNameMap, this.defaultSecuritySchemeName = Objects.requireNonNull(defaultSecuritySchemeName); this.doAutoTag = doAutoTag; this.doAutoOperation = doAutoOperation; + this.doAutoBadRequest = doAutoBadRequest; this.alwaysIncludeScopesValidForScheme = alwaysIncludeScopesValidForScheme; } @@ -77,18 +80,18 @@ public void filterOpenAPI(OpenAPI openAPI) { .map(Map.Entry::getValue) .map(PathItem::getOperations) .filter(Objects::nonNull) - .map(Map::values) - .flatMap(Collection::stream) + .flatMap(operations -> operations.entrySet().stream()) .forEach(operation -> { - final String methodRef = methodRef(operation); + final String methodRef = methodRef(operation.getValue()); if (methodRef != null) { - maybeSetSummaryAndTag(operation, methodRef); - maybeAddSecurityRequirement(operation, methodRef, schemeName, scopesValidForScheme, + maybeSetSummaryAndTag(operation.getValue(), methodRef); + maybeAddSecurityRequirement(operation.getValue(), methodRef, schemeName, scopesValidForScheme, defaultSecurityErrors); + maybeAddBadRequestResponse(operation, methodRef); } - operation.removeExtension(EXT_METHOD_REF); + operation.getValue().removeExtension(EXT_METHOD_REF); }); } @@ -97,6 +100,20 @@ private String methodRef(Operation operation) { return (String) (extensions != null ? extensions.get(EXT_METHOD_REF) : null); } + private void maybeAddBadRequestResponse(Map.Entry operation, String methodRef) { + if (!classNameMap.containsKey(methodRef)) { + return; + } + if (doAutoBadRequest && (operation.getKey().equals(PathItem.HttpMethod.POST) + || operation.getKey().equals(PathItem.HttpMethod.PUT))) { // Only applies to input + if (!operation.getValue().getResponses().hasAPIResponse("400")) { // Only when the user has not already added one + operation.getValue().getResponses().addAPIResponse("400", + OASFactory.createAPIResponse().description("Bad Request")); + } + } + + } + private void maybeSetSummaryAndTag(Operation operation, String methodRef) { if (!classNameMap.containsKey(methodRef)) { return; diff --git a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/deployment/filter/SecurityConfigFilterTest.java b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/deployment/filter/SecurityConfigFilterTest.java index 807399e5982e0..2bf6c3ba209e8 100644 --- a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/deployment/filter/SecurityConfigFilterTest.java +++ b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/deployment/filter/SecurityConfigFilterTest.java @@ -127,6 +127,11 @@ public boolean autoAddTags() { return false; } + @Override + public boolean autoAddBadRequestResponse() { + return false; + } + @Override public boolean autoAddOperationSummary() { return false; diff --git a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/Auto400Resource.java b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/Auto400Resource.java new file mode 100644 index 0000000000000..8c1c2c85ffd02 --- /dev/null +++ b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/Auto400Resource.java @@ -0,0 +1,32 @@ +package io.quarkus.smallrye.openapi.test.jaxrs; + +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; + +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; + +@Path("/auto") +public class Auto400Resource { + + @POST + @Path("/400") + public void addBar(MyBean myBean) { + System.out.println(myBean.bar); + } + + @POST + @Path("/provided/400") + @APIResponses({ + @APIResponse(responseCode = "204", description = "Successful"), + @APIResponse(responseCode = "400", description = "Invalid bean supplied") + }) + public void addProvidedBar(MyBean myBean) { + System.out.println(myBean.bar); + } + + private static class MyBean { + public String bar; + } + +} diff --git a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/Auto400TestCase.java b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/Auto400TestCase.java new file mode 100644 index 0000000000000..c90636279d891 --- /dev/null +++ b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/Auto400TestCase.java @@ -0,0 +1,40 @@ +package io.quarkus.smallrye.openapi.test.jaxrs; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +class Auto400TestCase { + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(Auto400Resource.class)); + + @Test + void test400InOpenApi() { + RestAssured.given().header("Accept", "application/json") + .when() + .get("/q/openapi") + .then() + .log().ifValidationFails() + .assertThat() + .statusCode(200) + .body("paths.'/auto/400'.post.responses.400.description", Matchers.is("Bad Request")); + } + + @Test + void test400ProvidedInOpenApi() { + RestAssured.given().header("Accept", "application/json") + .when() + .get("/q/openapi") + .then() + .log().ifValidationFails() + .assertThat() + .statusCode(200) + .body("paths.'/auto/provided/400'.post.responses.400.description", Matchers.is("Invalid bean supplied")); + } + +}