Skip to content

Commit b2b342d

Browse files
OpenAPI Auto add Bad Request response for operation input
Signed-off-by: Phillip Kruger <[email protected]>
1 parent 0b131a7 commit b2b342d

File tree

6 files changed

+155
-12
lines changed

6 files changed

+155
-12
lines changed

extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java

+6
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ public interface SmallRyeOpenApiConfig {
9494
@WithDefault("true")
9595
boolean autoAddTags();
9696

97+
/**
98+
* This will automatically add Bad Request (400 HTTP response) API response to operations with an input.
99+
*/
100+
@WithDefault("true")
101+
boolean autoAddBadRequestResponse();
102+
97103
/**
98104
* This will automatically add a summary to operations based on the Java method name.
99105
*/

extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,8 @@ private OASFilter getOperationFilter(OpenApiFilteredIndexViewBuildItem indexView
561561
if (!classNamesMethods.isEmpty() || !rolesAllowedMethods.isEmpty() || !authenticatedMethods.isEmpty()) {
562562
return new OperationFilter(classNamesMethods, rolesAllowedMethods, authenticatedMethods,
563563
config.securitySchemeName(),
564-
config.autoAddTags(), config.autoAddOperationSummary(), isOpenApi_3_1_0_OrGreater(config));
564+
config.autoAddTags(), config.autoAddOperationSummary(), config.autoAddBadRequestResponse(),
565+
isOpenApi_3_1_0_OrGreater(config));
565566
}
566567

567568
return null;

extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/filter/OperationFilter.java

+29-11
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,14 @@
2323
import org.eclipse.microprofile.openapi.models.security.SecurityScheme;
2424

2525
/**
26-
* This filter replaces the former AutoTagFilter and AutoRolesAllowedFilter and has three functions:
26+
* This filter has the following functions:
2727
* <ul>
28-
* <li>Add operation descriptions based on the associated Java method name handling the operation
29-
* <li>Add operation tags based on the associated Java class of the operation
28+
* <li>Add operation descriptions based on the associated Java method name handling the operation</li>
29+
* <li>Add operation tags based on the associated Java class of the operation</li>
3030
* <li>Add security requirements based on discovered {@link jakarta.annotation.security.RolesAllowed},
3131
* {@link io.quarkus.security.PermissionsAllowed}, and {@link io.quarkus.security.Authenticated}
32-
* annotations.
32+
* annotations. Also add the expected security responses if needed.</li>
33+
* <li>Add Bad Request (400) response for invalid input (if none is provided)</li>
3334
* </ul>
3435
*/
3536
public class OperationFilter implements OASFilter {
@@ -42,20 +43,22 @@ public class OperationFilter implements OASFilter {
4243
private final String defaultSecuritySchemeName;
4344
private final boolean doAutoTag;
4445
private final boolean doAutoOperation;
46+
private final boolean doAutoBadRequest;
4547
private final boolean alwaysIncludeScopesValidForScheme;
4648

4749
public OperationFilter(Map<String, ClassAndMethod> classNameMap,
4850
Map<String, List<String>> rolesAllowedMethodReferences,
4951
List<String> authenticatedMethodReferences,
5052
String defaultSecuritySchemeName,
51-
boolean doAutoTag, boolean doAutoOperation, boolean alwaysIncludeScopesValidForScheme) {
53+
boolean doAutoTag, boolean doAutoOperation, boolean doAutoBadRequest, boolean alwaysIncludeScopesValidForScheme) {
5254

5355
this.classNameMap = Objects.requireNonNull(classNameMap);
5456
this.rolesAllowedMethodReferences = Objects.requireNonNull(rolesAllowedMethodReferences);
5557
this.authenticatedMethodReferences = Objects.requireNonNull(authenticatedMethodReferences);
5658
this.defaultSecuritySchemeName = Objects.requireNonNull(defaultSecuritySchemeName);
5759
this.doAutoTag = doAutoTag;
5860
this.doAutoOperation = doAutoOperation;
61+
this.doAutoBadRequest = doAutoBadRequest;
5962
this.alwaysIncludeScopesValidForScheme = alwaysIncludeScopesValidForScheme;
6063
}
6164

@@ -77,18 +80,18 @@ public void filterOpenAPI(OpenAPI openAPI) {
7780
.map(Map.Entry::getValue)
7881
.map(PathItem::getOperations)
7982
.filter(Objects::nonNull)
80-
.map(Map::values)
81-
.flatMap(Collection::stream)
83+
.flatMap(operations -> operations.entrySet().stream())
8284
.forEach(operation -> {
83-
final String methodRef = methodRef(operation);
85+
final String methodRef = methodRef(operation.getValue());
8486

8587
if (methodRef != null) {
86-
maybeSetSummaryAndTag(operation, methodRef);
87-
maybeAddSecurityRequirement(operation, methodRef, schemeName, scopesValidForScheme,
88+
maybeSetSummaryAndTag(operation.getValue(), methodRef);
89+
maybeAddSecurityRequirement(operation.getValue(), methodRef, schemeName, scopesValidForScheme,
8890
defaultSecurityErrors);
91+
maybeAddBadRequestResponse(operation, methodRef);
8992
}
9093

91-
operation.removeExtension(EXT_METHOD_REF);
94+
operation.getValue().removeExtension(EXT_METHOD_REF);
9295
});
9396
}
9497

@@ -97,6 +100,21 @@ private String methodRef(Operation operation) {
97100
return (String) (extensions != null ? extensions.get(EXT_METHOD_REF) : null);
98101
}
99102

103+
private void maybeAddBadRequestResponse(Map.Entry<PathItem.HttpMethod, Operation> operation, String methodRef) {
104+
if (!classNameMap.containsKey(methodRef)) {
105+
return;
106+
}
107+
108+
if (doAutoBadRequest && (operation.getKey().equals(PathItem.HttpMethod.POST)
109+
|| operation.getKey().equals(PathItem.HttpMethod.PUT)) && operation.getValue().getRequestBody() != null) { // Only applies to input
110+
if (!operation.getValue().getResponses().hasAPIResponse("400")) { // Only when the user has not already added one
111+
operation.getValue().getResponses().addAPIResponse("400",
112+
OASFactory.createAPIResponse().description("Bad Request"));
113+
}
114+
}
115+
116+
}
117+
100118
private void maybeSetSummaryAndTag(Operation operation, String methodRef) {
101119
if (!classNameMap.containsKey(methodRef)) {
102120
return;

extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/deployment/filter/SecurityConfigFilterTest.java

+5
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ public boolean autoAddTags() {
127127
return false;
128128
}
129129

130+
@Override
131+
public boolean autoAddBadRequestResponse() {
132+
return false;
133+
}
134+
130135
@Override
131136
public boolean autoAddOperationSummary() {
132137
return false;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package io.quarkus.smallrye.openapi.test.jaxrs;
2+
3+
import jakarta.ws.rs.POST;
4+
import jakarta.ws.rs.PUT;
5+
import jakarta.ws.rs.Path;
6+
7+
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
8+
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
9+
10+
@Path("/auto")
11+
public class AutoBadRequestResource {
12+
13+
@POST
14+
@Path("/400")
15+
public void addBar(MyBean myBean) {
16+
}
17+
18+
@PUT
19+
@Path("/400")
20+
public void updateBar(MyBean myBean) {
21+
}
22+
23+
@POST
24+
@Path("/provided/400")
25+
@APIResponses({
26+
@APIResponse(responseCode = "204", description = "Successful"),
27+
@APIResponse(responseCode = "400", description = "Invalid bean supplied")
28+
})
29+
public void addProvidedBar(MyBean myBean) {
30+
}
31+
32+
@PUT
33+
@Path("/provided/400")
34+
@APIResponses({
35+
@APIResponse(responseCode = "204", description = "Successful"),
36+
@APIResponse(responseCode = "400", description = "Invalid bean supplied")
37+
})
38+
public void updateProvidedBar(MyBean myBean) {
39+
}
40+
41+
@POST
42+
@Path("/nobody/400")
43+
public void addNobodyBar() {
44+
}
45+
46+
@PUT
47+
@Path("/nobody/400")
48+
public void updateNobodyBar() {
49+
}
50+
51+
private static class MyBean {
52+
public String bar;
53+
}
54+
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package io.quarkus.smallrye.openapi.test.jaxrs;
2+
3+
import org.hamcrest.Matchers;
4+
import org.junit.jupiter.api.Test;
5+
import org.junit.jupiter.api.extension.RegisterExtension;
6+
7+
import io.quarkus.test.QuarkusUnitTest;
8+
import io.restassured.RestAssured;
9+
10+
class AutoBadRequestTestCase {
11+
@RegisterExtension
12+
static QuarkusUnitTest runner = new QuarkusUnitTest()
13+
.withApplicationRoot((jar) -> jar
14+
.addClasses(AutoBadRequestResource.class));
15+
16+
@Test
17+
void test400InOpenApi() {
18+
RestAssured.given().header("Accept", "application/json")
19+
.when()
20+
.get("/q/openapi")
21+
.then()
22+
.log().ifValidationFails()
23+
.assertThat()
24+
.statusCode(200)
25+
.body("paths.'/auto/400'.post.responses.400.description", Matchers.is("Bad Request"))
26+
.and()
27+
.body("paths.'/auto/400'.put.responses.400.description", Matchers.is("Bad Request"));
28+
}
29+
30+
@Test
31+
void test400ProvidedInOpenApi() {
32+
RestAssured.given().header("Accept", "application/json")
33+
.when()
34+
.get("/q/openapi")
35+
.then()
36+
.log().ifValidationFails()
37+
.assertThat()
38+
.statusCode(200)
39+
.body("paths.'/auto/provided/400'.post.responses.400.description", Matchers.is("Invalid bean supplied"))
40+
.and()
41+
.body("paths.'/auto/provided/400'.put.responses.400.description", Matchers.is("Invalid bean supplied"));
42+
}
43+
44+
@Test
45+
void test400NobodyInOpenApi() {
46+
RestAssured.given().header("Accept", "application/json")
47+
.when()
48+
.get("/q/openapi")
49+
.then()
50+
.log().ifValidationFails()
51+
.assertThat()
52+
.statusCode(200)
53+
.body("paths.'/auto/nobody/400'.post.responses.400", Matchers.is(Matchers.emptyOrNullString()))
54+
.and()
55+
.body("paths.'/auto/nobody/400'.put.responses.400", Matchers.is(Matchers.emptyOrNullString()));
56+
}
57+
58+
}

0 commit comments

Comments
 (0)