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,19 @@ 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) {
for (CodegenOperation op : objs.getOperations().getOperation()) {
op.responses.stream()
.filter(r -> r.is2xx || r.is3xx)
.findFirst()
.ifPresent(r -> op.vendorExtensions.put("x-java-success-response-code", r.code));
}
if (objs.getOperations().getOperation().stream()
.anyMatch(op -> op.vendorExtensions.containsKey("x-java-success-response-code"))) {
objs.put("hasResponseStatusAnnotations", true);
additionalProperties.put("hasResponseStatusAnnotations", true);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Global additionalProperties flag reuses a per-file key, allowing hasResponseStatusAnnotations to leak across API renders and trigger incorrect class-level conditional output.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaJAXRSSpecServerCodegen.java, line 356:

<comment>Global `additionalProperties` flag reuses a per-file key, allowing `hasResponseStatusAnnotations` to leak across API renders and trigger incorrect class-level conditional output.</comment>

<file context>
@@ -353,6 +353,7 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<Mo
             if (objs.getOperations().getOperation().stream()
                     .anyMatch(op -> op.vendorExtensions.containsKey("x-java-success-response-code"))) {
                 objs.put("hasResponseStatusAnnotations", true);
+                additionalProperties.put("hasResponseStatusAnnotations", true);
             }
         }
</file context>

}
}
return objs;
}

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

{{#useGzipFeature}}
import org.jboss.resteasy.annotations.GZIP;
Expand Down Expand Up @@ -116,4 +117,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 @@ -44,4 +44,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 @@ -44,6 +44,7 @@
{{^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}}
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}}) {
{{#vendorExtensions.x-java-success-response-code}}@ResponseStatus({{{vendorExtensions.x-java-success-response-code}}})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Raw injection of success response code into @ResponseStatus(...) can generate invalid Java for range keys like 2XX, causing compile failures.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/JavaJaxRS/spec/libraries/quarkus/apiMethod.mustache, line 47:

<comment>Raw injection of success response code into `@ResponseStatus(...)` can generate invalid Java for range keys like `2XX`, causing compile failures.</comment>

<file context>
@@ -44,6 +44,7 @@
             }){{^-last}},{{/-last}}{{/responses}}
         }){{/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}}) {
+    {{#vendorExtensions.x-java-success-response-code}}@ResponseStatus({{{vendorExtensions.x-java-success-response-code}}})
+    {{/vendorExtensions.x-java-success-response-code}}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}};
</file context>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: @ResponseStatus is emitted on Quarkus stub methods that return explicit Response, where Quarkus ignores the annotation; this can produce misleading generated status contracts.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/JavaJaxRS/spec/libraries/quarkus/apiMethod.mustache, line 47:

<comment>`@ResponseStatus` is emitted on Quarkus stub methods that return explicit `Response`, where Quarkus ignores the annotation; this can produce misleading generated status contracts.</comment>

<file context>
@@ -44,6 +44,7 @@
             }){{^-last}},{{/-last}}{{/responses}}
         }){{/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}}) {
+    {{#vendorExtensions.x-java-success-response-code}}@ResponseStatus({{{vendorExtensions.x-java-success-response-code}}})
+    {{/vendorExtensions.x-java-success-response-code}}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}};
</file context>

{{/vendorExtensions.x-java-success-response-code}}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 @@ -90,6 +90,14 @@
<groupId>io.quarkus.resteasy.reactive</groupId>
<artifactId>resteasy-reactive</artifactId>
</dependency>
{{/returnJBossResponse}}
{{^returnJBossResponse}}
{{#hasResponseStatusAnnotations}}
<dependency>
<groupId>io.quarkus.resteasy.reactive</groupId>
<artifactId>resteasy-reactive</artifactId>
</dependency>
{{/hasResponseStatusAnnotations}}
{{/returnJBossResponse}}
<dependency>
<groupId>jakarta.ws.rs</groupId>
Expand All @@ -104,6 +112,20 @@
</dependency>
{{/useJakartaEe}}
{{^useJakartaEe}}
{{#returnJBossResponse}}
<dependency>
<groupId>io.quarkus.resteasy.reactive</groupId>
<artifactId>resteasy-reactive</artifactId>
</dependency>
{{/returnJBossResponse}}
{{^returnJBossResponse}}
{{#hasResponseStatusAnnotations}}
<dependency>
<groupId>io.quarkus.resteasy.reactive</groupId>
<artifactId>resteasy-reactive</artifactId>
</dependency>
{{/hasResponseStatusAnnotations}}
{{/returnJBossResponse}}
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
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,38 +1273,170 @@ 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();
}

/**
* 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
// 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);

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(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 when using the quarkus library with interfaceOnly=true and a 3xx response,
* the generated interface method is annotated with {@code @ResponseStatus(<code>)}.
*/
@Test
public void generateQuarkusInterfaceAddsResponseStatusAnnotationFor3xxResponseCode() throws Exception {
final File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();

final OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/jaxrs-spec-quarkus-redirect.yaml", null, new ParseOptions()).getOpenAPI();

codegen.setOutputDir(output.getAbsolutePath());
codegen.setLibrary(QUARKUS_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 generated interface contains the ResponseStatus import and annotation with code 302
TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/RedirectApi.java");
assertFileContains(output.toPath().resolve("src/gen/java/org/openapitools/api/RedirectApi.java"),
"import org.jboss.resteasy.reactive.ResponseStatus;",
"@ResponseStatus(302)");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
openapi: 3.0.1
info:
title: redirect test
version: '1.0'
servers:
- url: 'http://localhost:8000/'
paths:
/redirect:
get:
operationId: redirectGet
responses:
'302':
description: Temporary Redirect
headers:
Location:
schema:
type: string

Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus.resteasy.reactive</groupId>
<artifactId>resteasy-reactive</artifactId>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
Expand Down
Loading