Skip to content

[core] Resolve oneOf discriminator property type from child schemas#24172

Open
Ignacio-Vidal wants to merge 1 commit into
OpenAPITools:masterfrom
Ignacio-Vidal:pr0-discriminator-type-from-children
Open

[core] Resolve oneOf discriminator property type from child schemas#24172
Ignacio-Vidal wants to merge 1 commit into
OpenAPITools:masterfrom
Ignacio-Vidal:pr0-discriminator-type-from-children

Conversation

@Ignacio-Vidal

@Ignacio-Vidal Ignacio-Vidal commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

What

getDiscriminatorPropertyType resolved the discriminator property's type only from the oneOf/anyOf schema's own properties, defaulting to "string" otherwise. For a pure oneOf/anyOf interface whose discriminator property lives on a shared base the children inherit via allOf, this produced a String getter that clashed with the enum the subtypes actually expose.

This adds a fallback (in DiscriminatorUtils) that resolves the type from the mapped child schemas, chasing the property through each child's allOf members. It only fires when the previous code would have returned "string", so existing specs are unaffected — regenerating the full sample suite produces no diffs.

Scenarios covered

Discriminator property declared… Resolves to Test
As a $ref on the schema's own properties (unchanged path) the referenced type (e.g. PetType) DiscriminatorUtilsTest, DefaultCodegenTest
Only on a shared base the oneOf children inherit via allOf PetType (from children) DiscriminatorUtilsTest, DefaultCodegenTest
Only on a shared base the anyOf children inherit via allOf PetType (from children) DiscriminatorUtilsTest
On a grandparent reached through multi-level allOf PetType (recursive descent) DiscriminatorUtilsTest
As a plain inline string (no $ref, no enum) "string" (fallback, unchanged) DiscriminatorUtilsTest, DefaultCodegenTest

Simples example where the discriminator is in the base object that is separate from the schema that contains oneOf

PetRequest:
      type: object
      oneOf:
        - $ref: '#/components/schemas/CatRequest'
        - $ref: '#/components/schemas/DogRequest'
      discriminator:
        propertyName: petType
        mapping:
          CAT: '#/components/schemas/CatRequest'
          DOG: '#/components/schemas/DogRequest'

    # Shared base WITHOUT a discriminator, so children flatten it via allOf
    # instead of extending it (which would create a cyclical reference).
    PetBase:
      type: object
      required:
        - petType
        - name
      properties:
        petType:
          $ref: '#/components/schemas/PetType'  # ---> This would be duplicated into PetRequest without this PR refactor
        name:
          type: string

    CatRequest:
      type: object
      allOf:
        - $ref: '#/components/schemas/PetBase'
        - type: object
          required:
            - indoor
          properties:
            indoor:
              type: boolean

    DogRequest:
      type: object
      allOf:
        - $ref: '#/components/schemas/PetBase'
        - type: object
          required:
            - trained
          properties:
            trained:
              type: boolean

PR checklist

  • Read the contribution guidelines.
  • Run the following to build the project and update samples:
    ./mvnw clean package || exit
    ./bin/generate-samples.sh ./bin/configs/*.yaml || exit
    ./bin/utils/export_docs_generators.sh || exit
    
    (For Windows users, please run the script in WSL)
    Commit all changed files.
    This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
    These must match the expectations made by your contribution.
    You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example ./bin/generate-samples.sh bin/configs/java*.
    IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

Summary by cubic

Fixes discriminator type resolution for oneOf/anyOf interfaces by deriving the property type from child schemas (via allOf) with cycle-safe recursion. Getter types now use the correct enum (e.g., PetType) instead of "string", avoiding interface vs subtype mismatches.

  • Bug Fixes
    • Reworked DiscriminatorUtils.getDiscriminatorPropertyType(OpenAPI, ...) to check the schema’s own $ref first, then fall back to oneOf/anyOf children via getDiscriminatorPropertyTypeFromChildren and getDiscriminatorSchemaDeep (supports multi-level allOf).
    • Added visited-schema guard to getDiscriminatorSchemaDeep to handle cyclic allOf graphs safely.
    • Updated DefaultCodegen.getDiscriminatorPropertyType to delegate to the util, map to model names, and keep the "string" fallback; added DiscriminatorUtilsTest, expanded DefaultCodegenTest, and new YAMLs (oneOf/anyOf, nested base, cyclic, string fallback). Samples regenerate with no diffs.

Written for commit 310c3f0. Summary will update on new commits.

Review in cubic

@Ignacio-Vidal Ignacio-Vidal force-pushed the pr0-discriminator-type-from-children branch 2 times, most recently from 3a3cbe2 to 0a6c7f0 Compare June 30, 2026 20:59
@Ignacio-Vidal Ignacio-Vidal marked this pull request as ready for review June 30, 2026 21:16
@Ignacio-Vidal Ignacio-Vidal changed the title DRAFT: [core] Resolve oneOf discriminator property type from child schemas [core] Resolve oneOf discriminator property type from child schemas Jun 30, 2026
@Ignacio-Vidal

Ignacio-Vidal commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

@Mattias-Sehlstedt - This is an enhancement to your recent contribution 439f608 (introduce DiscriminatorUtils). Could you review it please?

@wing328 - Could you review i? Given you approved 439f608 too

@Ignacio-Vidal Ignacio-Vidal force-pushed the pr0-discriminator-type-from-children branch from 4532046 to 02c7380 Compare June 30, 2026 21:24

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

1 issue found and verified against the latest diff

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

Re-trigger cubic

@Ignacio-Vidal Ignacio-Vidal force-pushed the pr0-discriminator-type-from-children branch from 02c7380 to 7b0dc26 Compare June 30, 2026 21:41
getDiscriminatorPropertyType resolved the discriminator property's type only
from the oneOf/anyOf schema's own properties, defaulting to "string" when the
property was not declared there. For a pure oneOf interface whose discriminator
property lives on a shared base that the children inherit via allOf, this
produced a String getter type that clashed with the enum the subtypes actually
expose.

Add the resolution to DiscriminatorUtils (alongside the existing
getDiscriminatorPropertyType / getDiscriminatorSchema): getDiscriminatorPropertyType
now falls back to the mapped child schemas, chasing the discriminator property
through each child's allOf members (getDiscriminatorPropertyTypeFromChildren /
getDiscriminatorSchemaDeep). The allOf descent tracks visited schemas to guard
against a cyclic allOf composition recursing infinitely. The fallback only fires
when the own-properties lookup is empty, so the type now resolves to the enum
ref (e.g. PetType) when a child declares the discriminator property as a $ref,
and otherwise still falls back to "string".

DefaultCodegen.getDiscriminatorPropertyType becomes a thin binding that maps the
resolved schema ref name to a generator-specific model name and applies the
"string" default, as those depend on this codegen's instance state.

The first resolution branch is unchanged, so existing specs are unaffected:
regenerating the full sample suite produces no diffs.
@Ignacio-Vidal Ignacio-Vidal force-pushed the pr0-discriminator-type-from-children branch from 4483ce2 to 310c3f0 Compare June 30, 2026 22:22
@Mattias-Sehlstedt

Copy link
Copy Markdown
Contributor

Hi, see #24158 and #24156 for similar changes. For your change I would say that the same as in #24156 (comment) applies (i.e., is it really necessary to do two recursive passes?).

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.

2 participants