Skip to content

Commit e591aa0

Browse files
authored
[kotlin-server][JAX-RS] Add useTags to kotlin-server JAXRS code generator (#22970)
* Add `useTags` to kotlin-server JAXRS code generator * Add tests and update samples for useTags in kotlin-server JAXRS codegen * Fix P2 issues: ensure useTags default works and README shows full paths * Check USE_TAGS key presence * regenerate samples
1 parent 846a8c8 commit e591aa0

File tree

15 files changed

+164
-30
lines changed

15 files changed

+164
-30
lines changed

bin/configs/kotlin-server-jaxrs-spec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ inputSpec: modules/openapi-generator/src/test/resources/2_0/petstore.yaml
55
templateDir: modules/openapi-generator/src/main/resources/kotlin-server
66
additionalProperties:
77
useCoroutines: "true"
8+
useTags: "true"

docs/generators/kotlin-server.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
4747
|useCoroutines|Whether to use the Coroutines. This option is currently supported only when using jaxrs-spec library.| |false|
4848
|useJakartaEe|whether to use Jakarta EE namespace instead of javax| |false|
4949
|useMutiny|Whether to use Mutiny (should not be used with useCoroutines). This option is currently supported only when using jaxrs-spec library.| |false|
50+
|useTags|use tags for creating interface and controller classnames| |false|
5051

5152
## IMPORT MAPPING
5253

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ public abstract class AbstractKotlinCodegen extends DefaultCodegen implements Co
6060
public static final String JACKSON2_PACKAGE = "com.fasterxml.jackson";
6161
public static final String JACKSON3_PACKAGE = "tools.jackson";
6262
public static final String JACKSON_PACKAGE = "jacksonPackage";
63+
public static final String USE_TAGS = "useTags";
64+
public static final String USE_TAGS_DESC = "use tags for creating interface and controller classnames";
6365
public static final String SCHEMA_IMPLEMENTS = "schemaImplements";
6466
public static final String SCHEMA_IMPLEMENTS_FIELDS = "schemaImplementsFields";
6567
public static final String X_KOTLIN_IMPLEMENTS_SKIP = "xKotlinImplementsSkip";

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinServerCodegen.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.openapitools.codegen.languages;
1919

2020
import com.google.common.collect.ImmutableMap;
21+
import io.swagger.v3.oas.models.Operation;
2122
import lombok.Getter;
2223
import lombok.Setter;
2324
import org.apache.commons.lang3.StringUtils;
@@ -64,6 +65,7 @@ public class KotlinServerCodegen extends AbstractKotlinCodegen implements BeanVa
6465
private Boolean metricsFeatureEnabled = true;
6566
private boolean interfaceOnly = false;
6667
private boolean useBeanValidation = false;
68+
private boolean useTags = false;
6769
private boolean useCoroutines = false;
6870
private boolean useMutiny = false;
6971
private boolean returnResponse = false;
@@ -97,6 +99,7 @@ public class KotlinServerCodegen extends AbstractKotlinCodegen implements BeanVa
9799
))
98100
.put(Constants.JAXRS_SPEC, Arrays.asList(
99101
USE_BEANVALIDATION,
102+
USE_TAGS,
100103
Constants.USE_COROUTINES,
101104
Constants.USE_MUTINY,
102105
Constants.RETURN_RESPONSE,
@@ -170,6 +173,7 @@ public KotlinServerCodegen() {
170173
addSwitch(Constants.METRICS, Constants.METRICS_DESC, getMetricsFeatureEnabled());
171174
addSwitch(Constants.INTERFACE_ONLY, Constants.INTERFACE_ONLY_DESC, interfaceOnly);
172175
addSwitch(USE_BEANVALIDATION, Constants.USE_BEANVALIDATION_DESC, useBeanValidation);
176+
addSwitch(USE_TAGS, USE_TAGS_DESC, useTags);
173177
addSwitch(Constants.USE_COROUTINES, Constants.USE_COROUTINES_DESC, useCoroutines);
174178
addSwitch(Constants.USE_MUTINY, Constants.USE_MUTINY_DESC, useMutiny);
175179
addSwitch(Constants.RETURN_RESPONSE, Constants.RETURN_RESPONSE_DESC, returnResponse);
@@ -241,6 +245,10 @@ public void processOpts() {
241245
setUseBeanValidation(convertPropertyToBoolean(USE_BEANVALIDATION));
242246
}
243247

248+
if (additionalProperties.containsKey(USE_TAGS)) {
249+
useTags = Boolean.parseBoolean(additionalProperties.get(USE_TAGS).toString());
250+
}
251+
244252
if (additionalProperties.containsKey(Constants.OMIT_GRADLE_WRAPPER)) {
245253
setOmitGradleWrapper(Boolean.parseBoolean(additionalProperties.get(Constants.OMIT_GRADLE_WRAPPER).toString()));
246254
}
@@ -698,6 +706,23 @@ public void postProcess() {
698706
@Override
699707
public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<ModelMap> allModels) {
700708
OperationMap operations = objs.getOperations();
709+
// For JAXRS_SPEC library, compute commonPath similar to JavaJaxRS generators
710+
if (operations != null && Objects.equals(library, Constants.JAXRS_SPEC)) {
711+
String commonPath = null;
712+
List<CodegenOperation> ops = operations.getOperation();
713+
for (CodegenOperation operation : ops) {
714+
if (commonPath == null) {
715+
commonPath = operation.path;
716+
} else {
717+
commonPath = getCommonPath(commonPath, operation.path);
718+
}
719+
}
720+
for (CodegenOperation co : ops) {
721+
co.path = StringUtils.removeStart(co.path, commonPath);
722+
co.subresourceOperation = co.path.length() > 1;
723+
}
724+
objs.put("commonPath", "/".equals(commonPath) ? StringUtils.EMPTY : commonPath);
725+
}
701726
// The following processing breaks the JAX-RS spec, so we only do this for the other libs.
702727
if (operations != null && !Objects.equals(library, Constants.JAXRS_SPEC)) {
703728
List<CodegenOperation> ops = operations.getOperation();
@@ -758,6 +783,24 @@ public void setReturnContainer(final String returnContainer) {
758783
return objs;
759784
}
760785

786+
@Override
787+
public void addOperationToGroup(String tag, String resourcePath, Operation operation, CodegenOperation co, Map<String, List<CodegenOperation>> operations) {
788+
if (Objects.equals(library, Constants.JAXRS_SPEC) && additionalProperties.containsKey(USE_TAGS) && !useTags) {
789+
String basePath = StringUtils.substringBefore(StringUtils.removeStart(resourcePath, "/"), "/");
790+
if (!StringUtils.isEmpty(basePath)) {
791+
co.subresourceOperation = !co.path.isEmpty();
792+
}
793+
co.baseName = basePath;
794+
if (StringUtils.isEmpty(co.baseName) || StringUtils.containsAny(co.baseName, "{", "}")) {
795+
co.baseName = "default";
796+
}
797+
final List<CodegenOperation> opList = operations.computeIfAbsent(co.baseName, k -> new ArrayList<>());
798+
opList.add(co);
799+
} else {
800+
super.addOperationToGroup(tag, resourcePath, operation, co, operations);
801+
}
802+
}
803+
761804
private boolean isJavalin() {
762805
return Constants.JAVALIN5.equals(library) || Constants.JAVALIN6.equals(library);
763806
}
@@ -788,4 +831,17 @@ private boolean isKtor() {
788831
private boolean isKtor2() {
789832
return Constants.KTOR2.equals(library);
790833
}
834+
835+
private static String getCommonPath(String path1, String path2) {
836+
final String[] parts1 = StringUtils.split(path1, "/");
837+
final String[] parts2 = StringUtils.split(path2, "/");
838+
StringBuilder builder = new StringBuilder();
839+
for (int i = 0; i < Math.min(parts1.length, parts2.length); i++) {
840+
if (!parts1[i].equals(parts2[i])) {
841+
break;
842+
}
843+
builder.append("/").append(parts1[i]);
844+
}
845+
return builder.toString();
846+
}
791847
}

modules/openapi-generator/src/main/resources/kotlin-server/README.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ All URIs are relative to *{{{basePath}}}*
3535

3636
Class | Method | HTTP request | Description
3737
------------ | ------------- | ------------- | -------------
38-
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{{summary}}}
38+
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{#commonPath}}{{commonPath}}{{/commonPath}}{{path}} | {{{summary}}}
3939
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
4040
{{/generateApiDocs}}
4141

modules/openapi-generator/src/main/resources/kotlin-server/libraries/jaxrs-spec/api.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {{javaxPackage}}.validation.Valid{{/useBeanValidation}}
1818
@Api(description = "the {{{baseName}}} API"){{/useSwaggerAnnotations}}{{#hasConsumes}}
1919
@Consumes({ {{#consumes}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/consumes}} }){{/hasConsumes}}{{#hasProduces}}
2020
@Produces({ {{#produces}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/produces}} }){{/hasProduces}}
21-
@Path("/")
21+
@Path("{{commonPath}}")
2222
{{>generatedAnnotation}}
2323

2424
{{#interfaceOnly}}interface{{/interfaceOnly}}{{^interfaceOnly}}class{{/interfaceOnly}} {{classname}} {

modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinServerCodegenTest.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import static org.openapitools.codegen.TestUtils.assertFileContains;
3131
import static org.openapitools.codegen.TestUtils.assertFileNotContains;
3232
import static org.openapitools.codegen.languages.AbstractKotlinCodegen.USE_JAKARTA_EE;
33+
import static org.openapitools.codegen.languages.AbstractKotlinCodegen.USE_TAGS;
3334
import static org.openapitools.codegen.languages.KotlinServerCodegen.Constants.*;
3435
import static org.openapitools.codegen.languages.features.BeanValidationFeatures.USE_BEANVALIDATION;
3536

@@ -509,4 +510,60 @@ public void fixJacksonJsonTypeInfoInheritance_canBeDisabled() throws IOException
509510
"visible = false"
510511
);
511512
}
513+
514+
@Test
515+
public void useTags_commonPathIsComputedForJaxrsSpecLibrary() throws IOException {
516+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
517+
output.deleteOnExit();
518+
519+
KotlinServerCodegen codegen = new KotlinServerCodegen();
520+
codegen.setOutputDir(output.getAbsolutePath());
521+
codegen.additionalProperties().put(LIBRARY, JAXRS_SPEC);
522+
codegen.additionalProperties().put(USE_TAGS, true);
523+
524+
new DefaultGenerator().opts(new ClientOptInput()
525+
.openAPI(TestUtils.parseSpec("src/test/resources/2_0/petstore.yaml"))
526+
.config(codegen))
527+
.generate();
528+
529+
String outputPath = output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/server";
530+
Path petApi = Paths.get(outputPath + "/apis/PetApi.kt");
531+
532+
assertFileContains(
533+
petApi,
534+
"@Path(\"/pet\")"
535+
);
536+
assertFileNotContains(
537+
petApi,
538+
"@Path(\"/\")"
539+
);
540+
}
541+
542+
@Test
543+
public void useTags_false_operationsGroupedByPathBaseForJaxrsSpecLibrary() throws IOException {
544+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
545+
output.deleteOnExit();
546+
547+
KotlinServerCodegen codegen = new KotlinServerCodegen();
548+
codegen.setOutputDir(output.getAbsolutePath());
549+
codegen.additionalProperties().put(LIBRARY, JAXRS_SPEC);
550+
codegen.additionalProperties().put(USE_TAGS, false);
551+
552+
new DefaultGenerator().opts(new ClientOptInput()
553+
.openAPI(TestUtils.parseSpec("src/test/resources/2_0/petstore.yaml"))
554+
.config(codegen))
555+
.generate();
556+
557+
String outputPath = output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/server";
558+
Path petApi = Paths.get(outputPath + "/apis/PetApi.kt");
559+
560+
assertFileContains(
561+
petApi,
562+
"class PetApi"
563+
);
564+
assertFileNotContains(
565+
petApi,
566+
"class DefaultApi"
567+
);
568+
}
512569
}

samples/server/others/kotlin-server/jaxrs-spec-array-response/src/main/kotlin/org/openapitools/server/apis/StuffApi.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import java.io.InputStream
1010

1111

1212

13-
@Path("/")
13+
@Path("")
1414
@jakarta.annotation.Generated(value = arrayOf("org.openapitools.codegen.languages.KotlinServerCodegen"), comments = "Generator version: 7.21.0-SNAPSHOT")
1515
interface StuffApi {
1616

samples/server/others/kotlin-server/jaxrs-spec/src/main/kotlin/org/openapitools/server/apis/DefaultApi.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import java.io.InputStream
99

1010

1111

12-
@Path("/")
12+
@Path("/test/parameters/{path_default}/{path_nullable}")
1313
@javax.annotation.Generated(value = arrayOf("org.openapitools.codegen.languages.KotlinServerCodegen"), comments = "Generator version: 7.21.0-SNAPSHOT")
1414
class DefaultApi {
1515

samples/server/petstore/kotlin-server/jaxrs-spec-mutiny/src/main/kotlin/org/openapitools/server/apis/PetApi.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,46 +11,46 @@ import java.io.InputStream
1111

1212

1313

14-
@Path("/")
14+
@Path("/pet")
1515
@javax.annotation.Generated(value = arrayOf("org.openapitools.codegen.languages.KotlinServerCodegen"), comments = "Generator version: 7.21.0-SNAPSHOT")
1616
interface PetApi {
1717

1818
@POST
19-
@Path("/pet")
19+
@Path("")
2020
@Consumes("application/json", "application/xml")
2121
fun addPet( body: Pet): io.smallrye.mutiny.Uni<Response>
2222

2323
@DELETE
24-
@Path("/pet/{petId}")
24+
@Path("/{petId}")
2525
fun deletePet(@PathParam("petId") petId: kotlin.Long,@HeaderParam("api_key") apiKey: kotlin.String?): io.smallrye.mutiny.Uni<Response>
2626

2727
@GET
28-
@Path("/pet/findByStatus")
28+
@Path("/findByStatus")
2929
@Produces("application/xml", "application/json")
3030
fun findPetsByStatus(@QueryParam("status") status: kotlin.collections.List<kotlin.String>): io.smallrye.mutiny.Uni<Response>
3131

3232
@GET
33-
@Path("/pet/findByTags")
33+
@Path("/findByTags")
3434
@Produces("application/xml", "application/json")
3535
fun findPetsByTags(@QueryParam("tags") tags: kotlin.collections.List<kotlin.String>): io.smallrye.mutiny.Uni<Response>
3636

3737
@GET
38-
@Path("/pet/{petId}")
38+
@Path("/{petId}")
3939
@Produces("application/xml", "application/json")
4040
fun getPetById(@PathParam("petId") petId: kotlin.Long): io.smallrye.mutiny.Uni<Response>
4141

4242
@PUT
43-
@Path("/pet")
43+
@Path("")
4444
@Consumes("application/json", "application/xml")
4545
fun updatePet( body: Pet): io.smallrye.mutiny.Uni<Response>
4646

4747
@POST
48-
@Path("/pet/{petId}")
48+
@Path("/{petId}")
4949
@Consumes("application/x-www-form-urlencoded")
5050
fun updatePetWithForm(@PathParam("petId") petId: kotlin.Long,@FormParam(value = "name") name: kotlin.String?,@FormParam(value = "status") status: kotlin.String?): io.smallrye.mutiny.Uni<Response>
5151

5252
@POST
53-
@Path("/pet/{petId}/uploadImage")
53+
@Path("/{petId}/uploadImage")
5454
@Consumes("multipart/form-data")
5555
@Produces("application/json")
5656
fun uploadFile(@PathParam("petId") petId: kotlin.Long,@FormParam(value = "additionalMetadata") additionalMetadata: kotlin.String?, @FormParam(value = "file") fileInputStream: InputStream?): io.smallrye.mutiny.Uni<Response>

0 commit comments

Comments
 (0)