Skip to content

[kotlin-server] Add configurable inheritance modes#24090

Open
rm127 wants to merge 1 commit into
OpenAPITools:masterfrom
rm127:feat/kotlin-server-inheritance-mode
Open

[kotlin-server] Add configurable inheritance modes#24090
rm127 wants to merge 1 commit into
OpenAPITools:masterfrom
rm127:feat/kotlin-server-inheritance-mode

Conversation

@rm127

@rm127 rm127 commented Jun 22, 2026

Copy link
Copy Markdown

Summary

This change adds configurable inheritance handling to the kotlin-server generator, including jaxrs-spec.

Fixes

Added option

  • inheritanceMode
    • none
    • sealed
    • abstract
    • composition

Default behavior

  • ktor / javalin* default to sealed
  • jaxrs-spec defaults to abstract

Validation

  • Reject invalid inheritanceMode values
  • Reject jaxrs-spec + sealed

Implementation details

Codegen option plumbing

In KotlinServerCodegen:

  • added inheritanceMode state
  • registered the new CLI/additional-property option
  • added mode validation and library-specific defaults
  • exposed template flags:
    • x-inheritance-mode-none
    • x-inheritance-mode-sealed
    • x-inheritance-mode-abstract
    • x-inheritance-mode-composition

Model post-processing

Added mode-aware model shaping in postProcessAllModels:

  • none
    • flattens inheritance/discriminator metadata
    • removes parent linkage and inherited flags
  • composition
    • flattens inheritance first
    • replaces discriminator owners with a wrapper model containing:
      • value: kotlin.Any
  • existing inheritance processing remains active for sealed / abstract

Template updates

Shared kotlin-server model template

  • discriminator parents now render as:
    • sealed class in sealed mode
    • abstract class in abstract mode

jaxrs-spec model templates

  • discriminator parents can render as abstract class instead of interface
  • inherited properties now emit override
  • child classes forward inherited constructor args via x-parent-ctor-args

Tests

Added/updated coverage in KotlinServerCodegenTest for:

  • default sealed mode for ktor
  • default abstract mode for jaxrs-spec
  • invalid mode rejection
  • jaxrs-spec + sealed rejection
  • acceptance of composition
  • acceptance of none
  • javalin abstract-mode parent rendering
  • javalin flat-model rendering in none
  • javalin wrapper-parent generation in composition
  • jaxrs-spec abstract parent + override child rendering

Documentation

Updated docs/generators/kotlin-server.md with the new inheritanceMode option and mode descriptions.

Verification

Tested with:

./mvnw -pl modules/openapi-generator -am -Djacoco.skip=true -Dtest=org.openapitools.codegen.kotlin.KotlinServerCodegenTest -Dsurefire.failIfNoSpecifiedTests=false test

## Addresses
- https://github.com/OpenAPITools/openapi-generator/issues/11552

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds configurable inheritance modes to `kotlin-server` (including `jaxrs-spec`) to control model shaping: `none`, `sealed`, `abstract`, or `composition`. Updates templates, sets sensible defaults per library, and adds validation and tests.

- **New Features**
  - New option: `inheritanceMode` with values `none`, `sealed`, `abstract`, `composition`.
  - Defaults: `ktor`/`javalin*``sealed`, `jaxrs-spec``abstract`; rejects invalid values and `jaxrs-spec + sealed`.
  - Mode behavior:
    - `none`: flattens inheritance/discriminator metadata.
    - `composition`: flattens and generates wrapper parents with `value: kotlin.Any`.
    - `sealed`/`abstract`: existing inheritance processing; discriminator parents render as `sealed class` or `abstract class`.
  - `jaxrs-spec` templates: parents can be `abstract class`; children `override` inherited props and pass parent constructor args via `x-parent-ctor-args`.
  - Exposes template flags: `x-inheritance-mode-none|sealed|abstract|composition`.
  - Docs updated; tests cover defaults, validation, and rendering across modes.

- **Migration**
  - `jaxrs-spec` now uses abstract base classes by default for discriminator parents. Update implementations to extend the abstract class and `override` inherited properties. Use `inheritanceMode=none` for flat models or `inheritanceMode=composition` for wrapper-style polymorphism.

<sup>Written for commit 67ac5c7af9d5bad25cc0c397ee8dbd72ab28549f. Summary will update on new commits.</sup>

<a href="https://cubic.dev/pr/OpenAPITools/openapi-generator/pull/24090?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

3 issues found across 7 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="docs/generators/kotlin-server.md">

<violation number="1" location="docs/generators/kotlin-server.md:37">
P2: Default description for `inheritanceMode` uses non-existent library label `javalin`; should match the concrete supported library values `javalin5`/`javalin6`.</violation>

<violation number="2" location="docs/generators/kotlin-server.md:37">
P2: The `inheritanceMode` documentation does not document the cross-option restriction that `library=jaxrs-spec` with `inheritanceMode=sealed` is rejected at generation time, which can mislead users into a guaranteed failing configuration.</violation>
</file>

<file name="modules/openapi-generator/src/main/resources/kotlin-server/libraries/jaxrs-spec/data_class.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/kotlin-server/libraries/jaxrs-spec/data_class.mustache:44">
P1: Parent constructor argument forwarding is only applied to the non-serializable/non-parcelize branch; the serializableModel and parcelizeModels parent branches still extend the parent with no arguments, breaking compilation when an abstract parent has constructor parameters.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

{{#required}}{{>data_class_sealed_var}}{{/required}}{{^required}}{{>data_class_sealed_var}}{{/required}}{{^-last}},{{/-last}}

{{/allVars}}
){{/x-inheritance-mode-abstract}}{{/discriminator}}{{#parent}}{{^serializableModel}}{{^parcelizeModels}} : {{{parent}}}{{#vendorExtensions.x-parent-ctor-args}}({{{.}}}){{/vendorExtensions.x-parent-ctor-args}}{{^vendorExtensions.x-parent-ctor-args}}{{#isMap}}(){{/isMap}}{{#isArray}}(){{/isArray}}{{/vendorExtensions.x-parent-ctor-args}}{{/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}} {

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.

P1: Parent constructor argument forwarding is only applied to the non-serializable/non-parcelize branch; the serializableModel and parcelizeModels parent branches still extend the parent with no arguments, breaking compilation when an abstract parent has constructor parameters.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/kotlin-server/libraries/jaxrs-spec/data_class.mustache, line 44:

<comment>Parent constructor argument forwarding is only applied to the non-serializable/non-parcelize branch; the serializableModel and parcelizeModels parent branches still extend the parent with no arguments, breaking compilation when an abstract parent has constructor parameters.</comment>

<file context>
@@ -29,22 +29,28 @@ import java.io.Serializable
+{{#required}}{{>data_class_sealed_var}}{{/required}}{{^required}}{{>data_class_sealed_var}}{{/required}}{{^-last}},{{/-last}}
+
+{{/allVars}}
+){{/x-inheritance-mode-abstract}}{{/discriminator}}{{#parent}}{{^serializableModel}}{{^parcelizeModels}} : {{{parent}}}{{#vendorExtensions.x-parent-ctor-args}}({{{.}}}){{/vendorExtensions.x-parent-ctor-args}}{{^vendorExtensions.x-parent-ctor-args}}{{#isMap}}(){{/isMap}}{{#isArray}}(){{/isArray}}{{/vendorExtensions.x-parent-ctor-args}}{{/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}} {
 {{/vendorExtensions.x-has-data-class-body}}
 {{#serializableModel}}
</file context>
Suggested change
){{/x-inheritance-mode-abstract}}{{/discriminator}}{{#parent}}{{^serializableModel}}{{^parcelizeModels}} : {{{parent}}}{{#vendorExtensions.x-parent-ctor-args}}({{{.}}}){{/vendorExtensions.x-parent-ctor-args}}{{^vendorExtensions.x-parent-ctor-args}}{{#isMap}}(){{/isMap}}{{#isArray}}(){{/isArray}}{{/vendorExtensions.x-parent-ctor-args}}{{/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}} {
){{/x-inheritance-mode-abstract}}{{/discriminator}}{{#parent}}{{^serializableModel}}{{^parcelizeModels}} : {{{parent}}}{{#vendorExtensions.x-parent-ctor-args}}({{{.}}}){{/vendorExtensions.x-parent-ctor-args}}{{^vendorExtensions.x-parent-ctor-args}}{{#isMap}}(){{/isMap}}{{#isArray}}(){{/isArray}}{{/vendorExtensions.x-parent-ctor-args}}{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{#parent}}{{#serializableModel}}{{^parcelizeModels}} : {{{parent}}}{{#vendorExtensions.x-parent-ctor-args}}({{{.}}}){{/vendorExtensions.x-parent-ctor-args}}{{^vendorExtensions.x-parent-ctor-args}}{{#isMap}}(){{/isMap}}{{#isArray}}(){{/isArray}}{{/vendorExtensions.x-parent-ctor-args}}, Serializable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{#parent}}{{^serializableModel}}{{#parcelizeModels}} : {{{parent}}}{{#vendorExtensions.x-parent-ctor-args}}({{{.}}}){{/vendorExtensions.x-parent-ctor-args}}{{^vendorExtensions.x-parent-ctor-args}}{{#isMap}}(){{/isMap}}{{#isArray}}(){{/isArray}}{{/vendorExtensions.x-parent-ctor-args}}, Parcelable{{/parcelizeModels}}{{/serializableModel}}{{/parent}}{{#parent}}{{#serializableModel}}{{#parcelizeModels}} : {{{parent}}}{{#vendorExtensions.x-parent-ctor-args}}({{{.}}}){{/vendorExtensions.x-parent-ctor-args}}{{^vendorExtensions.x-parent-ctor-args}}{{#isMap}}(){{/isMap}}{{#isArray}}(){{/isArray}}{{/vendorExtensions.x-parent-ctor-args}}, 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}} {

|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`.|<dl><dt>**none**</dt><dd>Flatten inheritance metadata (no discriminator/parent shaping).</dd><dt>**sealed**</dt><dd>Use sealed/interface-oriented shaping.</dd><dt>**abstract**</dt><dd>Use abstract base class shaping.</dd><dt>**composition**</dt><dd>Flatten inheritance and generate discriminator owners as composition wrappers (`value: Any`).</dd></dl>|sealed (ktor/javalin), abstract (jaxrs-spec)|

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.

P2: Default description for inheritanceMode uses non-existent library label javalin; should match the concrete supported library values javalin5/javalin6.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/generators/kotlin-server.md, line 37:

<comment>Default description for `inheritanceMode` uses non-existent library label `javalin`; should match the concrete supported library values `javalin5`/`javalin6`.</comment>

<file context>
@@ -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`.|<dl><dt>**none**</dt><dd>Flatten inheritance metadata (no discriminator/parent shaping).</dd><dt>**sealed**</dt><dd>Use sealed/interface-oriented shaping.</dd><dt>**abstract**</dt><dd>Use abstract base class shaping.</dd><dt>**composition**</dt><dd>Flatten inheritance and generate discriminator owners as composition wrappers (`value: Any`).</dd></dl>|sealed (ktor/javalin), abstract (jaxrs-spec)|
 |library|library template (sub-template)|<dl><dt>**ktor**</dt><dd>ktor framework</dd><dt>**ktor2**</dt><dd>ktor (2.x) framework</dd><dt>**jaxrs-spec**</dt><dd>JAX-RS spec only</dd><dt>**javalin5**</dt><dd>Javalin 5</dd><dt>**javalin6**</dt><dd>Javalin 6</dd></dl>|ktor|
 |modelMutable|Create mutable models| |false|
</file context>

|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`.|<dl><dt>**none**</dt><dd>Flatten inheritance metadata (no discriminator/parent shaping).</dd><dt>**sealed**</dt><dd>Use sealed/interface-oriented shaping.</dd><dt>**abstract**</dt><dd>Use abstract base class shaping.</dd><dt>**composition**</dt><dd>Flatten inheritance and generate discriminator owners as composition wrappers (`value: Any`).</dd></dl>|sealed (ktor/javalin), abstract (jaxrs-spec)|

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.

P2: The inheritanceMode documentation does not document the cross-option restriction that library=jaxrs-spec with inheritanceMode=sealed is rejected at generation time, which can mislead users into a guaranteed failing configuration.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/generators/kotlin-server.md, line 37:

<comment>The `inheritanceMode` documentation does not document the cross-option restriction that `library=jaxrs-spec` with `inheritanceMode=sealed` is rejected at generation time, which can mislead users into a guaranteed failing configuration.</comment>

<file context>
@@ -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`.|<dl><dt>**none**</dt><dd>Flatten inheritance metadata (no discriminator/parent shaping).</dd><dt>**sealed**</dt><dd>Use sealed/interface-oriented shaping.</dd><dt>**abstract**</dt><dd>Use abstract base class shaping.</dd><dt>**composition**</dt><dd>Flatten inheritance and generate discriminator owners as composition wrappers (`value: Any`).</dd></dl>|sealed (ktor/javalin), abstract (jaxrs-spec)|
 |library|library template (sub-template)|<dl><dt>**ktor**</dt><dd>ktor framework</dd><dt>**ktor2**</dt><dd>ktor (2.x) framework</dd><dt>**jaxrs-spec**</dt><dd>JAX-RS spec only</dd><dt>**javalin5**</dt><dd>Javalin 5</dd><dt>**javalin6**</dt><dd>Javalin 6</dd></dl>|ktor|
 |modelMutable|Create mutable models| |false|
</file context>
Suggested change
|inheritanceMode|Strategy for model inheritance generation. Values: `none`, `sealed`, `abstract`, `composition`.|<dl><dt>**none**</dt><dd>Flatten inheritance metadata (no discriminator/parent shaping).</dd><dt>**sealed**</dt><dd>Use sealed/interface-oriented shaping.</dd><dt>**abstract**</dt><dd>Use abstract base class shaping.</dd><dt>**composition**</dt><dd>Flatten inheritance and generate discriminator owners as composition wrappers (`value: Any`).</dd></dl>|sealed (ktor/javalin), abstract (jaxrs-spec)|
|inheritanceMode|Strategy for model inheritance generation. Values: `none`, `sealed`, `abstract`, `composition`. Note: `sealed` is not supported when `library=jaxrs-spec`.|<dl><dt>**none**</dt><dd>Flatten inheritance metadata (no discriminator/parent shaping).</dd><dt>**sealed**</dt><dd>Use sealed/interface-oriented shaping.</dd><dt>**abstract**</dt><dd>Use abstract base class shaping.</dd><dt>**composition**</dt><dd>Flatten inheritance and generate discriminator owners as composition wrappers (`value: Any`).</dd></dl>|sealed (ktor/javalin), abstract (jaxrs-spec)|

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant