diff --git a/docs/generators/kotlin-server.md b/docs/generators/kotlin-server.md
index 08125378d996..b5f7b5a74766 100644
--- a/docs/generators/kotlin-server.md
+++ b/docs/generators/kotlin-server.md
@@ -34,6 +34,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
|implicitHeaders|Skip header parameters in the generated API methods.| |false|
|interfaceOnly|Whether to generate only API interface stubs without the server files. This option is currently supported only when using jaxrs-spec library.| |false|
+|inheritanceMode|Strategy for model inheritance generation. Values: `none`, `sealed`, `abstract`, `composition`.|
- **none**
- Flatten inheritance metadata (no discriminator/parent shaping).
- **sealed**
- Use sealed/interface-oriented shaping (supported for `ktor`, `javalin5`, and `javalin6`; rejected for `jaxrs-spec`).
- **abstract**
- Use abstract base class shaping.
- **composition**
- Flatten inheritance and generate discriminator owners as composition wrappers (`value: Any`).
|sealed (ktor, javalin5/javalin6), abstract (jaxrs-spec)|
|library|library template (sub-template)|- **ktor**
- ktor framework
- **ktor2**
- ktor (2.x) framework
- **jaxrs-spec**
- JAX-RS spec only
- **javalin5**
- Javalin 5
- **javalin6**
- Javalin 6
|ktor|
|modelMutable|Create mutable models| |false|
|omitGradleWrapper|Whether to omit Gradle wrapper for creating a sub project.| |false|
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinServerCodegen.java
index ec6cfeb9f787..831523ce5044 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinServerCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinServerCodegen.java
@@ -24,6 +24,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -109,6 +110,7 @@ public class KotlinServerCodegen extends AbstractKotlinCodegen implements BeanVa
@Getter
@Setter
private Boolean delegatePatternEnabled = false;
+ private String inheritanceMode;
/**
* Constructs an instance of `KotlinServerCodegen`.
@@ -184,6 +186,17 @@ public KotlinServerCodegen() {
addSwitch(USE_JAKARTA_EE, Constants.USE_JAKARTA_EE_DESC, useJakartaEe);
addSwitch(Constants.FIX_JACKSON_JSON_TYPE_INFO_INHERITANCE, Constants.FIX_JACKSON_JSON_TYPE_INFO_INHERITANCE_DESC, fixJacksonJsonTypeInfoInheritance);
addSwitch(Constants.DELEGATE_PATTERN, Constants.DELEGATE_PATTERN_DESC, getDelegatePatternEnabled());
+ addOption(Constants.INHERITANCE_MODE, Constants.INHERITANCE_MODE_DESC,
+ defaultInheritanceModeForLibrary(DEFAULT_LIBRARY), buildInheritanceModeOptions());
+ }
+
+ private Map buildInheritanceModeOptions() {
+ Map options = new HashMap<>();
+ options.put(Constants.INHERITANCE_MODE_NONE, "Flat models (no inheritance-aware shaping)");
+ options.put(Constants.INHERITANCE_MODE_SEALED, "Sealed/interface oriented inheritance");
+ options.put(Constants.INHERITANCE_MODE_ABSTRACT, "Abstract base classes and concrete children");
+ options.put(Constants.INHERITANCE_MODE_COMPOSITION, "Composition wrappers for polymorphism");
+ return options;
}
@Override
@@ -271,6 +284,37 @@ public void processOpts() {
LOGGER.info("`library` option is empty. Default to {}", DEFAULT_LIBRARY);
}
+ inheritanceMode = defaultInheritanceModeForLibrary(library);
+ if (additionalProperties.containsKey(Constants.INHERITANCE_MODE)) {
+ String configuredInheritanceMode = String.valueOf(additionalProperties.get(Constants.INHERITANCE_MODE));
+ if (!isValidInheritanceMode(configuredInheritanceMode)) {
+ throw new IllegalArgumentException(String.format(Locale.ROOT,
+ "Invalid %s value '%s'. Supported values are: %s, %s, %s, %s",
+ Constants.INHERITANCE_MODE,
+ configuredInheritanceMode,
+ Constants.INHERITANCE_MODE_NONE,
+ Constants.INHERITANCE_MODE_SEALED,
+ Constants.INHERITANCE_MODE_ABSTRACT,
+ Constants.INHERITANCE_MODE_COMPOSITION));
+ }
+ inheritanceMode = configuredInheritanceMode;
+ }
+
+ if (Constants.JAXRS_SPEC.equals(library) && Constants.INHERITANCE_MODE_SEALED.equals(inheritanceMode)) {
+ throw new IllegalArgumentException(String.format(Locale.ROOT,
+ "Library '%s' does not support inheritanceMode '%s'. Use '%s', '%s', or '%s'.",
+ Constants.JAXRS_SPEC,
+ Constants.INHERITANCE_MODE_SEALED,
+ Constants.INHERITANCE_MODE_NONE,
+ Constants.INHERITANCE_MODE_ABSTRACT,
+ Constants.INHERITANCE_MODE_COMPOSITION));
+ }
+ additionalProperties.put(Constants.INHERITANCE_MODE, inheritanceMode);
+ additionalProperties.put(Constants.X_INHERITANCE_MODE_NONE, Constants.INHERITANCE_MODE_NONE.equals(inheritanceMode));
+ additionalProperties.put(Constants.X_INHERITANCE_MODE_SEALED, Constants.INHERITANCE_MODE_SEALED.equals(inheritanceMode));
+ additionalProperties.put(Constants.X_INHERITANCE_MODE_ABSTRACT, Constants.INHERITANCE_MODE_ABSTRACT.equals(inheritanceMode));
+ additionalProperties.put(Constants.X_INHERITANCE_MODE_COMPOSITION, Constants.INHERITANCE_MODE_COMPOSITION.equals(inheritanceMode));
+
if (isKtor()) {
typeMapping.put("date-time", "kotlin.String");
typeMapping.put("DateTime", "kotlin.String");
@@ -436,12 +480,36 @@ public static class Constants {
public static final String USE_TAGS_DESC = "use tags for creating interface and controller classnames.";
public static final String DELEGATE_PATTERN = "delegatePattern";
public static final String DELEGATE_PATTERN_DESC = "Whether to generate the server files using the delegate pattern. This option is currently supported only when using ktor library.";
+ public static final String INHERITANCE_MODE = "inheritanceMode";
+ public static final String INHERITANCE_MODE_DESC = "Strategy for model inheritance generation. Values: none, sealed, abstract, composition.";
+ public static final String INHERITANCE_MODE_NONE = "none";
+ public static final String INHERITANCE_MODE_SEALED = "sealed";
+ public static final String INHERITANCE_MODE_ABSTRACT = "abstract";
+ public static final String INHERITANCE_MODE_COMPOSITION = "composition";
+ public static final String X_INHERITANCE_MODE_NONE = "x-inheritance-mode-none";
+ public static final String X_INHERITANCE_MODE_SEALED = "x-inheritance-mode-sealed";
+ public static final String X_INHERITANCE_MODE_ABSTRACT = "x-inheritance-mode-abstract";
+ public static final String X_INHERITANCE_MODE_COMPOSITION = "x-inheritance-mode-composition";
}
@Override
public Map postProcessAllModels(Map objs) {
objs = super.postProcessAllModels(objs);
+ if (Constants.INHERITANCE_MODE_NONE.equals(inheritanceMode)) {
+ flattenInheritanceForNoneMode(objs);
+ return objs;
+ }
+
+ if (Constants.INHERITANCE_MODE_COMPOSITION.equals(inheritanceMode)) {
+ applyCompositionMode(objs);
+ return objs;
+ }
+
+ if (!isInheritancePostProcessingEnabled()) {
+ return objs;
+ }
+
// For libraries that use Jackson, set up parent-child relationships for discriminator children
// This enables proper polymorphism support with @JsonTypeInfo and @JsonSubTypes annotations
if (usesJacksonSerialization()) {
@@ -920,4 +988,112 @@ private static String getCommonPath(String path1, String path2) {
}
return builder.toString();
}
+
+ private static String defaultInheritanceModeForLibrary(String currentLibrary) {
+ return Constants.JAXRS_SPEC.equals(currentLibrary)
+ ? Constants.INHERITANCE_MODE_ABSTRACT
+ : Constants.INHERITANCE_MODE_SEALED;
+ }
+
+ private static boolean isValidInheritanceMode(String value) {
+ return Constants.INHERITANCE_MODE_NONE.equals(value)
+ || Constants.INHERITANCE_MODE_SEALED.equals(value)
+ || Constants.INHERITANCE_MODE_ABSTRACT.equals(value)
+ || Constants.INHERITANCE_MODE_COMPOSITION.equals(value);
+ }
+
+ private static void flattenInheritanceForNoneMode(Map objs) {
+ for (ModelsMap modelsMap : objs.values()) {
+ for (ModelMap modelMap : modelsMap.getModels()) {
+ CodegenModel model = modelMap.getModel();
+
+ model.setDiscriminator(null);
+ model.setParent(null);
+ model.getVendorExtensions().remove("x-parent-ctor-args");
+ model.getVendorExtensions().remove("x-discriminator-has-parent-properties");
+ model.getVendorExtensions().remove("x-discriminator-visible-true");
+
+ if (model.interfaces != null) {
+ model.interfaces.clear();
+ }
+
+ for (CodegenProperty prop : model.getAllVars()) {
+ prop.isInherited = false;
+ }
+ for (CodegenProperty prop : model.getVars()) {
+ prop.isInherited = false;
+ }
+ for (CodegenProperty prop : model.getRequiredVars()) {
+ prop.isInherited = false;
+ }
+ for (CodegenProperty prop : model.getOptionalVars()) {
+ prop.isInherited = false;
+ }
+ }
+ }
+ }
+
+ private static void applyCompositionMode(Map objs) {
+ Set discriminatorOwners = new HashSet<>();
+ for (ModelsMap modelsMap : objs.values()) {
+ for (ModelMap modelMap : modelsMap.getModels()) {
+ CodegenModel model = modelMap.getModel();
+ if (model.getDiscriminator() != null
+ && model.getDiscriminator().getMappedModels() != null
+ && !model.getDiscriminator().getMappedModels().isEmpty()) {
+ discriminatorOwners.add(model.getClassname());
+ }
+ }
+ }
+
+ flattenInheritanceForNoneMode(objs);
+
+ for (ModelsMap modelsMap : objs.values()) {
+ for (ModelMap modelMap : modelsMap.getModels()) {
+ CodegenModel model = modelMap.getModel();
+ if (discriminatorOwners.contains(model.getClassname())) {
+ replaceWithCompositionWrapper(model);
+ }
+ }
+ }
+ }
+
+ private static void replaceWithCompositionWrapper(CodegenModel model) {
+ CodegenProperty wrapperValue = new CodegenProperty();
+ wrapperValue.baseName = "value";
+ wrapperValue.name = "value";
+ wrapperValue.dataType = "kotlin.Any";
+ wrapperValue.datatypeWithEnum = "kotlin.Any";
+ wrapperValue.required = true;
+ wrapperValue.isNullable = false;
+ wrapperValue.isReadOnly = false;
+
+ model.getVars().clear();
+ model.getAllVars().clear();
+ model.getRequiredVars().clear();
+ model.getOptionalVars().clear();
+ model.getMandatory().clear();
+ model.getAllMandatory().clear();
+ model.oneOf.clear();
+ model.anyOf.clear();
+ model.allOf.clear();
+
+ model.getVars().add(wrapperValue);
+ model.getAllVars().add(wrapperValue);
+ model.getRequiredVars().add(wrapperValue);
+ model.getMandatory().add("value");
+ model.getAllMandatory().add("value");
+
+ model.hasVars = true;
+ model.hasRequired = true;
+ model.hasOptional = false;
+ model.emptyVars = false;
+ model.getVendorExtensions().put("x-composition-wrapper", true);
+ }
+
+ private boolean isInheritancePostProcessingEnabled() {
+ // `none` and `composition` are handled in dedicated short-circuit paths.
+ return !Constants.INHERITANCE_MODE_NONE.equals(inheritanceMode)
+ && !Constants.INHERITANCE_MODE_COMPOSITION.equals(inheritanceMode);
+ }
}
diff --git a/modules/openapi-generator/src/main/resources/kotlin-server/data_class.mustache b/modules/openapi-generator/src/main/resources/kotlin-server/data_class.mustache
index 12fa7fceef56..4fecc4a53900 100644
--- a/modules/openapi-generator/src/main/resources/kotlin-server/data_class.mustache
+++ b/modules/openapi-generator/src/main/resources/kotlin-server/data_class.mustache
@@ -85,7 +85,7 @@ import java.io.Serializable
{{#discriminator}}
{{>typeInfoAnnotation}}
{{#vendorExtensions.x-discriminator-has-parent-properties}}
-sealed class {{classname}}(
+{{#x-inheritance-mode-abstract}}abstract class{{/x-inheritance-mode-abstract}}{{^x-inheritance-mode-abstract}}sealed class{{/x-inheritance-mode-abstract}} {{classname}}(
{{#requiredVars}}
{{>data_class_sealed_var}}{{^-last}},
{{/-last}}{{/requiredVars}}{{#hasRequired}}{{#hasOptional}},
@@ -94,7 +94,7 @@ sealed class {{classname}}(
)
{{/vendorExtensions.x-discriminator-has-parent-properties}}
{{^vendorExtensions.x-discriminator-has-parent-properties}}
-sealed class {{classname}}
+{{#x-inheritance-mode-abstract}}abstract class{{/x-inheritance-mode-abstract}}{{^x-inheritance-mode-abstract}}sealed class{{/x-inheritance-mode-abstract}} {{classname}}
{{/vendorExtensions.x-discriminator-has-parent-properties}}
{{/discriminator}}
{{^discriminator}}
diff --git a/modules/openapi-generator/src/main/resources/kotlin-server/libraries/jaxrs-spec/data_class.mustache b/modules/openapi-generator/src/main/resources/kotlin-server/libraries/jaxrs-spec/data_class.mustache
index 4c38bf64dccc..8384544057fb 100644
--- a/modules/openapi-generator/src/main/resources/kotlin-server/libraries/jaxrs-spec/data_class.mustache
+++ b/modules/openapi-generator/src/main/resources/kotlin-server/libraries/jaxrs-spec/data_class.mustache
@@ -29,22 +29,28 @@ import java.io.Serializable
{{/isDeprecated}}
{{>additionalModelTypeAnnotations}}
-{{#nonPublicApi}}internal {{/nonPublicApi}}{{#discriminator}}interface{{/discriminator}}{{^discriminator}}data class{{/discriminator}} {{classname}}{{^discriminator}} (
+{{#nonPublicApi}}internal {{/nonPublicApi}}{{#discriminator}}{{#x-inheritance-mode-abstract}}abstract class{{/x-inheritance-mode-abstract}}{{^x-inheritance-mode-abstract}}interface{{/x-inheritance-mode-abstract}}{{/discriminator}}{{^discriminator}}data class{{/discriminator}} {{classname}}{{^discriminator}} (
{{#allVars}}
{{#required}}{{>data_class_req_var}}{{/required}}{{^required}}{{>data_class_opt_var}}{{/required}}{{^-last}},{{/-last}}
{{/allVars}}
-){{/discriminator}}{{#parent}}{{^serializableModel}}{{^parcelizeModels}} : {{{parent}}}{{#isMap}}(){{/isMap}}{{#isArray}}(){{/isArray}}{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{#parent}}{{#serializableModel}}{{^parcelizeModels}} : {{{parent}}}{{#isMap}}(){{/isMap}}{{#isArray}}(){{/isArray}}, Serializable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{#parent}}{{^serializableModel}}{{#parcelizeModels}} : {{{parent}}}{{#isMap}}(){{/isMap}}{{#isArray}}(){{/isArray}}, Parcelable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{#parent}}{{#serializableModel}}{{#parcelizeModels}} : {{{parent}}}{{#isMap}}(){{/isMap}}{{#isArray}}(){{/isArray}}, Serializable, Parcelable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{^parent}}{{#serializableModel}}{{^parcelizeModels}} : Serializable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{^parent}}{{^serializableModel}}{{#parcelizeModels}} : Parcelable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{^parent}}{{#serializableModel}}{{#parcelizeModels}} : Serializable, Parcelable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{#vendorExtensions.x-has-data-class-body}} {
+){{/discriminator}}{{#discriminator}}{{#x-inheritance-mode-abstract}}(
+
+{{#allVars}}
+{{#required}}{{>data_class_sealed_var}}{{/required}}{{^required}}{{>data_class_sealed_var}}{{/required}}{{^-last}},{{/-last}}
+
+{{/allVars}}
+){{/x-inheritance-mode-abstract}}{{/discriminator}}{{#parent}}{{#vendorExtensions.x-parent-ctor-args}}{{#serializableModel}}{{#parcelizeModels}} : {{{parent}}}({{{.}}}), Serializable, Parcelable{{/parcelizeModels}}{{^parcelizeModels}} : {{{parent}}}({{{.}}}), Serializable{{/parcelizeModels}}{{/serializableModel}}{{^serializableModel}}{{#parcelizeModels}} : {{{parent}}}({{{.}}}), Parcelable{{/parcelizeModels}}{{^parcelizeModels}} : {{{parent}}}({{{.}}}){{/parcelizeModels}}{{/serializableModel}}{{/vendorExtensions.x-parent-ctor-args}}{{^vendorExtensions.x-parent-ctor-args}}{{#serializableModel}}{{#parcelizeModels}} : {{{parent}}}{{#isMap}}(){{/isMap}}{{#isArray}}(){{/isArray}}, Serializable, Parcelable{{/parcelizeModels}}{{^parcelizeModels}} : {{{parent}}}{{#isMap}}(){{/isMap}}{{#isArray}}(){{/isArray}}, Serializable{{/parcelizeModels}}{{/serializableModel}}{{^serializableModel}}{{#parcelizeModels}} : {{{parent}}}{{#isMap}}(){{/isMap}}{{#isArray}}(){{/isArray}}, Parcelable{{/parcelizeModels}}{{^parcelizeModels}} : {{{parent}}}{{#isMap}}(){{/isMap}}{{#isArray}}(){{/isArray}}{{/parcelizeModels}}{{/serializableModel}}{{/vendorExtensions.x-parent-ctor-args}}{{/parent}}{{^parent}}{{#serializableModel}}{{^parcelizeModels}} : Serializable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{^parent}}{{^serializableModel}}{{#parcelizeModels}} : Parcelable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{^parent}}{{#serializableModel}}{{#parcelizeModels}} : Serializable, Parcelable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{#vendorExtensions.x-has-data-class-body}} {
{{/vendorExtensions.x-has-data-class-body}}
{{#serializableModel}}
{{#nonPublicApi}}internal {{/nonPublicApi}}companion object {
private const val serialVersionUID: Long = 123
}
{{/serializableModel}}
-{{#discriminator}}{{#vars}}{{#required}}
+{{#discriminator}}{{^x-inheritance-mode-abstract}}{{#vars}}{{#required}}
{{>interface_req_var}}{{/required}}{{^required}}
-{{>interface_opt_var}}{{/required}}{{/vars}}{{/discriminator}}
+{{>interface_opt_var}}{{/required}}{{/vars}}{{/x-inheritance-mode-abstract}}{{/discriminator}}
{{#hasEnums}}
{{#vars}}
{{#isEnum}}
diff --git a/modules/openapi-generator/src/main/resources/kotlin-server/libraries/jaxrs-spec/data_class_opt_var.mustache b/modules/openapi-generator/src/main/resources/kotlin-server/libraries/jaxrs-spec/data_class_opt_var.mustache
index f277f71da441..e263fe331119 100644
--- a/modules/openapi-generator/src/main/resources/kotlin-server/libraries/jaxrs-spec/data_class_opt_var.mustache
+++ b/modules/openapi-generator/src/main/resources/kotlin-server/libraries/jaxrs-spec/data_class_opt_var.mustache
@@ -3,4 +3,4 @@
{{/description}}
@JsonProperty("{{#lambda.escapeDollar}}{{baseName}}{{/lambda.escapeDollar}}")
-{{#useBeanValidation}}{{>beanValidation}}{{>beanValidationModel}}{{/useBeanValidation}} {{>modelMutable}} {{{name}}}: {{#isEnum}}{{classname}}.{{nameInPascalCase}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}? = {{{defaultValue}}}{{^defaultValue}}null{{/defaultValue}}
\ No newline at end of file
+{{#useBeanValidation}}{{>beanValidation}}{{>beanValidationModel}}{{/useBeanValidation}} {{#isInherited}}override {{/isInherited}}{{>modelMutable}} {{{name}}}: {{#isEnum}}{{classname}}.{{nameInPascalCase}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}? = {{{defaultValue}}}{{^defaultValue}}null{{/defaultValue}}
diff --git a/modules/openapi-generator/src/main/resources/kotlin-server/libraries/jaxrs-spec/data_class_req_var.mustache b/modules/openapi-generator/src/main/resources/kotlin-server/libraries/jaxrs-spec/data_class_req_var.mustache
index 6f4647d16e98..7b495b55ef54 100644
--- a/modules/openapi-generator/src/main/resources/kotlin-server/libraries/jaxrs-spec/data_class_req_var.mustache
+++ b/modules/openapi-generator/src/main/resources/kotlin-server/libraries/jaxrs-spec/data_class_req_var.mustache
@@ -3,4 +3,4 @@
{{/description}}
@JsonProperty("{{#lambda.escapeDollar}}{{baseName}}{{/lambda.escapeDollar}}")
-{{#useBeanValidation}}{{>beanValidation}}{{>beanValidationModel}}{{/useBeanValidation}} {{>modelMutable}} {{{name}}}: {{#isEnum}}{{classname}}.{{nameInPascalCase}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}
\ No newline at end of file
+{{#useBeanValidation}}{{>beanValidation}}{{>beanValidationModel}}{{/useBeanValidation}} {{#isInherited}}override {{/isInherited}}{{>modelMutable}} {{{name}}}: {{#isEnum}}{{classname}}.{{nameInPascalCase}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinServerCodegenTest.java
index 87176dc4eaa3..8f54206c5e5b 100644
--- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinServerCodegenTest.java
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinServerCodegenTest.java
@@ -36,6 +36,11 @@
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.INHERITANCE_MODE;
+import static org.openapitools.codegen.languages.KotlinServerCodegen.Constants.INHERITANCE_MODE_ABSTRACT;
+import static org.openapitools.codegen.languages.KotlinServerCodegen.Constants.INHERITANCE_MODE_COMPOSITION;
+import static org.openapitools.codegen.languages.KotlinServerCodegen.Constants.INHERITANCE_MODE_NONE;
+import static org.openapitools.codegen.languages.KotlinServerCodegen.Constants.INHERITANCE_MODE_SEALED;
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.KotlinServerCodegen.Constants.USE_TAGS;
@@ -43,6 +48,10 @@
import static org.openapitools.codegen.languages.KotlinServerCodegen.Constants.DELEGATE_PATTERN;
import static org.openapitools.codegen.languages.KotlinServerCodegen.Constants.KTOR;
import static org.openapitools.codegen.languages.KotlinServerCodegen.Constants.RESOURCES;
+import static org.openapitools.codegen.languages.KotlinServerCodegen.Constants.X_INHERITANCE_MODE_ABSTRACT;
+import static org.openapitools.codegen.languages.KotlinServerCodegen.Constants.X_INHERITANCE_MODE_COMPOSITION;
+import static org.openapitools.codegen.languages.KotlinServerCodegen.Constants.X_INHERITANCE_MODE_NONE;
+import static org.openapitools.codegen.languages.KotlinServerCodegen.Constants.X_INHERITANCE_MODE_SEALED;
public class KotlinServerCodegenTest {
@@ -465,6 +474,142 @@ public void allOfWithDiscriminator_generatesChildrenWithOverrideProperties() thr
);
}
+ @Test
+ public void allOfWithDiscriminator_javalinAbstractMode_generatesAbstractParent() throws IOException {
+ File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
+ output.deleteOnExit();
+
+ KotlinServerCodegen codegen = new KotlinServerCodegen();
+ codegen.setOutputDir(output.getAbsolutePath());
+ codegen.additionalProperties().put(LIBRARY, JAVALIN6);
+ codegen.additionalProperties().put(INHERITANCE_MODE, INHERITANCE_MODE_ABSTRACT);
+
+ new DefaultGenerator().opts(new ClientOptInput()
+ .openAPI(TestUtils.parseSpec("src/test/resources/3_1/polymorphism-allof-and-discriminator.yaml"))
+ .config(codegen))
+ .generate();
+
+ String outputPath = output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/server";
+ Path petModel = Paths.get(outputPath + "/models/Pet.kt");
+
+ assertFileContains(
+ petModel,
+ "abstract class Pet(",
+ "open val name: kotlin.String",
+ "open val petType: kotlin.String"
+ );
+ assertFileNotContains(
+ petModel,
+ "sealed class Pet("
+ );
+ }
+
+ @Test
+ public void allOfWithDiscriminator_javalinNoneMode_generatesFlatModels() throws IOException {
+ File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
+ output.deleteOnExit();
+
+ KotlinServerCodegen codegen = new KotlinServerCodegen();
+ codegen.setOutputDir(output.getAbsolutePath());
+ codegen.additionalProperties().put(LIBRARY, JAVALIN6);
+ codegen.additionalProperties().put(INHERITANCE_MODE, INHERITANCE_MODE_NONE);
+
+ new DefaultGenerator().opts(new ClientOptInput()
+ .openAPI(TestUtils.parseSpec("src/test/resources/3_1/polymorphism-allof-and-discriminator.yaml"))
+ .config(codegen))
+ .generate();
+
+ String outputPath = output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/server";
+ Path petModel = Paths.get(outputPath + "/models/Pet.kt");
+ Path catModel = Paths.get(outputPath + "/models/Cat.kt");
+
+ assertFileContains(petModel, "data class Pet(");
+ assertFileNotContains(
+ petModel,
+ "sealed class Pet(",
+ "abstract class Pet(",
+ "@com.fasterxml.jackson.annotation.JsonTypeInfo",
+ "@com.fasterxml.jackson.annotation.JsonSubTypes"
+ );
+
+ assertFileContains(catModel, "data class Cat(");
+ assertFileNotContains(catModel, ") : Pet(");
+ }
+
+ @Test
+ public void oneOfWithDiscriminator_javalinCompositionMode_generatesWrapperParent() throws IOException {
+ File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
+ output.deleteOnExit();
+
+ KotlinServerCodegen codegen = new KotlinServerCodegen();
+ codegen.setOutputDir(output.getAbsolutePath());
+ codegen.additionalProperties().put(LIBRARY, JAVALIN6);
+ codegen.additionalProperties().put(INHERITANCE_MODE, INHERITANCE_MODE_COMPOSITION);
+
+ new DefaultGenerator().opts(new ClientOptInput()
+ .openAPI(TestUtils.parseSpec("src/test/resources/3_1/polymorphism-and-discriminator.yaml"))
+ .config(codegen))
+ .generate();
+
+ String outputPath = output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/server";
+ Path petModel = Paths.get(outputPath + "/models/Pet.kt");
+ Path catModel = Paths.get(outputPath + "/models/Cat.kt");
+
+ assertFileContains(
+ petModel,
+ "data class Pet(",
+ "val value: kotlin.Any"
+ );
+ assertFileNotContains(
+ petModel,
+ "sealed class Pet",
+ "abstract class Pet",
+ "@com.fasterxml.jackson.annotation.JsonTypeInfo",
+ "@com.fasterxml.jackson.annotation.JsonSubTypes"
+ );
+
+ assertFileContains(catModel, "data class Cat(");
+ assertFileNotContains(catModel, ") : Pet(");
+ }
+
+ @Test
+ public void allOfWithDiscriminator_jaxrsAbstractMode_generatesAbstractParentAndOverrideChildren() 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(INHERITANCE_MODE, INHERITANCE_MODE_ABSTRACT);
+
+ new DefaultGenerator().opts(new ClientOptInput()
+ .openAPI(TestUtils.parseSpec("src/test/resources/3_1/polymorphism-allof-and-discriminator.yaml"))
+ .config(codegen))
+ .generate();
+
+ String outputPath = output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/server";
+
+ Path petModel = Paths.get(outputPath + "/models/Pet.kt");
+ assertFileContains(
+ petModel,
+ "abstract class Pet(",
+ "open val name: kotlin.String",
+ "open val petType: kotlin.String"
+ );
+ assertFileNotContains(
+ petModel,
+ "interface Pet"
+ );
+
+ Path catModel = Paths.get(outputPath + "/models/Cat.kt");
+ assertFileContains(
+ catModel,
+ "override val name: kotlin.String",
+ "override val petType: kotlin.String",
+ ") : Pet(name = name, petType = petType)"
+ );
+ }
+
@Test
public void polymorphismWithoutDiscriminator_generatesRegularDataClass() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
@@ -671,6 +816,78 @@ public void delegatePattern_canBeEnabled() throws IOException {
Assert.assertTrue(Files.exists(Paths.get(infraPath + "/APINotImplementedException.kt")));
}
+ @Test
+ public void inheritanceMode_defaultIsSealedForKtor() {
+ KotlinServerCodegen codegen = new KotlinServerCodegen();
+ codegen.additionalProperties().put(LIBRARY, KTOR);
+
+ codegen.processOpts();
+
+ Assert.assertEquals(codegen.additionalProperties().get(INHERITANCE_MODE), INHERITANCE_MODE_SEALED);
+ Assert.assertEquals(codegen.additionalProperties().get(X_INHERITANCE_MODE_SEALED), Boolean.TRUE);
+ Assert.assertEquals(codegen.additionalProperties().get(X_INHERITANCE_MODE_ABSTRACT), Boolean.FALSE);
+ Assert.assertEquals(codegen.additionalProperties().get(X_INHERITANCE_MODE_COMPOSITION), Boolean.FALSE);
+ Assert.assertEquals(codegen.additionalProperties().get(X_INHERITANCE_MODE_NONE), Boolean.FALSE);
+ }
+
+ @Test
+ public void inheritanceMode_defaultIsAbstractForJaxrsSpec() {
+ KotlinServerCodegen codegen = new KotlinServerCodegen();
+ codegen.additionalProperties().put(LIBRARY, JAXRS_SPEC);
+
+ codegen.processOpts();
+
+ Assert.assertEquals(codegen.additionalProperties().get(INHERITANCE_MODE), INHERITANCE_MODE_ABSTRACT);
+ Assert.assertEquals(codegen.additionalProperties().get(X_INHERITANCE_MODE_ABSTRACT), Boolean.TRUE);
+ Assert.assertEquals(codegen.additionalProperties().get(X_INHERITANCE_MODE_SEALED), Boolean.FALSE);
+ }
+
+ @Test
+ public void inheritanceMode_rejectsInvalidValue() {
+ KotlinServerCodegen codegen = new KotlinServerCodegen();
+ codegen.additionalProperties().put(LIBRARY, KTOR);
+ codegen.additionalProperties().put(INHERITANCE_MODE, "invalid-mode");
+
+ IllegalArgumentException ex = Assert.expectThrows(IllegalArgumentException.class, codegen::processOpts);
+ Assert.assertTrue(ex.getMessage().contains("Invalid inheritanceMode value 'invalid-mode'"));
+ }
+
+ @Test
+ public void inheritanceMode_rejectsSealedForJaxrsSpec() {
+ KotlinServerCodegen codegen = new KotlinServerCodegen();
+ codegen.additionalProperties().put(LIBRARY, JAXRS_SPEC);
+ codegen.additionalProperties().put(INHERITANCE_MODE, INHERITANCE_MODE_SEALED);
+
+ IllegalArgumentException ex = Assert.expectThrows(IllegalArgumentException.class, codegen::processOpts);
+ Assert.assertTrue(ex.getMessage().contains("does not support inheritanceMode 'sealed'"));
+ }
+
+ @Test
+ public void inheritanceMode_acceptsCompositionForJaxrsSpec() {
+ KotlinServerCodegen codegen = new KotlinServerCodegen();
+ codegen.additionalProperties().put(LIBRARY, JAXRS_SPEC);
+ codegen.additionalProperties().put(INHERITANCE_MODE, INHERITANCE_MODE_COMPOSITION);
+
+ codegen.processOpts();
+
+ Assert.assertEquals(codegen.additionalProperties().get(INHERITANCE_MODE), INHERITANCE_MODE_COMPOSITION);
+ Assert.assertEquals(codegen.additionalProperties().get(X_INHERITANCE_MODE_COMPOSITION), Boolean.TRUE);
+ Assert.assertEquals(codegen.additionalProperties().get(X_INHERITANCE_MODE_ABSTRACT), Boolean.FALSE);
+ }
+
+ @Test
+ public void inheritanceMode_acceptsNoneForJaxrsSpec() {
+ KotlinServerCodegen codegen = new KotlinServerCodegen();
+ codegen.additionalProperties().put(LIBRARY, JAXRS_SPEC);
+ codegen.additionalProperties().put(INHERITANCE_MODE, INHERITANCE_MODE_NONE);
+
+ codegen.processOpts();
+
+ Assert.assertEquals(codegen.additionalProperties().get(INHERITANCE_MODE), INHERITANCE_MODE_NONE);
+ Assert.assertEquals(codegen.additionalProperties().get(X_INHERITANCE_MODE_NONE), Boolean.TRUE);
+ Assert.assertEquals(codegen.additionalProperties().get(X_INHERITANCE_MODE_ABSTRACT), Boolean.FALSE);
+ }
+
@Test
public void delegatePattern_enumWireValue() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();