Skip to content

fix: do not mint a duplicate model for a single-$ref allOf property - #18

Open
rmocanu-ionos wants to merge 2 commits into
ionos-cloud-sdk-v7from
fix/single-ref-allof-no-duplicate-model
Open

fix: do not mint a duplicate model for a single-$ref allOf property#18
rmocanu-ionos wants to merge 2 commits into
ionos-cloud-sdk-v7from
fix/single-ref-allof-no-duplicate-model

Conversation

@rmocanu-ionos

Copy link
Copy Markdown

InlineModelResolver.isModelNeeded() treated a property written as

properties:
  bootVolume:
    allOf:
      - $ref: '#/components/schemas/ResourceReference' description: ...

as needing its own inline model, so the resolver minted a duplicate schema (ServerPropertiesBootVolume) instead of reusing the referenced one. The sibling keyword is the trigger: a bare $ref cannot carry a description, so spec authors wrap it in a single-element allOf, which is the idiomatic OpenAPI 3.0 workaround and means "this property IS the referenced schema".

The existing loop below dereferences each allOf member via getReferencedSchema() and reports "model needed" for any object target, which is correct for a real composition but wrong for the alias case. Handle the single-$ref case before that loop and recurse on the RAW ref schema, so the property resolves to the referenced model.

Effect on the IONOS cloudapi-v6 spec, measured A/B against an otherwise identical run: ServerProperties.bootCdrom and bootVolume resolve to ResourceReference and maintenanceWindow to ServerMaintenanceWindow, matching what sdk-go has shipped since v6.3.11. 21 exported symbols removed, 0 added, 3 dead files gone; purely subtractive. Verified for go (build + vet), java (mvn compile), node (tsc --noEmit) and python.

Note this is subtractive by construction: it removes generated types that were duplicates. Before pointing an already-released product at this build, check that none of the types it drops are types that product has shipped.

Uses a plain null/isEmpty check rather than StringUtils.isNotEmpty, which the codegen StringUtils imported by this file does not provide.

PR checklist

  • Read the contribution guidelines.
  • Pull Request title clearly describes the work in the pull request and Pull Request description provides details about how to validate the work. Missing information here may result in delayed response from the community.
  • Run the following to build the project and update samples:
    ./mvnw clean package 
    ./bin/generate-samples.sh
    ./bin/utils/export_docs_generators.sh
    
    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*.
    For Windows users, please run the script in Git BASH.
  • In case you are adding a new generator, run the following additional script :
    ./bin/utils/ensure-up-to-date
    
    Commit all changed files.
  • File the PR against the correct branch: master (6.3.0) (minor release - breaking changes with fallbacks), 7.0.x (breaking changes without fallbacks)
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

InlineModelResolver.isModelNeeded() treated a property written as

    properties:
      bootVolume:
        allOf:
          - $ref: '#/components/schemas/ResourceReference'
        description: ...

as needing its own inline model, so the resolver minted a duplicate
<Parent><Property> schema (ServerPropertiesBootVolume) instead of
reusing the referenced one. The sibling keyword is the trigger: a bare
$ref cannot carry a description, so spec authors wrap it in a
single-element allOf, which is the idiomatic OpenAPI 3.0 workaround and
means "this property IS the referenced schema".

The existing loop below dereferences each allOf member via
getReferencedSchema() and reports "model needed" for any object target,
which is correct for a real composition but wrong for the alias case.
Handle the single-$ref case before that loop and recurse on the RAW ref
schema, so the property resolves to the referenced model.

Effect on the IONOS cloudapi-v6 spec, measured A/B against an otherwise
identical run: ServerProperties.bootCdrom and bootVolume resolve to
ResourceReference and maintenanceWindow to ServerMaintenanceWindow,
matching what sdk-go has shipped since v6.3.11. 21 exported symbols
removed, 0 added, 3 dead files gone; purely subtractive. Verified for
go (build + vet), java (mvn compile), node (tsc --noEmit) and python.

Note this is subtractive by construction: it removes generated types
that were duplicates. Before pointing an already-released product at
this build, check that none of the types it drops are types that
product has shipped.

Uses a plain null/isEmpty check rather than StringUtils.isNotEmpty,
which the codegen StringUtils imported by this file does not provide.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Prevents duplicate inline models for properties represented by a single-reference allOf.

Changes:

  • Detects single-$ref allOf aliases.
  • Reuses the referenced schema instead of minting a duplicate model.
Suppressed comments (1)

modules/openapi-generator/src/main/java/org/openapitools/codegen/InlineModelResolver.java:244

  • This model-resolution regression is not covered by an automated test, although InlineModelResolverTest extensively tests flatten. Add a property with a description plus a single-$ref allOf and assert that flattening reuses the referenced schema without creating <Parent>_<property>; include a combined-composition case so the alias shortcut remains narrowly scoped.
                return isModelNeeded(m.getAllOf().get(0), visitedSchemas);

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +237 to +238
if (isSingleAllOf && m.getAllOf().get(0).get$ref() != null
&& !m.getAllOf().get(0).get$ref().isEmpty()) {

@rmocanu-ionos rmocanu-ionos Aug 12, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the regression test (testSingleRefAllOfPropertyReusesReferencedSchema + fixture) — it fails on the parent commit and passes with the fix, 45 tests green.

Skipping the guard: allOf combined with anyOf/oneOf/not occurs nowhere in public/cloud/v6.ga.yaml, public-preview/cloud/v7.ea.yml, or any fixture with a $ref allOf member, so the path is unreachable for this fork. Two notes on the reasoning though — isModelNeeded has no not check at all, and the pre-existing allOf loop already returned before the anyOf/oneOf checks, so the ordering isn't the mechanism, the changed return value is.

Regression test for the isModelNeeded change: a property written as a
single-element allOf around a $ref is an alias for the referenced schema,
so it must not get a model of its own.

Verified to discriminate rather than pass vacuously -- against the parent
commit it fails on the assertNull, since the resolver mints
ServerProperties_bootVolume there. 45 tests, 0 failures with the fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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