Skip to content
Merged
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
3 changes: 2 additions & 1 deletion bin/configs/kotlin-gson.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
generatorName: kotlin
outputDir: samples/client/petstore/kotlin-gson
inputSpec: modules/openapi-generator/src/test/resources/2_0/petstore.yaml
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
templateDir: modules/openapi-generator/src/main/resources/kotlin-client
additionalProperties:
serializationLibrary: gson
artifactId: kotlin-petstore-gson
companionObject: true
1 change: 1 addition & 0 deletions docs/generators/kotlin-spring.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|autoXSpringPaginated|Automatically add x-spring-paginated to operations that have 'page', 'size', and 'sort' query parameters. When enabled, operations with all three parameters will have Pageable support automatically applied. Operations with x-spring-paginated explicitly set to false will not be auto-detected.| |false|
|basePackage|base package (invokerPackage) for generated code| |org.openapitools|
|beanQualifiers|Whether to add fully-qualifier class names as bean qualifiers in @Component and @RestController annotations. May be used to prevent bean names clash if multiple generated libraries (contexts) added to single project.| |false|
|companionObject|Whether to generate companion objects in data classes, enabling companion extensions.| |false|
|configPackage|configuration package for generated code| |org.openapitools.configuration|
|declarativeInterfaceReactiveMode|What type of reactive style to use in Spring Http declarative interface|<dl><dt>**coroutines**</dt><dd>Use kotlin-idiomatic 'suspend' functions</dd><dt>**reactor**</dt><dd>Use reactor return wrappers 'Mono' and 'Flux'</dd></dl>|coroutines|
|delegatePattern|Whether to generate the server files using the delegate pattern| |false|
Expand Down
1 change: 1 addition & 0 deletions docs/generators/kotlin.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|artifactId|Generated artifact id (name of jar).| |kotlin-client|
|artifactVersion|Generated artifact's package version.| |1.0.0|
|collectionType|Option. Collection type to use|<dl><dt>**array**</dt><dd>kotlin.Array</dd><dt>**list**</dt><dd>kotlin.collections.List</dd></dl>|list|
|companionObject|Whether to generate companion objects in data classes, enabling companion extensions.| |false|
|dateLibrary|Option. Date library to use|<dl><dt>**threetenbp-localdatetime**</dt><dd>Threetenbp - Backport of JSR310 (jvm only, for legacy app only)</dd><dt>**kotlinx-datetime**</dt><dd>kotlinx-datetime (preferred for multiplatform)</dd><dt>**string**</dt><dd>String</dd><dt>**java8-localdatetime**</dt><dd>Java 8 native JSR310 (jvm only, for legacy app only)</dd><dt>**java8**</dt><dd>Java 8 native JSR310 (jvm only, preferred for jdk 1.8+)</dd><dt>**threetenbp**</dt><dd>Threetenbp - Backport of JSR310 (jvm only, preferred for jdk &lt; 1.8)</dd></dl>|java8|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |original|
|explicitApi|Generates code with explicit access modifiers to comply with Kotlin Explicit API Mode.| |false|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {

public static final String GENERATE_ONEOF_ANYOF_WRAPPERS = "generateOneOfAnyOfWrappers";

public static final String COMPANION_OBJECT = "companionObject";

protected static final String VENDOR_EXTENSION_BASE_NAME_LITERAL = "x-base-name-literal";


Expand All @@ -123,6 +125,7 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
@Setter protected boolean mapFileBinaryToByteArray = false;
@Setter protected boolean generateOneOfAnyOfWrappers = true;
@Getter @Setter protected boolean failOnUnknownProperties = false;
@Setter protected boolean companionObject = false;

protected String authFolder;

Expand Down Expand Up @@ -288,6 +291,8 @@ public KotlinClientCodegen() {

cliOptions.add(CliOption.newBoolean(GENERATE_ONEOF_ANYOF_WRAPPERS, "Generate oneOf, anyOf schemas as wrappers. Only `jvm-retrofit2`(library) with `gson` or `kotlinx_serialization`(serializationLibrary) support this option."));

cliOptions.add(CliOption.newBoolean(COMPANION_OBJECT, "Whether to generate companion objects in data classes, enabling companion extensions.", false));

CliOption serializationLibraryOpt = new CliOption(CodegenConstants.SERIALIZATION_LIBRARY, SERIALIZATION_LIBRARY_DESC);
cliOptions.add(serializationLibraryOpt.defaultValue(serializationLibrary.name()));

Expand Down Expand Up @@ -322,6 +327,10 @@ public boolean getGenerateOneOfAnyOfWrappers() {
return generateOneOfAnyOfWrappers;
}

public boolean getCompanionObject() {
return companionObject;
}

public void setGenerateRoomModels(Boolean generateRoomModels) {
this.generateRoomModels = generateRoomModels;
}
Expand Down Expand Up @@ -484,6 +493,12 @@ public void processOpts() {
setFailOnUnknownProperties(false);
}

if (additionalProperties.containsKey(COMPANION_OBJECT)) {
setCompanionObject(convertPropertyToBooleanAndWriteBack(COMPANION_OBJECT));
} else {
additionalProperties.put(COMPANION_OBJECT, companionObject);
}

commonSupportingFiles();

switch (getLibrary()) {
Expand Down Expand Up @@ -936,7 +951,7 @@ public ModelsMap postProcessModels(ModelsMap objs) {

for (ModelMap mo : objects.getModels()) {
CodegenModel cm = mo.getModel();
if (getGenerateRoomModels() || getGenerateOneOfAnyOfWrappers()) {
if (getGenerateRoomModels() || getGenerateOneOfAnyOfWrappers() || getCompanionObject()) {
cm.vendorExtensions.put("x-has-data-class-body", true);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ public class KotlinSpringServerCodegen extends AbstractKotlinCodegen
public static final String USE_REQUEST_MAPPING_ON_INTERFACE = "useRequestMappingOnInterface";
public static final String AUTO_X_SPRING_PAGINATED = "autoXSpringPaginated";
public static final String USE_SEALED_RESPONSE_INTERFACES = "useSealedResponseInterfaces";
public static final String COMPANION_OBJECT = "companionObject";

@Getter
public enum DeclarativeInterfaceReactiveMode {
Expand Down Expand Up @@ -163,6 +164,7 @@ public String getDescription() {
@Setter private boolean useResponseEntity = true;
@Setter private boolean autoXSpringPaginated = false;
@Setter private boolean useSealedResponseInterfaces = false;
@Setter private boolean companionObject = false;

@Getter @Setter
protected boolean useSpringBoot3 = false;
Expand Down Expand Up @@ -265,6 +267,7 @@ public KotlinSpringServerCodegen() {
addOption(SCHEMA_IMPLEMENTS, "A map of single interface or a list of interfaces per schema name that should be implemented (serves similar purpose as `x-kotlin-implements`, but is fully decoupled from the api spec). Example: yaml `schemaImplements: {Pet: com.some.pack.WithId, Category: [com.some.pack.CategoryInterface], Dog: [com.some.pack.Canine, com.some.pack.OtherInterface]}` implements interfaces in schemas `Pet` (interface `com.some.pack.WithId`), `Category` (interface `com.some.pack.CategoryInterface`), `Dog`(interfaces `com.some.pack.Canine`, `com.some.pack.OtherInterface`)", "empty map");
addOption(SCHEMA_IMPLEMENTS_FIELDS, "A map of single field or a list of fields per schema name that should be prepended with `override` (serves similar purpose as `x-kotlin-implements-fields`, but is fully decoupled from the api spec). Example: yaml `schemaImplementsFields: {Pet: id, Category: [name, id], Dog: [bark, breed]}` marks fields to be prepended with `override` in schemas `Pet` (field `id`), `Category` (fields `name`, `id`) and `Dog` (fields `bark`, `breed`)", "empty map");
addSwitch(AUTO_X_SPRING_PAGINATED, "Automatically add x-spring-paginated to operations that have 'page', 'size', and 'sort' query parameters. When enabled, operations with all three parameters will have Pageable support automatically applied. Operations with x-spring-paginated explicitly set to false will not be auto-detected.", autoXSpringPaginated);
addSwitch(COMPANION_OBJECT, "Whether to generate companion objects in data classes, enabling companion extensions.", companionObject);
supportedLibraries.put(SPRING_BOOT, "Spring-boot Server application.");
supportedLibraries.put(SPRING_CLOUD_LIBRARY,
"Spring-Cloud-Feign client with Spring-Boot auto-configured settings.");
Expand Down Expand Up @@ -512,6 +515,12 @@ public void processOpts() {
}
writePropertyBack(USE_SEALED_RESPONSE_INTERFACES, useSealedResponseInterfaces);

if (additionalProperties.containsKey(COMPANION_OBJECT)) {
this.setCompanionObject(convertPropertyToBooleanAndWriteBack(COMPANION_OBJECT));
} else {
additionalProperties.put(COMPANION_OBJECT, companionObject);
}

additionalProperties.put("springHttpStatus", new SpringHttpStatusLambda());

// Set basePackage from invokerPackage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ import {{packageName}}.infrastructure.ITransformForStorage
private const val serialVersionUID: Long = 123
}
{{/serializableModel}}
{{#discriminator}}{{#vars}}{{#required}}
{{^serializableModel}}{{^generateRoomModels}}{{^generateOneOfAnyOfWrappers}}{{#companionObject}} {{#nonPublicApi}}internal {{/nonPublicApi}}{{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}{{/nonPublicApi}}companion object { }
{{/companionObject}}{{/generateOneOfAnyOfWrappers}}{{/generateRoomModels}}{{/serializableModel}}{{#discriminator}}{{#vars}}{{#required}}
{{>interface_req_var}}{{/required}}{{^required}}
{{>interface_opt_var}}{{/required}}{{/vars}}{{/discriminator}}
{{#hasEnums}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,5 @@
private const val serialVersionUID: kotlin.Long = 1
}
{{/serializableModel}}
}
{{^serializableModel}}{{#companionObject}} companion object { }
{{/companionObject}}{{/serializableModel}}}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import java.util.Map;

import static org.openapitools.codegen.CodegenConstants.*;
import static org.openapitools.codegen.languages.KotlinClientCodegen.COMPANION_OBJECT;
import static org.openapitools.codegen.languages.KotlinClientCodegen.GENERATE_ONEOF_ANYOF_WRAPPERS;

@SuppressWarnings("static-method")
Expand Down Expand Up @@ -878,6 +879,50 @@ public void emptyModelKotlinxSerializationTest() throws IOException {
TestUtils.assertFileNotContains(modelKt, "data class EmptyModel");
}

@Test
public void testCompanionObjectAdditionalProperty() {
final KotlinClientCodegen codegen = new KotlinClientCodegen();

// Default case, nothing provided
codegen.processOpts();

ConfigAssert configAssert = new ConfigAssert(codegen.additionalProperties());
// Default to false
configAssert.assertValue(COMPANION_OBJECT, codegen::getCompanionObject, Boolean.FALSE);

// Provide true
codegen.additionalProperties().put(COMPANION_OBJECT, true);
codegen.processOpts();

// Should be true
configAssert.assertValue(COMPANION_OBJECT, codegen::getCompanionObject, Boolean.TRUE);

// Provide false
codegen.additionalProperties().put(COMPANION_OBJECT, false);
codegen.processOpts();

// Should be false
configAssert.assertValue(COMPANION_OBJECT, codegen::getCompanionObject, Boolean.FALSE);
}

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

final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("kotlin")
.addAdditionalProperty(COMPANION_OBJECT, true)
.setInputSpec("src/test/resources/3_0/petstore.yaml")
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));

DefaultGenerator generator = new DefaultGenerator();
generator.opts(configurator.toClientOptInput()).generate();

Path petModel = Paths.get(output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/client/models/Pet.kt");
TestUtils.assertFileContains(petModel, "companion object { }");
}

private static class ModelNameTest {
private final String expectedName;
private final String expectedClassName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4706,6 +4706,35 @@ public void testDeprecatedAnnotationOnController() throws IOException {
"@Deprecated(message=\"Operation is deprecated\") @RequestMapping("
);
}

@Test
public void testCompanionObjectDefaultIsFalse() {
final KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen();
codegen.processOpts();

Assert.assertEquals(codegen.additionalProperties().get(COMPANION_OBJECT), false);
}

@Test
public void testCompanionObjectGeneratesCompanionInModel() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();
String outputPath = output.getAbsolutePath().replace('\\', '/');

KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen();
codegen.setOutputDir(output.getAbsolutePath());
codegen.additionalProperties().put(COMPANION_OBJECT, true);

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

assertFileContains(
Paths.get(outputPath + "/src/main/kotlin/org/openapitools/model/Pet.kt"),
"companion object { }"
);
}
}


36 changes: 24 additions & 12 deletions samples/client/petstore/kotlin-gson/docs/PetApi.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,23 @@ All URIs are relative to *http://petstore.swagger.io/v2*

<a id="addPet"></a>
# **addPet**
> addPet(body)
> Pet addPet(pet)

Add a new pet to the store



### Example
```kotlin
// Import classes:
//import org.openapitools.client.infrastructure.*
//import org.openapitools.client.models.*

val apiInstance = PetApi()
val body : Pet = // Pet | Pet object that needs to be added to the store
val pet : Pet = // Pet | Pet object that needs to be added to the store
try {
apiInstance.addPet(body)
val result : Pet = apiInstance.addPet(pet)
println(result)
} catch (e: ClientException) {
println("4xx response calling PetApi#addPet")
e.printStackTrace()
Expand All @@ -42,11 +45,11 @@ try {
### Parameters
| Name | Type | Description | Notes |
| ------------- | ------------- | ------------- | ------------- |
| **body** | [**Pet**](Pet.md)| Pet object that needs to be added to the store | |
| **pet** | [**Pet**](Pet.md)| Pet object that needs to be added to the store | |

### Return type

null (empty response body)
[**Pet**](Pet.md)

### Authorization

Expand All @@ -57,14 +60,16 @@ Configure petstore_auth:
### HTTP request headers

- **Content-Type**: application/json
- **Accept**: Not defined
- **Accept**: application/json

<a id="deletePet"></a>
# **deletePet**
> deletePet(petId, apiKey)

Deletes a pet



### Example
```kotlin
// Import classes:
Expand Down Expand Up @@ -253,20 +258,23 @@ Configure api_key:

<a id="updatePet"></a>
# **updatePet**
> updatePet(body)
> Pet updatePet(pet)

Update an existing pet



### Example
```kotlin
// Import classes:
//import org.openapitools.client.infrastructure.*
//import org.openapitools.client.models.*

val apiInstance = PetApi()
val body : Pet = // Pet | Pet object that needs to be added to the store
val pet : Pet = // Pet | Pet object that needs to be added to the store
try {
apiInstance.updatePet(body)
val result : Pet = apiInstance.updatePet(pet)
println(result)
} catch (e: ClientException) {
println("4xx response calling PetApi#updatePet")
e.printStackTrace()
Expand All @@ -279,11 +287,11 @@ try {
### Parameters
| Name | Type | Description | Notes |
| ------------- | ------------- | ------------- | ------------- |
| **body** | [**Pet**](Pet.md)| Pet object that needs to be added to the store | |
| **pet** | [**Pet**](Pet.md)| Pet object that needs to be added to the store | |

### Return type

null (empty response body)
[**Pet**](Pet.md)

### Authorization

Expand All @@ -294,14 +302,16 @@ Configure petstore_auth:
### HTTP request headers

- **Content-Type**: application/json
- **Accept**: Not defined
- **Accept**: application/json

<a id="updatePetWithForm"></a>
# **updatePetWithForm**
> updatePetWithForm(petId, name, status)

Updates a pet in the store with form data



### Example
```kotlin
// Import classes:
Expand Down Expand Up @@ -351,6 +361,8 @@ Configure petstore_auth:

uploads an image



### Example
```kotlin
// Import classes:
Expand Down
Loading
Loading