fix: Fix duplicate schemas from root document back-references (#1961) - #2383
Merged
Conversation
ewaostrowska
force-pushed
the
issue-1961
branch
from
August 20, 2026 13:44
1b1c19d to
b6478d4
Compare
djankows
approved these changes
Aug 21, 2026
ewaostrowska
force-pushed
the
issue-1961
branch
from
August 24, 2026 11:18
72825f6 to
32c5b11
Compare
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request
Description
When an OpenAPI file has external
$refs that point back to the root document, the parser produces duplicate schemas.Example:
The parser treated
./TestCase.yamlas a new external file, loaded it from disk again, and when it foundTestCase_Fooalready in components, renamed the imported copyTestCase_Foo_1.Root cause: path comparison used plain string matching.
./TestCase.yamlandsrc/test/resources/TestCase.yamllook different as strings even though they point to the same file.Solution: root document identity
ResolverCachenow stores arootDocumentUri— a normalized URI computed once at construction time. Every external$refis checked against it before loading.PathUtils.rootDocumentUri(String)handles three input forms:https://example.com/api/../root.yaml#sectionhttps://example.com/root.yamlfile:URIfile:///home/user/specs/../root.yamlfile:///home/user/root.yamlsrc/test/resources/root.yamlfile:///abs/path/to/root.yamlUnsupported formats (
classpath:,jar:file:) returnnull, which disables the check and preserves the original behavior.How the check works
isRootDocument(file)runs three steps on every external file load:rootDocumentUrito get an absolute URI#fragment(the file is the same regardless of which part is referenced)rootDocumentUri— equal means it's a back-reference to rootWhen a back-reference is detected, the parser calls
loadInternalRef("#/" + definitionPath)— the same lookup used for internal#/components/...references — and returns the already-loaded object from memory. No file re-read, no deserialization, no duplicate.If the object is not found or has the wrong type, it falls back to deserializing from the cached file content.
Other changes
PathUtilshelpers (moved out ofResolverCachefor reusability):rootDocumentUri(String)— normalize any supported path to a URIparentDirectoryOfUri(URI)— get the parent folder of afile:URIisHttpUri(URI)— check forhttp/httpsschemewithoutFragment(URI)— strip the#fragmentfrom a URIThis also clears a long-standing
TODOinPathUtils: "TODO use properly java URL to identify absolute URL."Parent directory resolution for
file:URIs now usesPathUtils.parentDirectoryOfUri(URI), which strips the query string and fragment before returning the parent folder. Behavior for HTTP/HTTPS and other path types is unchanged.Fixes: #1961
Why the root snapshot is needed
Resolution copies external components into the root OpenAPI model, so the live component map ends up containing both original and imported entries.
ResolverCachesaves the original root components before resolution starts. A back-reference to the root can only reuse entries from that snapshot.For example, if the root originally contains only Local, an imported Payload cannot make this reference valid:
./root.yaml#/components/schemas/PayloadThe parser reads the root file and reports the missing schema. This keeps valid root back-references deduplicated and prevents resolution order from affecting results.
Type of Change
Checklist
Screenshots / Additional Context