Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,23 @@ public ModelsMap postProcessModels(ModelsMap objs) {
public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<ModelMap> allModels) {
objs = super.postProcessOperationsWithModels(objs, allModels);
removeImport(objs, "java.util.List");
if (QUARKUS_LIBRARY.equals(library) && !returnResponse && !returnJbossResponse && useJakartaEe) {
for (CodegenOperation op : objs.getOperations().getOperation()) {
op.responses.stream()
.filter(r -> r.is2xx && r.code.matches("\\d+"))
.findFirst()
.ifPresent(r -> op.vendorExtensions.put("x-java-success-response-code", r.code));
}
boolean hasAnnotations = objs.getOperations().getOperation().stream()
.anyMatch(op -> op.vendorExtensions.containsKey("x-java-success-response-code"));
// Always set explicitly so Mustache does not fall through to the global additionalProperties
// value when this file has no annotated operations.
objs.put("hasResponseStatusAnnotations", hasAnnotations);
if (hasAnnotations) {
// Global flag used by pom.mustache, which is rendered once per project.
additionalProperties.put("hasResponseStatusAnnotations", true);
}
}
return objs;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package {{package}};
import {{javaxPackage}}.ws.rs.*;
import {{javaxPackage}}.ws.rs.core.Response;
{{#returnJBossResponse}}import org.jboss.resteasy.reactive.RestResponse;{{/returnJBossResponse}}

{{#interfaceOnly}}{{#hasResponseStatusAnnotations}}import org.jboss.resteasy.reactive.ResponseStatus;{{/hasResponseStatusAnnotations}}{{/interfaceOnly}}
{{#useGzipFeature}}
import org.jboss.resteasy.annotations.GZIP;
{{/useGzipFeature}}
Expand Down Expand Up @@ -116,4 +116,4 @@ public {{#interfaceOnly}}interface{{/interfaceOnly}}{{^interfaceOnly}}class{{/in
{{#interfaceOnly}}{{>apiInterface}}{{/interfaceOnly}}{{^interfaceOnly}}{{>apiMethod}}{{/interfaceOnly}}
{{/operation}}
}
{{/operations}}
{{/operations}}
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,5 @@
{{^vendorExtensions.x-java-is-response-void}}@org.eclipse.microprofile.openapi.annotations.media.Content(schema = @org.eclipse.microprofile.openapi.annotations.media.Schema(implementation = {{{baseType}}}.class{{#vendorExtensions.x-microprofile-open-api-return-schema-container}}, type = {{{.}}} {{/vendorExtensions.x-microprofile-open-api-return-schema-container}}{{#vendorExtensions.x-microprofile-open-api-return-unique-items}}, uniqueItems = true {{/vendorExtensions.x-microprofile-open-api-return-unique-items}})){{/vendorExtensions.x-java-is-response-void}}
}){{^-last}},{{/-last}}{{/responses}}
}){{/hasProduces}}{{/useMicroProfileOpenAPIAnnotations}}
{{#supportAsync}}{{>returnAsyncTypeInterface}}{{/supportAsync}}{{^supportAsync}}{{#returnJBossResponse}}{{>returnResponseTypeInterface}}{{/returnJBossResponse}}{{^returnJBossResponse}}{{#returnResponse}}Response{{/returnResponse}}{{^returnResponse}}{{>returnTypeInterface}}{{/returnResponse}}{{/returnJBossResponse}}{{/supportAsync}} {{nickname}}({{#allParams}}{{>queryParams}}{{>pathParams}}{{>cookieParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{^-last}},{{/-last}}{{/allParams}});
{{#vendorExtensions.x-java-success-response-code}}@ResponseStatus({{{vendorExtensions.x-java-success-response-code}}}){{/vendorExtensions.x-java-success-response-code}}
{{#supportAsync}}{{>returnAsyncTypeInterface}}{{/supportAsync}}{{^supportAsync}}{{#returnJBossResponse}}{{>returnResponseTypeInterface}}{{/returnJBossResponse}}{{^returnJBossResponse}}{{#returnResponse}}Response{{/returnResponse}}{{^returnResponse}}{{>returnTypeInterface}}{{/returnResponse}}{{/returnJBossResponse}}{{/supportAsync}} {{nickname}}({{#allParams}}{{>queryParams}}{{>pathParams}}{{>cookieParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{^-last}},{{/-last}}{{/allParams}});
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,4 @@
}){{/hasProduces}}{{/useMicroProfileOpenAPIAnnotations}}
public {{#supportAsync}}{{#useMutiny}}Uni{{/useMutiny}}{{^useMutiny}}CompletionStage{{/useMutiny}}<{{/supportAsync}}{{#returnJBossResponse}}{{>returnResponseTypeInterface}}{{/returnJBossResponse}}{{^returnJBossResponse}}Response{{/returnJBossResponse}}{{#supportAsync}}>{{/supportAsync}} {{nickname}}({{#allParams}}{{>queryParams}}{{>pathParams}}{{>cookieParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{^-last}},{{/-last}}{{/allParams}}) {
return {{#supportAsync}}{{#useMutiny}}Uni.createFrom().item({{/useMutiny}}{{^useMutiny}}CompletableFuture.supplyAsync(() -> {{/useMutiny}}{{/supportAsync}}Response.ok().entity("magic!").build(){{#supportAsync}}){{/supportAsync}};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>
{{#hasResponseStatusAnnotations}}
<dependency>
<groupId>io.quarkus.resteasy.reactive</groupId>
<artifactId>resteasy-reactive</artifactId>
</dependency>
{{/hasResponseStatusAnnotations}}
{{#useJakartaEe}}
{{#returnJBossResponse}}
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ public void generateApiWithAsyncSupportAndInterfaceOnlyAndJBossResponse() throws
//And the generated interface contains CompletionStage<RestResponse<Pet>>
TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/PetApi.java");
assertFileContains(output.toPath().resolve("src/gen/java/org/openapitools/api/PetApi.java"),
"\nimport org.jboss.resteasy.reactive.RestResponse;\n",
"\nimport org.jboss.resteasy.reactive.RestResponse;\n",
"\nimport java.util.concurrent.CompletionStage;\n",
"CompletionStage<RestResponse<Pet>> addPet", "CompletionStage<RestResponse<Void>> deletePet");
}
Expand Down Expand Up @@ -1236,7 +1236,7 @@ public void disableGenerateJsonCreator() throws Exception {

assertFileNotContains(files.get("RequiredProperties.java").toPath(), "@JsonCreator");
}

@Test
public void testDiscriminatorMappingUsedInJsonTypeName() throws Exception {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
Expand Down Expand Up @@ -1273,37 +1273,37 @@ public void testDiscriminatorMappingUsedInJsonTypeName() throws Exception {
public void testGenerateJsonNullableListFieldsHelperMethodReferences_issue23251() throws Exception {
Map<String, Object> properties = new HashMap<>();
properties.put(OPENAPI_NULLABLE, "true");

File output = Files.createTempDirectory("test").toFile();

final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("jaxrs-spec")
.setAdditionalProperties(properties)
.setInputSpec("src/test/resources/bugs/issue_23251.yaml")
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));

final ClientOptInput clientOptInput = configurator.toClientOptInput();
DefaultGenerator generator = new DefaultGenerator();
List<File> files = generator.opts(clientOptInput).generate();

validateJavaSourceFiles(files);

TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/model/BugResponse.java");

// Assert that the generated model contains JsonNullable fields
assertFileContains(output.toPath().resolve("src/gen/java/org/openapitools/model/BugResponse.java"),
"private JsonNullable<String> nullableField = JsonNullable.<String>undefined();",
"private JsonNullable<List<String>> nullableList = JsonNullable.<List<String>>undefined();",
"private JsonNullable<List<@Valid NestedResponse>> nullableObjectList = JsonNullable.<List<@Valid NestedResponse>>undefined();"
);

// Assert that the generated model contains correct add and remove helper methods reference for JsonNullable fields
assertFileContains(output.toPath().resolve("src/gen/java/org/openapitools/model/BugResponse.java"),
"this.nullableList.get().add(nullableListItem);",
"this.nullableList.get().remove(nullableListItem);",
"this.nullableObjectList.get().add(nullableObjectListItem);",
"this.nullableObjectList.get().remove(nullableObjectListItem);");

output.deleteOnExit();
}

Expand Down Expand Up @@ -1342,4 +1342,207 @@ public void generatesDeprecatedAnnotationsForModelsOperationsAndParameters_issue
JavaFileAssert.assertThat(petApi).fileContains("findPetsByStatus", "@Deprecated", "@QueryParam(\"status\")");
}

/**
* Verify that when using the quarkus library with interfaceOnly=true, the generated interface
* method is always annotated with {@code @ResponseStatus(<code>)} for any 2xx or 3xx response,
* including 200, for explicit documentation purposes.
* ping.yaml has a 201 response.
*/
@Test
public void generateQuarkusInterfaceAddsResponseStatusAnnotationForSuccessCode() throws Exception {
final File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();

final OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/ping.yaml", null, new ParseOptions()).getOpenAPI();

codegen.setOutputDir(output.getAbsolutePath());
codegen.setLibrary(QUARKUS_LIBRARY); //Given the quarkus library is used
codegen.additionalProperties().put(INTERFACE_ONLY, true); //And only interfaces are generated
codegen.additionalProperties().put(USE_JAKARTA_EE, true); //Required: @ResponseStatus is only emitted for Jakarta EE (Quarkus 3+)
// returnResponse and returnJBossResponse are both false (defaults)

final ClientOptInput input = new ClientOptInput()
.openAPI(openAPI)
.config(codegen);

final DefaultGenerator generator = new DefaultGenerator();
final List<File> files = generator.opts(input).generate();

validateJavaSourceFiles(files);

//Then the generated interface contains the ResponseStatus import and annotation with code 201
TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/PingApi.java");
assertFileContains(output.toPath().resolve("src/gen/java/org/openapitools/api/PingApi.java"),
"import org.jboss.resteasy.reactive.ResponseStatus;",
"@ResponseStatus(201)");
}

/**
* Verify that {@code @ResponseStatus(200)} IS emitted even for the default 200 status code,
* for explicit documentation purposes.
*/
@Test
public void generateQuarkusInterfaceAddsResponseStatusAnnotationFor200Response() throws Exception {
final File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();

final OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/petstore.yaml", null, new ParseOptions()).getOpenAPI();

codegen.setOutputDir(output.getAbsolutePath());
codegen.setLibrary(QUARKUS_LIBRARY);
codegen.additionalProperties().put(INTERFACE_ONLY, true);
codegen.additionalProperties().put(USE_JAKARTA_EE, true); //Required: @ResponseStatus is only emitted for Jakarta EE (Quarkus 3+)

final ClientOptInput input = new ClientOptInput()
.openAPI(openAPI)
.config(codegen);

final DefaultGenerator generator = new DefaultGenerator();
final List<File> files = generator.opts(input).generate();

validateJavaSourceFiles(files);

//Then @ResponseStatus(200) IS present for explicit documentation
TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/PetApi.java");
assertFileContains(output.toPath().resolve("src/gen/java/org/openapitools/api/PetApi.java"),
"import org.jboss.resteasy.reactive.ResponseStatus;",
"@ResponseStatus(200)");
}


/**
* Verify that the {@code @ResponseStatus} annotation is NOT emitted when returnResponse=true,
* because the user controls the status code via the {@code Response} builder in that mode.
*/
@Test
public void generateQuarkusInterfaceDoesNotAddResponseStatusAnnotationWhenReturnResponse() throws Exception {
final File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();

final OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/ping.yaml", null, new ParseOptions()).getOpenAPI();

codegen.setOutputDir(output.getAbsolutePath());
codegen.setLibrary(QUARKUS_LIBRARY);
codegen.additionalProperties().put(INTERFACE_ONLY, true);
codegen.additionalProperties().put(USE_JAKARTA_EE, true); //Enabled so returnResponse is the only disabling factor
codegen.additionalProperties().put(RETURN_RESPONSE, true); //Given returnResponse is true

final ClientOptInput input = new ClientOptInput()
.openAPI(openAPI)
.config(codegen);

final DefaultGenerator generator = new DefaultGenerator();
final List<File> files = generator.opts(input).generate();

validateJavaSourceFiles(files);

//Then the annotation must NOT appear
TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/PingApi.java");
assertFileNotContains(output.toPath().resolve("src/gen/java/org/openapitools/api/PingApi.java"),
"@ResponseStatus",
"import org.jboss.resteasy.reactive.ResponseStatus");
}

/**
* Verify that the {@code @ResponseStatus} annotation is NOT emitted when returnJBossResponse=true,
* because the caller controls the status code via the {@code RestResponse} wrapper in that mode.
*/
@Test
public void generateQuarkusInterfaceDoesNotAddResponseStatusAnnotationWhenReturnJBossResponse() throws Exception {
final File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();

final OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/ping.yaml", null, new ParseOptions()).getOpenAPI();

codegen.setOutputDir(output.getAbsolutePath());
codegen.setLibrary(QUARKUS_LIBRARY);
codegen.additionalProperties().put(INTERFACE_ONLY, true);
codegen.additionalProperties().put(USE_JAKARTA_EE, true); //Required by returnJBossResponse
codegen.additionalProperties().put(RETURN_JBOSS_RESPONSE, true); //Given returnJBossResponse is true

final ClientOptInput input = new ClientOptInput()
.openAPI(openAPI)
.config(codegen);

final DefaultGenerator generator = new DefaultGenerator();
final List<File> files = generator.opts(input).generate();

validateJavaSourceFiles(files);

//Then the annotation must NOT appear
TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/PingApi.java");
assertFileNotContains(output.toPath().resolve("src/gen/java/org/openapitools/api/PingApi.java"),
"@ResponseStatus",
"import org.jboss.resteasy.reactive.ResponseStatus");
}

/**
* Verify that {@code @ResponseStatus} is NOT emitted when using a non-Quarkus jaxrs-spec library,
* since {@code org.jboss.resteasy.reactive.ResponseStatus} is a RESTEasy Reactive / Quarkus-specific annotation.
*/
@Test
public void generateNonQuarkusInterfaceDoesNotAddResponseStatusAnnotation() throws Exception {
final File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();

final OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/ping.yaml", null, new ParseOptions()).getOpenAPI();

codegen.setOutputDir(output.getAbsolutePath());
// No setLibrary call — uses the default jaxrs-spec library
codegen.additionalProperties().put(INTERFACE_ONLY, true);

final ClientOptInput input = new ClientOptInput()
.openAPI(openAPI)
.config(codegen);

final DefaultGenerator generator = new DefaultGenerator();
final List<File> files = generator.opts(input).generate();

validateJavaSourceFiles(files);

//Then the annotation must NOT appear
TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/PingApi.java");
assertFileNotContains(output.toPath().resolve("src/gen/java/org/openapitools/api/PingApi.java"),
"@ResponseStatus",
"import org.jboss.resteasy.reactive.ResponseStatus");
}

/**
* Verify that the concrete stub class does NOT contain {@code @ResponseStatus} because the
* stub always returns {@code Response}, and RESTEasy Reactive ignores {@code @ResponseStatus}
* when the method returns {@code Response}. The annotation lives on the interface instead.
*/
@Test
public void generateQuarkusConcreteClassDoesNotAddResponseStatusAnnotation() throws Exception {
final File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();

final OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/ping.yaml", null, new ParseOptions()).getOpenAPI();

codegen.setOutputDir(output.getAbsolutePath());
codegen.setLibrary(QUARKUS_LIBRARY);
codegen.additionalProperties().put(USE_JAKARTA_EE, true);
codegen.additionalProperties().put(INTERFACE_ONLY, false); //Given the concrete class is generated

final ClientOptInput input = new ClientOptInput()
.openAPI(openAPI)
.config(codegen);

final DefaultGenerator generator = new DefaultGenerator();
final List<File> files = generator.opts(input).generate();

validateJavaSourceFiles(files);

//Then the concrete class must NOT contain @ResponseStatus (it returns Response, so it would be ignored)
TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/PingApi.java");
assertFileNotContains(output.toPath().resolve("src/gen/java/org/openapitools/api/PingApi.java"),
"@ResponseStatus",
"import org.jboss.resteasy.reactive.ResponseStatus");
}
}
Loading