Skip to content

fix(bundler): compose a file once when a sequence item references it - #646

Closed
daveshanley wants to merge 1 commit into
mainfrom
fix/bundler-duplicate-schema-644
Closed

daveshanley wants to merge 1 commit into
mainfrom
fix/bundler-duplicate-schema-644

Conversation

@daveshanley

Copy link
Copy Markdown
Member

Fixes #644.

The bug

Since v0.38, BundleDocumentComposed could compose the same external file twice. The trigger is a file referenced from an item of a sequence (allOf, anyOf, oneOf, prefixItems, or an operation's parameters list) and also from a mapping slot (a property, or a components/parameters entry). For example:

  • Base and Base__schemas, with every $ref pointing at the copy. Wrapper, the schema holding the property ref, was dropped from the bundle, and the bundler logged unable to locate reference anywhere in the rolodex.
  • Limit and Limit__params for a parameter file.

The copies are identical, and code generators turn the suffix into type names. The reporter's BaseError became BaseErrorError in both oapi-codegen and Speakeasy.

Why

The composer keys each external reference by its target plus the component bucket it infers from the $ref's source path. That way a file is composed once per bucket.

The index records no position for sequence items. So a $ref directly in one has a source path ending in the key that holds the sequence:

  • [allOf] for a schema file whose root is an allOf
  • [paths, /a, get, parameters] for a parameter list

The inference expected an item index after those keys, found nothing, and left the key unscoped. The same file referenced from a property got a scoped key. Two keys means two compositions.

The reporter guessed right that the v0.38 source-contextual keys were involved.

The fix

sequenceItemComponentType in bundler/source_context.go handles the index's real path shape:

  • A $ref whose source path ends in allOf, anyOf, oneOf or prefixItems infers schemas.
  • One ending in an operation's or path item's parameters list infers parameters.
  • components/parameters is a map, so it is left alone. A component named after one of these keywords (e.g. components/parameters/allOf) still takes its bucket from the components path.

Tests

  • Regression tests from the report. The [BUG] [v0.38] Bundler duplicates a schema (Name__<dir>) when one file is referenced from both allOf and properties #644 layout is run with each of the four schema keywords, with sequential and concurrent ref extraction. Each run asserts:
    • components.schemas is exactly Wrapper, Extended and Base
    • both refs point at Base
    • nothing is logged about unresolvable references
  • The parameter variant. A single Limit, with PageLimit pointing at it.
  • The issue-928 fixture. It hit the same bug through a oneOf: its composed bundle lifted Code2Map, Code3Map and CodeUp4Map twice, as X and X__CodeXMap. A new test pins the de-duplicated schema list.
  • Inference unit cases using the exact path shapes the index produces. The existing cases use hand-written paths with item indices, which the index never emits.
  • Fail without the fix, pass with it: all three regression tests.
  • Coverage: bundler is at 100%, and go test ./... passes.
  • Golden harness: all public outputs across the 72 spec fixtures, compared with main. Exactly one output changes: the issue-928 composed bundle above, now 63 lines shorter with the duplicates gone.
  • Reporter's repro (https://github.com/mcstepp/dup-schema-repro): produces the v0.36.0 output again.

🤖 Generated with Claude Code

The composed bundler keys each external reference by its target plus the
component bucket it infers from the $ref's source path, so one file is
composed once per bucket. The index records no position for sequence
items, so a $ref directly in one ends its source path with the key that
holds the sequence: [allOf] for a schema file whose root is an allOf, or
[paths, /a, get, parameters] for an operation's parameter list. The
inference expected an item index after those keys, found nothing, and
left the key unscoped.

The same file referenced from a mapping slot (a property, or a
components/parameters entry) got a scoped key, so it was composed twice:
Base and Base__schemas, or Limit and Limit__params. Every $ref pointed at
the copy, and the schema holding the property ref was dropped from the
bundle with "unable to locate reference anywhere in the rolodex".

A $ref whose source path ends in allOf, anyOf, oneOf or prefixItems now
infers schemas, and one ending in a parameters list infers parameters.
components/parameters is a map, so it is left alone, and a component
named after one of these keywords still takes its bucket from the
components path.

The issue-928 fixture hit the same bug through a oneOf: its composed
bundle lifted Code2Map, Code3Map and CodeUp4Map twice, as X and
X__CodeXMap. It is now composed once, and a test pins that.

Fixes #644

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (47a34d4) to head (cb235f8).

Additional details and impacted files
@@            Coverage Diff            @@
##              main      #646   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          300       300           
  Lines        38451     38462   +11     
=========================================
+ Hits         38451     38462   +11     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.

[BUG] [v0.38] Bundler duplicates a schema (Name__<dir>) when one file is referenced from both allOf and properties

1 participant