diff --git a/docs/generators/kotlin.md b/docs/generators/kotlin.md index 82011d0e54b..7dbee25f2f7 100644 --- a/docs/generators/kotlin.md +++ b/docs/generators/kotlin.md @@ -51,6 +51,14 @@ These options may be applied as additional-properties (cli) or configOptions (pl |useSettingsGradle|Whether the project uses settings.gradle.| |false| |useSpringBoot3|Whether to use the Spring Boot 3 with the jvm-spring-webclient library.| |false| +## SUPPORTED VENDOR EXTENSIONS + +| Extension name | Description | Applicable for | Default value | +| -------------- | ----------- | -------------- | ------------- | +|x-class-extra-annotation|List of custom annotations to be added to model|MODEL|null +|x-field-extra-annotation|List of custom annotations to be added to property|FIELD, OPERATION_PARAMETER|null + + ## IMPORT MAPPING | Type/Alias | Imports | diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java index 1310e286191..69258bdf092 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java @@ -38,6 +38,7 @@ import org.openapitools.codegen.CodegenParameter; import org.openapitools.codegen.CodegenProperty; import org.openapitools.codegen.CodegenType; import org.openapitools.codegen.SupportingFile; +import org.openapitools.codegen.VendorExtension; import org.openapitools.codegen.meta.features.ClientModificationFeature; import org.openapitools.codegen.meta.features.DocumentationFeature; import org.openapitools.codegen.meta.features.GlobalFeature; @@ -1049,4 +1050,12 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen { System.out.println("# Please support his work directly via https://patreon.com/jimschubert \uD83D\uDE4F #"); System.out.println("################################################################################"); } + + @Override + public List getSupportedVendorExtensions() { + var extensions = super.getSupportedVendorExtensions(); + extensions.add(VendorExtension.X_CLASS_EXTRA_ANNOTATION); + extensions.add(VendorExtension.X_FIELD_EXTRA_ANNOTATION); + return extensions; + } } diff --git a/modules/openapi-generator/src/main/resources/kotlin-client/data_class.mustache b/modules/openapi-generator/src/main/resources/kotlin-client/data_class.mustache index b6d2b11522f..394af616a12 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-client/data_class.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-client/data_class.mustache @@ -74,6 +74,9 @@ import {{packageName}}.infrastructure.ITransformForStorage @Deprecated(message = "This schema is deprecated.") {{/isDeprecated}} {{>additionalModelTypeAnnotations}} +{{#vendorExtensions.x-class-extra-annotation}} +{{{vendorExtensions.x-class-extra-annotation}}} +{{/vendorExtensions.x-class-extra-annotation}} {{#nonPublicApi}}internal {{/nonPublicApi}}{{#discriminator}}interface{{/discriminator}}{{^discriminator}}{{#hasVars}}data {{/hasVars}}class{{/discriminator}} {{classname}}{{^discriminator}} ( {{#allVars}} @@ -194,7 +197,7 @@ import {{packageName}}.infrastructure.ITransformForStorage companion object { var openapiFields = HashSet() var openapiRequiredFields = HashSet() - + init { {{#allVars}} {{#-first}} @@ -210,7 +213,7 @@ import {{packageName}}.infrastructure.ITransformForStorage openapiRequiredFields.add("{{baseName}}") {{/requiredVars}} } - + /** * Validates the JSON Element and throws an exception if issues found * @@ -227,7 +230,7 @@ import {{packageName}}.infrastructure.ITransformForStorage {{^hasChildren}} {{#requiredVars}} {{#-first}} - + // check to make sure all required properties/fields are present in the JSON string for (requiredField in openapiRequiredFields) { requireNotNull(jsonElement!!.getAsJsonObject()[requiredField]) { @@ -249,7 +252,7 @@ import {{packageName}}.infrastructure.ITransformForStorage if (!jsonObj.get("{{{baseName}}}").isJsonArray) { throw IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj["{{{baseName}}}"].toString())) } - + // validate the required field `{{{baseName}}}` (array) for (i in 0 until jsonObj.getAsJsonArray("{{{baseName}}}").size()) { {{{items.dataType}}}.validateJsonElement(jsonObj.getAsJsonArray("{{{baseName}}}").get(i)) @@ -262,7 +265,7 @@ import {{packageName}}.infrastructure.ITransformForStorage require(jsonObj["{{{baseName}}}"].isJsonArray) { String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj["{{{baseName}}}"].toString()) } - + // validate the optional field `{{{baseName}}}` (array) for (i in 0 until jsonObj.getAsJsonArray("{{{baseName}}}").size()) { {{{items.dataType}}}.validateJsonElement(jsonObj.getAsJsonArray("{{{baseName}}}").get(i)) diff --git a/modules/openapi-generator/src/main/resources/kotlin-client/data_class_opt_var.mustache b/modules/openapi-generator/src/main/resources/kotlin-client/data_class_opt_var.mustache index 1609fa8656e..897b18f9e59 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-client/data_class_opt_var.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-client/data_class_opt_var.mustache @@ -15,6 +15,9 @@ {{^isEnum}}{{^isArray}}{{^isPrimitiveType}}{{^isModel}}@Contextual {{/isModel}}{{/isPrimitiveType}}{{/isArray}}{{/isEnum}}@SerialName(value = "{{{vendorExtensions.x-base-name-literal}}}") {{/kotlinx_serialization}} {{/multiplatform}} + {{#vendorExtensions.x-field-extra-annotation}} + {{{vendorExtensions.x-field-extra-annotation}}} + {{/vendorExtensions.x-field-extra-annotation}} {{#deprecated}} @Deprecated(message = "This property is deprecated.") {{/deprecated}} diff --git a/modules/openapi-generator/src/main/resources/kotlin-client/data_class_req_var.mustache b/modules/openapi-generator/src/main/resources/kotlin-client/data_class_req_var.mustache index 3c9387d1015..811867dfc51 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-client/data_class_req_var.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-client/data_class_req_var.mustache @@ -15,6 +15,9 @@ {{^isEnum}}{{^isArray}}{{^isPrimitiveType}}{{^isModel}}@Contextual {{/isModel}}{{/isPrimitiveType}}{{/isArray}}{{/isEnum}}@SerialName(value = "{{{vendorExtensions.x-base-name-literal}}}") {{/kotlinx_serialization}} {{/multiplatform}} + {{#vendorExtensions.x-field-extra-annotation}} + {{{vendorExtensions.x-field-extra-annotation}}} + {{/vendorExtensions.x-field-extra-annotation}} {{#deprecated}} @Deprecated(message = "This property is deprecated.") {{/deprecated}} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java index 9bdbfcb4b54..f3026827af7 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java @@ -50,7 +50,7 @@ import java.util.Map; @SuppressWarnings("static-method") public class KotlinClientCodegenModelTest { - private Schema getArrayTestSchema() { + private Schema getArrayTestSchema() { return new ObjectSchema() .description("a sample model") .addProperties("id", new IntegerSchema().format("int64")) @@ -58,7 +58,7 @@ public class KotlinClientCodegenModelTest { .addRequiredItem("id"); } - private Schema getSimpleSchema() { + private Schema getSimpleSchema() { return new ObjectSchema() .description("a sample model") .addProperties("id", new IntegerSchema().format("int64")) @@ -68,14 +68,14 @@ public class KotlinClientCodegenModelTest { .addRequiredItem("name"); } - private Schema getMapSchema() { + private Schema getMapSchema() { return new ObjectSchema() .description("a sample model") .addProperties("mapping", new MapSchema() .additionalProperties(new StringSchema())); } - private Schema getComplexSchema() { + private Schema getComplexSchema() { return new ObjectSchema() .description("a sample model") .addProperties("child", new ObjectSchema().$ref("#/components/schemas/Child")); @@ -83,7 +83,7 @@ public class KotlinClientCodegenModelTest { @Test(description = "convert a simple model") public void simpleModelTest() { - final Schema schema = getSimpleSchema(); + final Schema schema = getSimpleSchema(); final DefaultCodegen codegen = new KotlinClientCodegen(); codegen.processOpts(); @@ -128,7 +128,7 @@ public class KotlinClientCodegenModelTest { @Test(description = "convert a simple model: threetenbp") public void selectDateLibraryAsThreetenbp() { - final Schema schema = getSimpleSchema(); + final Schema schema = getSimpleSchema(); final KotlinClientCodegen codegen = new KotlinClientCodegen(); codegen.setDateLibrary(KotlinClientCodegen.DateLibrary.THREETENBP.value); codegen.processOpts(); @@ -149,7 +149,7 @@ public class KotlinClientCodegenModelTest { @Test(description = "convert a simple model: threetenbp-localdatetime") public void selectDateLibraryAsThreetenbpLocalDateTime() { - final Schema schema = getSimpleSchema(); + final Schema schema = getSimpleSchema(); final KotlinClientCodegen codegen = new KotlinClientCodegen(); String value = KotlinClientCodegen.DateLibrary.THREETENBP_LOCALDATETIME.value; Assert.assertEquals(value, "threetenbp-localdatetime"); @@ -172,7 +172,7 @@ public class KotlinClientCodegenModelTest { @Test(description = "convert a simple model: date string") public void selectDateLibraryAsString() { - final Schema schema = getSimpleSchema(); + final Schema schema = getSimpleSchema(); final KotlinClientCodegen codegen = new KotlinClientCodegen(); codegen.setDateLibrary(KotlinClientCodegen.DateLibrary.STRING.value); codegen.processOpts(); @@ -193,7 +193,7 @@ public class KotlinClientCodegenModelTest { @Test(description = "convert a simple model: date java8") public void selectDateLibraryAsJava8() { - final Schema schema = getSimpleSchema(); + final Schema schema = getSimpleSchema(); final KotlinClientCodegen codegen = new KotlinClientCodegen(); codegen.setDateLibrary(KotlinClientCodegen.DateLibrary.JAVA8.value); codegen.processOpts(); @@ -214,7 +214,7 @@ public class KotlinClientCodegenModelTest { @Test(description = "convert a simple model: date java8-localdatetime") public void selectDateLibraryAsJava8LocalDateTime() { - final Schema schema = getSimpleSchema(); + final Schema schema = getSimpleSchema(); final KotlinClientCodegen codegen = new KotlinClientCodegen(); String value = KotlinClientCodegen.DateLibrary.JAVA8_LOCALDATETIME.value; Assert.assertEquals(value, "java8-localdatetime"); @@ -237,7 +237,7 @@ public class KotlinClientCodegenModelTest { @Test(description = "convert a model with array property to default kotlin.Array") public void arrayPropertyTest() { - final Schema model = getArrayTestSchema(); + final Schema model = getArrayTestSchema(); final DefaultCodegen codegen = new KotlinClientCodegen(); OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", model); @@ -264,7 +264,7 @@ public class KotlinClientCodegenModelTest { @Test(description = "convert a model with array property to a kotlin.collections.List") public void listPropertyTest() { - final Schema model = getArrayTestSchema(); + final Schema model = getArrayTestSchema(); final KotlinClientCodegen codegen = new KotlinClientCodegen(); codegen.setCollectionType(KotlinClientCodegen.CollectionType.LIST.value); @@ -293,7 +293,7 @@ public class KotlinClientCodegenModelTest { @Test(description = "convert a model with a map property") public void mapPropertyTest() { - final Schema schema = getMapSchema(); + final Schema schema = getMapSchema(); final DefaultCodegen codegen = new KotlinClientCodegen(); OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", schema); codegen.setOpenAPI(openAPI); @@ -317,7 +317,7 @@ public class KotlinClientCodegenModelTest { @Test(description = "convert a model with complex property") public void complexPropertyTest() { - final Schema schema = getComplexSchema(); + final Schema schema = getComplexSchema(); final DefaultCodegen codegen = new KotlinClientCodegen(); OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", schema); codegen.setOpenAPI(openAPI); @@ -351,7 +351,7 @@ public class KotlinClientCodegenModelTest { @Test(dataProvider = "modelNames", description = "sanitize model names") public void sanitizeModelNames(final String name, final ModelNameTest testCase) { - final Schema schema = getComplexSchema(); + final Schema schema = getComplexSchema(); final DefaultCodegen codegen = new KotlinClientCodegen(); OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema(name, schema); codegen.setOpenAPI(openAPI); @@ -448,4 +448,3 @@ public class KotlinClientCodegenModelTest { } } } - diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinModelCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinModelCodegenTest.java index 59e42b4c1c0..dd9bcbf26eb 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinModelCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinModelCodegenTest.java @@ -17,9 +17,14 @@ 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 static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; import static org.openapitools.codegen.TestUtils.assertFileContains; +import static org.openapitools.codegen.VendorExtension.X_CLASS_EXTRA_ANNOTATION; +import static org.openapitools.codegen.VendorExtension.X_FIELD_EXTRA_ANNOTATION; public class KotlinModelCodegenTest { @@ -110,4 +115,20 @@ public class KotlinModelCodegenTest { assertFileContains(Paths.get(outputPath + "/src/main/kotlin/models/UniqueArray.kt"), "var array: kotlin.collections.MutableSet"); } + + @Test(dataProvider = "generators") + public void xFieldExtraAnnotation(AbstractKotlinCodegen codegen) throws IOException { + assumeThat(codegen.getSupportedVendorExtensions().contains(X_FIELD_EXTRA_ANNOTATION)).isTrue(); + String outputPath = generateModels(codegen, "src/test/resources/3_0/issue_11772.yml", true); + Path ktClassPath = Paths.get(outputPath + "/src/main/kotlin/models/Employee.kt"); + assertThat(ktClassPath).content().contains("@javax.persistence.Id"); + } + + @Test(dataProvider = "generators") + public void xClassExtraAnnotation(AbstractKotlinCodegen codegen) throws IOException { + assumeThat(codegen.getSupportedVendorExtensions().contains(X_CLASS_EXTRA_ANNOTATION)).isTrue(); + String outputPath = generateModels(codegen, "src/test/resources/3_0/issue_11772.yml", true); + Path ktClassPath = Paths.get(outputPath + "/src/main/kotlin/models/Employee.kt"); + assertThat(ktClassPath).content().contains("@javax.persistence.MappedSuperclass"); + } } diff --git a/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiAnnotation.kt b/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiAnnotation.kt index bd33ca92746..457d2586a3d 100644 --- a/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiAnnotation.kt +++ b/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiAnnotation.kt @@ -71,13 +71,13 @@ data class ApiAnnotation ( companion object { var openapiFields = HashSet() var openapiRequiredFields = HashSet() - + init { // a set of all properties/fields (JSON key names) openapiFields.add("id") } - + /** * Validates the JSON Element and throws an exception if issues found * diff --git a/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiApiResponse.kt b/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiApiResponse.kt index 018bb458a12..b51d977e520 100644 --- a/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiApiResponse.kt +++ b/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiApiResponse.kt @@ -79,7 +79,7 @@ data class ApiApiResponse ( companion object { var openapiFields = HashSet() var openapiRequiredFields = HashSet() - + init { // a set of all properties/fields (JSON key names) openapiFields.add("code") @@ -87,7 +87,7 @@ data class ApiApiResponse ( openapiFields.add("message") } - + /** * Validates the JSON Element and throws an exception if issues found * diff --git a/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiCategory.kt b/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiCategory.kt index 4d2ab888bb3..38d92229ede 100644 --- a/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiCategory.kt +++ b/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiCategory.kt @@ -75,14 +75,14 @@ data class ApiCategory ( companion object { var openapiFields = HashSet() var openapiRequiredFields = HashSet() - + init { // a set of all properties/fields (JSON key names) openapiFields.add("id") openapiFields.add("name") } - + /** * Validates the JSON Element and throws an exception if issues found * diff --git a/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiOrder.kt b/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiOrder.kt index af39fc31cfc..e5bb80c0639 100644 --- a/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiOrder.kt +++ b/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiOrder.kt @@ -102,7 +102,7 @@ data class ApiOrder ( companion object { var openapiFields = HashSet() var openapiRequiredFields = HashSet() - + init { // a set of all properties/fields (JSON key names) openapiFields.add("id") @@ -113,7 +113,7 @@ data class ApiOrder ( openapiFields.add("complete") } - + /** * Validates the JSON Element and throws an exception if issues found * diff --git a/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiPet.kt b/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiPet.kt index 35871da8730..f47705a67d6 100644 --- a/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiPet.kt +++ b/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiPet.kt @@ -105,7 +105,7 @@ data class ApiPet ( companion object { var openapiFields = HashSet() var openapiRequiredFields = HashSet() - + init { // a set of all properties/fields (JSON key names) openapiFields.add("name") @@ -119,7 +119,7 @@ data class ApiPet ( openapiRequiredFields.add("name") openapiRequiredFields.add("photoUrls") } - + /** * Validates the JSON Element and throws an exception if issues found * @@ -133,7 +133,7 @@ data class ApiPet ( String.format("The required field(s) %s in ApiPet is not found in the empty JSON string", ApiPet.openapiRequiredFields.toString()) } } - + // check to make sure all required properties/fields are present in the JSON string for (requiredField in openapiRequiredFields) { requireNotNull(jsonElement!!.getAsJsonObject()[requiredField]) { @@ -161,7 +161,7 @@ data class ApiPet ( require(jsonObj["tags"].isJsonArray) { String.format("Expected the field `tags` to be an array in the JSON string but got `%s`", jsonObj["tags"].toString()) } - + // validate the optional field `tags` (array) for (i in 0 until jsonObj.getAsJsonArray("tags").size()) { ApiTag.validateJsonElement(jsonObj.getAsJsonArray("tags").get(i)) diff --git a/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiTag.kt b/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiTag.kt index 8f4b05dcd09..8418ea6512b 100644 --- a/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiTag.kt +++ b/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiTag.kt @@ -75,14 +75,14 @@ data class ApiTag ( companion object { var openapiFields = HashSet() var openapiRequiredFields = HashSet() - + init { // a set of all properties/fields (JSON key names) openapiFields.add("id") openapiFields.add("name") } - + /** * Validates the JSON Element and throws an exception if issues found * diff --git a/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiUser.kt b/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiUser.kt index d47f6636a72..dc5ff32bd7c 100644 --- a/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiUser.kt +++ b/samples/client/petstore/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/models/ApiUser.kt @@ -100,7 +100,7 @@ data class ApiUser ( companion object { var openapiFields = HashSet() var openapiRequiredFields = HashSet() - + init { // a set of all properties/fields (JSON key names) openapiFields.add("username") @@ -115,7 +115,7 @@ data class ApiUser ( // a set of required properties/fields (JSON key names) openapiRequiredFields.add("username") } - + /** * Validates the JSON Element and throws an exception if issues found * @@ -129,7 +129,7 @@ data class ApiUser ( String.format("The required field(s) %s in ApiUser is not found in the empty JSON string", ApiUser.openapiRequiredFields.toString()) } } - + // check to make sure all required properties/fields are present in the JSON string for (requiredField in openapiRequiredFields) { requireNotNull(jsonElement!!.getAsJsonObject()[requiredField]) {