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
1 change: 1 addition & 0 deletions bin/configs/kotlin-server-jaxrs-spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ inputSpec: modules/openapi-generator/src/test/resources/2_0/petstore.yaml
templateDir: modules/openapi-generator/src/main/resources/kotlin-server
additionalProperties:
useCoroutines: "true"
useTags: "true"
1 change: 1 addition & 0 deletions docs/generators/kotlin-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|sourceFolder|source folder for generated code| |src/main/kotlin|
|useBeanValidation|Use BeanValidation API annotations. This option is currently supported only when using jaxrs-spec library.| |false|
|useCoroutines|Whether to use the Coroutines. This option is currently supported only when using jaxrs-spec library.| |false|
|useTags|use tags for creating interface and controller classnames. This option is currently supported only when using jaxrs-spec library.| |false|
|useJakartaEe|whether to use Jakarta EE namespace instead of javax| |false|
|useMutiny|Whether to use Mutiny (should not be used with useCoroutines). This option is currently supported only when using jaxrs-spec library.| |false|

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ public abstract class AbstractKotlinCodegen extends DefaultCodegen implements Co

public static final String JAVAX_PACKAGE = "javaxPackage";
public static final String USE_JAKARTA_EE = "useJakartaEe";
public static final String USE_TAGS = "useTags";
public static final String USE_TAGS_DESC = "use tags for creating interface and controller classnames";
public static final String SCHEMA_IMPLEMENTS = "schemaImplements";
public static final String SCHEMA_IMPLEMENTS_FIELDS = "schemaImplementsFields";
public static final String X_KOTLIN_IMPLEMENTS_SKIP = "xKotlinImplementsSkip";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.openapitools.codegen.languages;

import com.google.common.collect.ImmutableMap;
import io.swagger.v3.oas.models.Operation;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
Expand Down Expand Up @@ -64,6 +65,7 @@ public class KotlinServerCodegen extends AbstractKotlinCodegen implements BeanVa
private Boolean metricsFeatureEnabled = true;
private boolean interfaceOnly = false;
private boolean useBeanValidation = false;
private boolean useTags = false;
private boolean useCoroutines = false;
private boolean useMutiny = false;
private boolean returnResponse = false;
Expand Down Expand Up @@ -97,6 +99,7 @@ public class KotlinServerCodegen extends AbstractKotlinCodegen implements BeanVa
))
.put(Constants.JAXRS_SPEC, Arrays.asList(
USE_BEANVALIDATION,
USE_TAGS,
Constants.USE_COROUTINES,
Constants.USE_MUTINY,
Constants.RETURN_RESPONSE,
Expand Down Expand Up @@ -170,6 +173,7 @@ public KotlinServerCodegen() {
addSwitch(Constants.METRICS, Constants.METRICS_DESC, getMetricsFeatureEnabled());
addSwitch(Constants.INTERFACE_ONLY, Constants.INTERFACE_ONLY_DESC, interfaceOnly);
addSwitch(USE_BEANVALIDATION, Constants.USE_BEANVALIDATION_DESC, useBeanValidation);
addSwitch(USE_TAGS, USE_TAGS_DESC, useTags);
addSwitch(Constants.USE_COROUTINES, Constants.USE_COROUTINES_DESC, useCoroutines);
addSwitch(Constants.USE_MUTINY, Constants.USE_MUTINY_DESC, useMutiny);
addSwitch(Constants.RETURN_RESPONSE, Constants.RETURN_RESPONSE_DESC, returnResponse);
Expand Down Expand Up @@ -241,6 +245,10 @@ public void processOpts() {
setUseBeanValidation(convertPropertyToBoolean(USE_BEANVALIDATION));
}

if (additionalProperties.containsKey(USE_TAGS)) {
useTags = Boolean.parseBoolean(additionalProperties.get(USE_TAGS).toString());
}

if (additionalProperties.containsKey(Constants.OMIT_GRADLE_WRAPPER)) {
setOmitGradleWrapper(Boolean.parseBoolean(additionalProperties.get(Constants.OMIT_GRADLE_WRAPPER).toString()));
}
Expand Down Expand Up @@ -698,6 +706,23 @@ public void postProcess() {
@Override
public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<ModelMap> allModels) {
OperationMap operations = objs.getOperations();
// For JAXRS_SPEC library, compute commonPath similar to JavaJaxRS generators
if (operations != null && Objects.equals(library, Constants.JAXRS_SPEC)) {
String commonPath = null;
List<CodegenOperation> ops = operations.getOperation();
for (CodegenOperation operation : ops) {
if (commonPath == null) {
commonPath = operation.path;
} else {
commonPath = getCommonPath(commonPath, operation.path);
}
}
for (CodegenOperation co : ops) {
co.path = StringUtils.removeStart(co.path, commonPath);
co.subresourceOperation = co.path.length() > 1;
}
objs.put("commonPath", "/".equals(commonPath) ? StringUtils.EMPTY : commonPath);
}
// The following processing breaks the JAX-RS spec, so we only do this for the other libs.
if (operations != null && !Objects.equals(library, Constants.JAXRS_SPEC)) {
List<CodegenOperation> ops = operations.getOperation();
Expand Down Expand Up @@ -758,6 +783,24 @@ public void setReturnContainer(final String returnContainer) {
return objs;
}

@Override
public void addOperationToGroup(String tag, String resourcePath, Operation operation, CodegenOperation co, Map<String, List<CodegenOperation>> operations) {
if (Objects.equals(library, Constants.JAXRS_SPEC) && additionalProperties.containsKey(USE_TAGS) && !useTags) {
String basePath = StringUtils.substringBefore(StringUtils.removeStart(resourcePath, "/"), "/");
if (!StringUtils.isEmpty(basePath)) {
co.subresourceOperation = !co.path.isEmpty();
}
co.baseName = basePath;
if (StringUtils.isEmpty(co.baseName) || StringUtils.containsAny(co.baseName, "{", "}")) {
co.baseName = "default";
}
final List<CodegenOperation> opList = operations.computeIfAbsent(co.baseName, k -> new ArrayList<>());
opList.add(co);
} else {
super.addOperationToGroup(tag, resourcePath, operation, co, operations);
}
}

private boolean isJavalin() {
return Constants.JAVALIN5.equals(library) || Constants.JAVALIN6.equals(library);
}
Expand Down Expand Up @@ -788,4 +831,17 @@ private boolean isKtor() {
private boolean isKtor2() {
return Constants.KTOR2.equals(library);
}

private static String getCommonPath(String path1, String path2) {
final String[] parts1 = StringUtils.split(path1, "/");
final String[] parts2 = StringUtils.split(path2, "/");
StringBuilder builder = new StringBuilder();
for (int i = 0; i < Math.min(parts1.length, parts2.length); i++) {
if (!parts1[i].equals(parts2[i])) {
break;
}
builder.append("/").append(parts1[i]);
}
return builder.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ All URIs are relative to *{{{basePath}}}*

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {{javaxPackage}}.validation.Valid{{/useBeanValidation}}
@Api(description = "the {{{baseName}}} API"){{/useSwaggerAnnotations}}{{#hasConsumes}}
@Consumes({ {{#consumes}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/consumes}} }){{/hasConsumes}}{{#hasProduces}}
@Produces({ {{#produces}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/produces}} }){{/hasProduces}}
@Path("/")
@Path("{{commonPath}}")
{{>generatedAnnotation}}

{{#interfaceOnly}}interface{{/interfaceOnly}}{{^interfaceOnly}}class{{/interfaceOnly}} {{classname}} {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import static org.openapitools.codegen.TestUtils.assertFileContains;
import static org.openapitools.codegen.TestUtils.assertFileNotContains;
import static org.openapitools.codegen.languages.AbstractKotlinCodegen.USE_JAKARTA_EE;
import static org.openapitools.codegen.languages.AbstractKotlinCodegen.USE_TAGS;
import static org.openapitools.codegen.languages.KotlinServerCodegen.Constants.*;
import static org.openapitools.codegen.languages.features.BeanValidationFeatures.USE_BEANVALIDATION;

Expand Down Expand Up @@ -509,4 +510,60 @@ public void fixJacksonJsonTypeInfoInheritance_canBeDisabled() throws IOException
"visible = false"
);
}

@Test
public void useTags_commonPathIsComputedForJaxrsSpecLibrary() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();

KotlinServerCodegen codegen = new KotlinServerCodegen();
codegen.setOutputDir(output.getAbsolutePath());
codegen.additionalProperties().put(LIBRARY, JAXRS_SPEC);
codegen.additionalProperties().put(USE_TAGS, true);

new DefaultGenerator().opts(new ClientOptInput()
.openAPI(TestUtils.parseSpec("src/test/resources/2_0/petstore.yaml"))
.config(codegen))
.generate();

String outputPath = output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/server";
Path petApi = Paths.get(outputPath + "/apis/PetApi.kt");

assertFileContains(
petApi,
"@Path(\"/pet\")"
);
assertFileNotContains(
petApi,
"@Path(\"/\")"
);
}

@Test
public void useTags_false_operationsGroupedByPathBaseForJaxrsSpecLibrary() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();

KotlinServerCodegen codegen = new KotlinServerCodegen();
codegen.setOutputDir(output.getAbsolutePath());
codegen.additionalProperties().put(LIBRARY, JAXRS_SPEC);
codegen.additionalProperties().put(USE_TAGS, false);

new DefaultGenerator().opts(new ClientOptInput()
.openAPI(TestUtils.parseSpec("src/test/resources/2_0/petstore.yaml"))
.config(codegen))
.generate();

String outputPath = output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/server";
Path petApi = Paths.get(outputPath + "/apis/PetApi.kt");

assertFileContains(
petApi,
"class PetApi"
);
assertFileNotContains(
petApi,
"class DefaultApi"
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import java.io.InputStream



@Path("/")
@Path("/pet")
@javax.annotation.Generated(value = arrayOf("org.openapitools.codegen.languages.KotlinServerCodegen"), comments = "Generator version: 7.20.0-SNAPSHOT")
class PetApi {

Expand All @@ -22,23 +22,27 @@ class PetApi {
}

@DELETE
@Path("/{petId}")
suspend fun deletePet(@PathParam("petId") petId: kotlin.Long,@HeaderParam("api_key") apiKey: kotlin.String?): Response {
return Response.ok().entity("magic!").build();
}

@GET
@Path("/findByStatus")
@Produces("application/xml", "application/json")
suspend fun findPetsByStatus(@QueryParam("status") status: kotlin.collections.List<kotlin.String>): Response {
return Response.ok().entity("magic!").build();
}

@GET
@Path("/findByTags")
@Produces("application/xml", "application/json")
suspend fun findPetsByTags(@QueryParam("tags") tags: kotlin.collections.List<kotlin.String>): Response {
return Response.ok().entity("magic!").build();
}

@GET
@Path("/{petId}")
@Produces("application/xml", "application/json")
suspend fun getPetById(@PathParam("petId") petId: kotlin.Long): Response {
return Response.ok().entity("magic!").build();
Expand All @@ -51,12 +55,14 @@ class PetApi {
}

@POST
@Path("/{petId}")
@Consumes("application/x-www-form-urlencoded")
suspend fun updatePetWithForm(@PathParam("petId") petId: kotlin.Long,@FormParam(value = "name") name: kotlin.String?,@FormParam(value = "status") status: kotlin.String?): Response {
return Response.ok().entity("magic!").build();
}

@POST
@Path("/{petId}/uploadImage")
@Consumes("multipart/form-data")
@Produces("application/json")
suspend fun uploadFile(@PathParam("petId") petId: kotlin.Long,@FormParam(value = "additionalMetadata") additionalMetadata: kotlin.String?, @FormParam(value = "file") fileInputStream: InputStream?): Response {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,32 @@ import java.io.InputStream



@Path("/")
@Path("/store")
@javax.annotation.Generated(value = arrayOf("org.openapitools.codegen.languages.KotlinServerCodegen"), comments = "Generator version: 7.20.0-SNAPSHOT")
class StoreApi {

@DELETE
@Path("/order/{orderId}")
suspend fun deleteOrder(@PathParam("orderId") orderId: kotlin.String): Response {
return Response.ok().entity("magic!").build();
}

@GET
@Path("/inventory")
@Produces("application/json")
suspend fun getInventory(): Response {
return Response.ok().entity("magic!").build();
}

@GET
@Path("/order/{orderId}")
@Produces("application/xml", "application/json")
suspend fun getOrderById(@PathParam("orderId") orderId: kotlin.Long): Response {
return Response.ok().entity("magic!").build();
}

@POST
@Path("/order")
@Produces("application/xml", "application/json")
suspend fun placeOrder( body: Order): Response {
return Response.ok().entity("magic!").build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import java.io.InputStream



@Path("/")
@Path("/user")
@javax.annotation.Generated(value = arrayOf("org.openapitools.codegen.languages.KotlinServerCodegen"), comments = "Generator version: 7.20.0-SNAPSHOT")
class UserApi {

Expand All @@ -20,38 +20,45 @@ class UserApi {
}

@POST
@Path("/createWithArray")
suspend fun createUsersWithArrayInput( body: kotlin.collections.List<User>): Response {
return Response.ok().entity("magic!").build();
}

@POST
@Path("/createWithList")
suspend fun createUsersWithListInput( body: kotlin.collections.List<User>): Response {
return Response.ok().entity("magic!").build();
}

@DELETE
@Path("/{username}")
suspend fun deleteUser(@PathParam("username") username: kotlin.String): Response {
return Response.ok().entity("magic!").build();
}

@GET
@Path("/{username}")
@Produces("application/xml", "application/json")
suspend fun getUserByName(@PathParam("username") username: kotlin.String): Response {
return Response.ok().entity("magic!").build();
}

@GET
@Path("/login")
@Produces("application/xml", "application/json")
suspend fun loginUser(@QueryParam("username") username: kotlin.String,@QueryParam("password") password: kotlin.String): Response {
return Response.ok().entity("magic!").build();
}

@GET
@Path("/logout")
suspend fun logoutUser(): Response {
return Response.ok().entity("magic!").build();
}

@PUT
@Path("/{username}")
suspend fun updateUser(@PathParam("username") username: kotlin.String, body: User): Response {
return Response.ok().entity("magic!").build();
}
Expand Down