Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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-array-response.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ templateDir: modules/openapi-generator/src/main/resources/kotlin-server
additionalProperties:
interfaceOnly: "true"
useJakartaEe: true
useTags: "true"
1 change: 1 addition & 0 deletions bin/configs/kotlin-server-jaxrs-spec-parameter-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ inputSpec: modules/openapi-generator/src/test/resources/3_0/parameter-test-spec.
templateDir: modules/openapi-generator/src/main/resources/kotlin-server
additionalProperties:
useCoroutines: "true"
useTags: "true"
1 change: 0 additions & 1 deletion bin/configs/kotlin-server-jaxrs-spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +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"
implicitHeaders: "true"
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,36 @@
package org.openapitools.codegen.languages;

import com.google.common.collect.ImmutableMap;
import io.swagger.v3.oas.models.Operation;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.*;
import org.openapitools.codegen.CodegenConstants;
import org.openapitools.codegen.CodegenDiscriminator;
import org.openapitools.codegen.CodegenModel;
import org.openapitools.codegen.CodegenOperation;
import org.openapitools.codegen.CodegenParameter;
import org.openapitools.codegen.CodegenProperty;
import org.openapitools.codegen.CodegenResponse;
import org.openapitools.codegen.CodegenType;
import org.openapitools.codegen.SupportingFile;
import org.openapitools.codegen.languages.features.BeanValidationFeatures;
import org.openapitools.codegen.meta.features.*;
import org.openapitools.codegen.meta.features.DocumentationFeature;
import org.openapitools.codegen.meta.features.GlobalFeature;
import org.openapitools.codegen.meta.features.ParameterFeature;
import org.openapitools.codegen.meta.features.SchemaSupportFeature;
import org.openapitools.codegen.meta.features.SecurityFeature;
import org.openapitools.codegen.meta.features.WireFormatFeature;
import org.openapitools.codegen.model.ModelMap;
import org.openapitools.codegen.model.ModelsMap;
import org.openapitools.codegen.model.OperationMap;
Expand All @@ -33,9 +57,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.*;

public class KotlinServerCodegen extends AbstractKotlinCodegen implements BeanValidationFeatures {

public static final String DEFAULT_LIBRARY = Constants.KTOR;
Expand Down Expand Up @@ -702,32 +723,45 @@ public void postProcess() {
System.out.println("################################################################################");
}

@Override
public void addOperationToGroup(String tag, String resourcePath, Operation operation, CodegenOperation co, Map<String, List<CodegenOperation>> operations) {
if (!Objects.equals(library, Constants.JAXRS_SPEC) || useTags) {
super.addOperationToGroup(tag, resourcePath, operation, co, operations);
return;
}

final String basePath = StringUtils.substringBefore(resourcePath.startsWith("/") ? resourcePath.substring(1) : resourcePath, "/");
if (!StringUtils.isEmpty(basePath)) {
co.subresourceOperation = !co.path.isEmpty();
}
co.baseName = basePath;
if (StringUtils.isEmpty(co.baseName) || co.baseName.chars().anyMatch(ch -> ch == '{' || ch == '}')) {
co.baseName = "default";
}
final List<CodegenOperation> opList = operations.computeIfAbsent(co.baseName, k -> new ArrayList<>());
opList.add(co);
}

@Override
public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<ModelMap> allModels) {
OperationMap operations = objs.getOperations();
// For JAXRS_SPEC library, compute commonPath when useTags=true, otherwise default to "/"
// For JAXRS_SPEC library, compute commonPath for all library modes
if (operations != null && Objects.equals(library, Constants.JAXRS_SPEC)) {
if (useTags) {
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);
} else {
for (CodegenOperation co : operations.getOperation()) {
co.subresourceOperation = !co.path.isEmpty();
List<CodegenOperation> ops = operations.getOperation();
// Compute commonPath from operations in this group (called once per API class)
String commonPath = null;
for (CodegenOperation operation : ops) {
if (commonPath == null) {
commonPath = operation.path;
} else {
commonPath = getCommonPath(commonPath, operation.path);
}
objs.put("commonPath", "/");
}
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)) {
Expand Down Expand Up @@ -801,8 +835,8 @@ private boolean isJavalin() {
*/
private boolean usesJacksonSerialization() {
return Constants.JAVALIN5.equals(library) ||
Constants.JAVALIN6.equals(library) ||
Constants.JAXRS_SPEC.equals(library);
Constants.JAVALIN6.equals(library) ||
Constants.JAXRS_SPEC.equals(library);
}

private boolean isKtor2Or3() {
Expand Down
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}}** {{#useTags}}{{commonPath}}{{/useTags}}{{path}} | {{{summary}}}
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{commonPath}}{{path}} | {{{summary}}}
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.

It seems operationIdLowerCase is not set in the useTags=false case - probably because the super method that sets it is never called.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Nice catch, fixed in c3e559b

{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
{{/generateApiDocs}}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@{{httpMethod}}
@Path("{{{path}}}"){{#hasConsumes}}
@{{httpMethod}}{{#subresourceOperation}}
@Path("{{{path}}}"){{/subresourceOperation}}{{#hasConsumes}}
@Consumes({{#consumes}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/consumes}}){{/hasConsumes}}{{#hasProduces}}
@Produces({{#produces}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/produces}}){{/hasProduces}}
{{#useCoroutines}}suspend {{/useCoroutines}}fun {{nickname}}({{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{^-last}},{{/-last}}{{/allParams}}){{#returnResponse}}: {{#useMutiny}}io.smallrye.mutiny.Uni<{{/useMutiny}}Response{{#useMutiny}}>{{/useMutiny}}{{/returnResponse}}{{^returnResponse}}{{#returnType}}: {{#useMutiny}}io.smallrye.mutiny.Uni<{{/useMutiny}}{{{returnType}}}{{#useMutiny}}>{{/useMutiny}}{{/returnType}}{{/returnResponse}}{{^returnResponse}}{{^returnType}}{{#useMutiny}}: io.smallrye.mutiny.Uni<Void>{{/useMutiny}}{{/returnType}}{{/returnResponse}}
Original file line number Diff line number Diff line change
@@ -1,37 +1,41 @@
package org.openapitools.codegen.kotlin;

import lombok.Getter;
import org.antlr.v4.runtime.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.checkerframework.checker.units.qual.C;
import org.openapitools.codegen.ClientOptInput;
import org.openapitools.codegen.CodegenConstants;
import org.openapitools.codegen.DefaultGenerator;
import org.openapitools.codegen.TestUtils;
import org.openapitools.codegen.antlr4.KotlinLexer;
import org.openapitools.codegen.antlr4.KotlinParser;
import org.openapitools.codegen.antlr4.KotlinParserBaseListener;
import org.openapitools.codegen.languages.KotlinServerCodegen;
import org.openapitools.codegen.languages.KotlinSpringServerCodegen;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

import static org.openapitools.codegen.CodegenConstants.*;
import static org.openapitools.codegen.CodegenConstants.API_PACKAGE;
import static org.openapitools.codegen.CodegenConstants.LIBRARY;
import static org.openapitools.codegen.CodegenConstants.MODEL_PACKAGE;
import static org.openapitools.codegen.CodegenConstants.PACKAGE_NAME;
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.KotlinServerCodegen.Constants.INTERFACE_ONLY;
import static org.openapitools.codegen.languages.KotlinServerCodegen.Constants.JAVALIN5;
import static org.openapitools.codegen.languages.KotlinServerCodegen.Constants.JAVALIN6;
import static org.openapitools.codegen.languages.KotlinServerCodegen.Constants.JAXRS_SPEC;
import static org.openapitools.codegen.languages.KotlinServerCodegen.Constants.RETURN_RESPONSE;
import static org.openapitools.codegen.languages.features.BeanValidationFeatures.USE_BEANVALIDATION;

public class KotlinServerCodegenTest {
Expand Down Expand Up @@ -222,6 +226,7 @@ public void issue18177Arrays() throws IOException {
codegen.additionalProperties().put(INTERFACE_ONLY, true);
codegen.additionalProperties().put(USE_JAKARTA_EE, true);
codegen.additionalProperties().put(LIBRARY, JAXRS_SPEC);
codegen.additionalProperties().put(USE_TAGS, true);
new DefaultGenerator().opts(new ClientOptInput()
.openAPI(TestUtils.parseSpec("src/test/resources/3_0/kotlin/issue18177-array.yaml"))
.config(codegen))
Expand Down Expand Up @@ -559,9 +564,9 @@ public void useTags_false_classNameFromTagsAndRootPathForJaxrsSpecLibrary() thro

assertFileContains(petApi,
"class PetApi",
"@Path(\"/\")",
"@Path(\"/pet\")",
"@Path(\"/pet/{petId}\")"
"@Path(\"/findByStatus\")",
"@Path(\"/{petId}\")"
);
assertFileNotContains(petApi, "@Path(\"/pet\")".replace("/pet", "/store"));
}
Expand All @@ -586,9 +591,9 @@ public void useTags_notSpecified_behavesLikeUseTagsFalseForJaxrsSpecLibrary() th

assertFileContains(petApi,
"class PetApi",
"@Path(\"/\")",
"@Path(\"/pet\")",
"@Path(\"/pet/{petId}\")"
"@Path(\"/findByStatus\")",
"@Path(\"/{petId}\")"
);
assertFileNotContains(petApi, "@Path(\"/store\")");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import java.io.InputStream



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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ import java.io.InputStream



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

@GET
@Path("/test/parameters/{path_default}/{path_nullable}")
suspend fun findPetsByStatus(@PathParam("path_default") pathDefault: kotlin.String,@PathParam("path_nullable") pathNullable: kotlin.String,@QueryParam("query_default") @DefaultValue("available") queryDefault: kotlin.String,@QueryParam("query_default_enum") @DefaultValue("B") queryDefaultEnum: kotlin.String,@QueryParam("query_default_int") @DefaultValue("3") queryDefaultInt: java.math.BigDecimal,@HeaderParam("header_default") @DefaultValue("available") headerDefault: kotlin.String,@HeaderParam("header_default_enum") @DefaultValue("B") headerDefaultEnum: kotlin.String,@HeaderParam("header_default_int") @DefaultValue("3") headerDefaultInt: java.math.BigDecimal,@CookieParam("cookie_default") @DefaultValue("available") cookieDefault: kotlin.String,@CookieParam("cookie_default_enum") @DefaultValue("B") cookieDefaultEnum: kotlin.String,@CookieParam("cookie_default_int") @DefaultValue("3") cookieDefaultInt: java.math.BigDecimal,@QueryParam("query_nullable") queryNullable: kotlin.String?,@HeaderParam("header_nullable") headerNullable: kotlin.String?,@CookieParam("cookie_nullable") cookieNullable: kotlin.String?,@QueryParam("\$query-\$dollar-sign") dollarQueryDollarDollarSign: kotlin.String?): Response {
return Response.ok().entity("magic!").build();
}
Expand Down
40 changes: 20 additions & 20 deletions samples/server/petstore/kotlin-server/jaxrs-spec-mutiny/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,26 @@ All URIs are relative to *http://petstore.swagger.io/v2*

Class | Method | HTTP request | Description
------------ | ------------- | ------------- | -------------
*PetApi* | [**addPet**](docs/PetApi.md#addpet) | **POST** /pet | Add a new pet to the store
*PetApi* | [**deletePet**](docs/PetApi.md#deletepet) | **DELETE** /pet/{petId} | Deletes a pet
*PetApi* | [**findPetsByStatus**](docs/PetApi.md#findpetsbystatus) | **GET** /pet/findByStatus | Finds Pets by status
*PetApi* | [**findPetsByTags**](docs/PetApi.md#findpetsbytags) | **GET** /pet/findByTags | Finds Pets by tags
*PetApi* | [**getPetById**](docs/PetApi.md#getpetbyid) | **GET** /pet/{petId} | Find pet by ID
*PetApi* | [**updatePet**](docs/PetApi.md#updatepet) | **PUT** /pet | Update an existing pet
*PetApi* | [**updatePetWithForm**](docs/PetApi.md#updatepetwithform) | **POST** /pet/{petId} | Updates a pet in the store with form data
*PetApi* | [**uploadFile**](docs/PetApi.md#uploadfile) | **POST** /pet/{petId}/uploadImage | uploads an image
*StoreApi* | [**deleteOrder**](docs/StoreApi.md#deleteorder) | **DELETE** /store/order/{orderId} | Delete purchase order by ID
*StoreApi* | [**getInventory**](docs/StoreApi.md#getinventory) | **GET** /store/inventory | Returns pet inventories by status
*StoreApi* | [**getOrderById**](docs/StoreApi.md#getorderbyid) | **GET** /store/order/{orderId} | Find purchase order by ID
*StoreApi* | [**placeOrder**](docs/StoreApi.md#placeorder) | **POST** /store/order | Place an order for a pet
*UserApi* | [**createUser**](docs/UserApi.md#createuser) | **POST** /user | Create user
*UserApi* | [**createUsersWithArrayInput**](docs/UserApi.md#createuserswitharrayinput) | **POST** /user/createWithArray | Creates list of users with given input array
*UserApi* | [**createUsersWithListInput**](docs/UserApi.md#createuserswithlistinput) | **POST** /user/createWithList | Creates list of users with given input array
*UserApi* | [**deleteUser**](docs/UserApi.md#deleteuser) | **DELETE** /user/{username} | Delete user
*UserApi* | [**getUserByName**](docs/UserApi.md#getuserbyname) | **GET** /user/{username} | Get user by user name
*UserApi* | [**loginUser**](docs/UserApi.md#loginuser) | **GET** /user/login | Logs user into the system
*UserApi* | [**logoutUser**](docs/UserApi.md#logoutuser) | **GET** /user/logout | Logs out current logged in user session
*UserApi* | [**updateUser**](docs/UserApi.md#updateuser) | **PUT** /user/{username} | Updated user
*PetApi* | [**addPet**](docs/PetApi.md#) | **POST** /pet | Add a new pet to the store
*PetApi* | [**deletePet**](docs/PetApi.md#) | **DELETE** /pet/{petId} | Deletes a pet
*PetApi* | [**findPetsByStatus**](docs/PetApi.md#) | **GET** /pet/findByStatus | Finds Pets by status
*PetApi* | [**findPetsByTags**](docs/PetApi.md#) | **GET** /pet/findByTags | Finds Pets by tags
*PetApi* | [**getPetById**](docs/PetApi.md#) | **GET** /pet/{petId} | Find pet by ID
*PetApi* | [**updatePet**](docs/PetApi.md#) | **PUT** /pet | Update an existing pet
*PetApi* | [**updatePetWithForm**](docs/PetApi.md#) | **POST** /pet/{petId} | Updates a pet in the store with form data
*PetApi* | [**uploadFile**](docs/PetApi.md#) | **POST** /pet/{petId}/uploadImage | uploads an image
*StoreApi* | [**deleteOrder**](docs/StoreApi.md#) | **DELETE** /store/order/{orderId} | Delete purchase order by ID
*StoreApi* | [**getInventory**](docs/StoreApi.md#) | **GET** /store/inventory | Returns pet inventories by status
*StoreApi* | [**getOrderById**](docs/StoreApi.md#) | **GET** /store/order/{orderId} | Find purchase order by ID
*StoreApi* | [**placeOrder**](docs/StoreApi.md#) | **POST** /store/order | Place an order for a pet
*UserApi* | [**createUser**](docs/UserApi.md#) | **POST** /user | Create user
*UserApi* | [**createUsersWithArrayInput**](docs/UserApi.md#) | **POST** /user/createWithArray | Creates list of users with given input array
*UserApi* | [**createUsersWithListInput**](docs/UserApi.md#) | **POST** /user/createWithList | Creates list of users with given input array
*UserApi* | [**deleteUser**](docs/UserApi.md#) | **DELETE** /user/{username} | Delete user
*UserApi* | [**getUserByName**](docs/UserApi.md#) | **GET** /user/{username} | Get user by user name
*UserApi* | [**loginUser**](docs/UserApi.md#) | **GET** /user/login | Logs user into the system
*UserApi* | [**logoutUser**](docs/UserApi.md#) | **GET** /user/logout | Logs out current logged in user session
*UserApi* | [**updateUser**](docs/UserApi.md#) | **PUT** /user/{username} | Updated user


<a id="documentation-for-models"></a>
Expand Down
Loading
Loading