Skip to content

[kotlin-spring] seal allOf discriminator parent interfaces by default - #23953

Merged
wing328 merged 1 commit into
OpenAPITools:masterfrom
rvlasveld:fix/kotlin-spring-seal-allof-discriminator-interfaces
Sep 28, 2026
Merged

wing328 merged 1 commit into
OpenAPITools:masterfrom
rvlasveld:fix/kotlin-spring-seal-allof-discriminator-interfaces

Conversation

@rvlasveld

@rvlasveld rvlasveld commented Jun 5, 2026 •

Copy link
Copy Markdown
Contributor

What

A model carrying a discriminator is rendered as an interface. The generator knows every subtype at generation time, so that interface can be sealed:

// before
interface Pet { ... }

// after
sealed interface Pet { ... }

Two things this buys:

  • Exhaustive when over the subtypes, with no else branch and a compile error when a new subtype appears in the spec.
  • No external implementors. Code outside the generated package cannot implement Pet, which matters because the discriminator mapping cannot describe a type it has never seen.

Behaviour change — on by default

useSealedDiscriminatorInterfaces defaults to true, so this changes output for every existing user with an allOf discriminator, with no action on their part.

The rationale for defaulting on rather than off: oneOf discriminator parents are already sealed unconditionally in this generator (via oneof_interface.mustache, untouched here). Leaving allOf parents unsealed by default would keep two polymorphism styles behaving differently for no reason a user could predict. This aligns them.

The case it breaks is real but narrow — implementing the generated interface from a module outside the generated package:

additionalProperties:
  useSealedDiscriminatorInterfaces: false   # restores the plain `interface`

This is the main decision for maintainers on this PR. If the project would rather introduce it opt-in and flip the default in a later major, changing one line (= true → = false) plus regenerating samples is all it takes — say the word and I will push that.

Scope

Only the allOf/dataClass.mustache path is touched. oneOf parents render through a different template and are unaffected by the flag in either position — there is an explicit regression test for that (testOneOfInterfaceStillSealedWhenAllOfFlagOff).

Sealed-ness composes correctly with x-kotlin-implements external supertypes: sealed interface Pet : com.some.pack.Named.

Tests

KotlinSpringServerCodegenTest: 287 passing, 4 of them new — sealed by default, plain interface on opt-out, composition with external supertypes, and the oneOf-unaffected regression test above.

Samples

Regenerated all kotlin-spring sample configs. Three change — note kotlin-springboot-request-cookie is included, which the earlier revision of this PR had missed:

  • kotlin-springboot-x-kotlin-implements — Pet.kt
  • kotlin-springboot-include-http-request-context-delegate — Pet.kt
  • kotlin-springboot-request-cookie — Animal.kt

For maintainers

  • Independent PR. No ordering relationship with [kotlin-spring] allOf discriminator children get default discriminator value #23952; either merge order works. The only
    overlap is a both-added resolution at the tail of KotlinSpringServerCodegenTest.java.
  • Rebased on master after [kotlin-spring] fix multi-level allOf inheritance (fixes #18206) #23954 merged. Its open class change and this one edit opposite
    branches of the same dataClass.mustache line; that is now resolved in this branch, so the line
    carries both (sealed for the discriminator branch, open for the non-discriminator branch).
    This PR therefore also seals Animal in the allOf-multilevel sample [kotlin-spring] fix multi-level allOf inheritance (fixes #18206) #23954 added.
  • The three-way combination compiles. sealed interface Animal + open class Dog : Animal +
    data class BigDog : Dog(...) builds cleanly — verified with gradlew compileKotlin on the
    regenerated sample. Worth noting because no single PR's CI exercised that combination.
  • Decide on the default (true vs false) — see above. This is the one judgement call in the PR.
  • Feature-matrix flip. docs/generators/kotlin-spring.md moves allOf from ✗ to ✓. That is a pre-existing mis-declaration (the generator has long handled allOf inheritance) and is orthogonal to sealing; it rides here only because this is the one PR of the three that regenerates the docs. Happy to split it out if you would rather keep it separate.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VBn7VWUxZL6mTUbg21xNjm

@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.

No issues found across 10 files

Re-trigger cubic

@rvlasveld

This comment was marked as outdated.

@rvlasveld
rvlasveld marked this pull request as ready for review June 5, 2026 12:35

@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.

No issues found across 10 files

Re-trigger cubic

@rvlasveld

Copy link
Copy Markdown
Contributor Author

@wing328, could you help get this reviewed?

@rvlasveld
rvlasveld force-pushed the fix/kotlin-spring-seal-allof-discriminator-interfaces branch from 4433836 to 3618a83 Compare September 20, 2026 13:58

@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.

No issues found across 7 files

Re-trigger cubic

@wing328

wing328 commented Sep 27, 2026

Copy link
Copy Markdown
Member

please resolve the merge conflicts when you've time and i'll try to get it merged before the upcoming stable release since the change is straightforward and users can easily fallback by setting the option to false.

A model with a discriminator is rendered as an interface. Because the
generator knows every subtype at generation time, that interface can be
sealed, which lets callers use exhaustive `when` matching and stops
external code from adding implementors that the discriminator mapping
does not know about.

Adds `useSealedDiscriminatorInterfaces` (default true). Set it to false
to restore the plain `interface` rendering, which is needed when the
generated interface is implemented from outside the generated package.

Also declares SchemaSupportFeature.allOf, which the generator supports
but did not advertise.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBn7VWUxZL6mTUbg21xNjm
@rvlasveld
rvlasveld force-pushed the fix/kotlin-spring-seal-allof-discriminator-interfaces branch from 3618a83 to e89f258 Compare September 27, 2026 21:07
@wing328
wing328 merged commit 2f13add into OpenAPITools:master Sep 28, 2026
83 checks passed
@wing328 wing328 added this to the 7.26.0 milestone Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants