Skip to content

Commit

Permalink
OpenAPI Auto add Bad Request response for operation input
Browse files Browse the repository at this point in the history
Signed-off-by: Phillip Kruger <[email protected]>
  • Loading branch information
phillip-kruger committed Jan 30, 2025
1 parent 0b131a7 commit dd3cb1f
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
* <ul>
* <li>Add operation descriptions based on the associated Java method name handling the operation
* <li>Add operation tags based on the associated Java class of the operation
* <li>Add operation descriptions based on the associated Java method name handling the operation</li>
* <li>Add operation tags based on the associated Java class of the operation</li>
* <li>Add security requirements based on discovered {@link jakarta.annotation.security.RolesAllowed},
* {@link io.quarkus.security.PermissionsAllowed}, and {@link io.quarkus.security.Authenticated}
* annotations.
* annotations. Also add the expected security responses if needed.</li>
* <li>Add Bad Request (400) response for invalid input (if none is provided)</li>
* </ul>
*/
public class OperationFilter implements OASFilter {
Expand All @@ -42,20 +43,22 @@ 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<String, ClassAndMethod> classNameMap,
Map<String, List<String>> rolesAllowedMethodReferences,
List<String> 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);
this.authenticatedMethodReferences = Objects.requireNonNull(authenticatedMethodReferences);
this.defaultSecuritySchemeName = Objects.requireNonNull(defaultSecuritySchemeName);
this.doAutoTag = doAutoTag;
this.doAutoOperation = doAutoOperation;
this.doAutoBadRequest = doAutoBadRequest;
this.alwaysIncludeScopesValidForScheme = alwaysIncludeScopesValidForScheme;
}

Expand All @@ -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);
});
}

Expand All @@ -97,6 +100,20 @@ private String methodRef(Operation operation) {
return (String) (extensions != null ? extensions.get(EXT_METHOD_REF) : null);
}

private void maybeAddBadRequestResponse(Map.Entry<PathItem.HttpMethod, Operation> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ public boolean autoAddTags() {
return false;
}

@Override
public boolean autoAddBadRequestResponse() {
return false;
}

@Override
public boolean autoAddOperationSummary() {
return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
@@ -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"));
}

}

0 comments on commit dd3cb1f

Please sign in to comment.