Skip to content

fix: handle unresolvable $refs gracefully in parser parseRef() - #4087

Merged
mrlubos merged 4 commits into
mainfrom
pullfrog/4084-handle-unresolvable-refs
Jun 18, 2026
Merged

mrlubos merged 4 commits into
mainfrom
pullfrog/4084-handle-unresolvable-refs

Conversation

@pullfrog

@pullfrog pullfrog Bot commented Jun 18, 2026 •

Copy link
Copy Markdown
Contributor

When $RefParser.bundle() skips an unresolvable external $ref, the raw $ref string remains in the bundled spec. This causes hard failures later when parseRef() calls resolveRef() against those unresolvable refs.

Instead of catching these errors in parseRef() with try/catch, this PR adds a removeUnresolvableRefs() pass in transformOpenApiSpec that walks the entire spec before transforms and removes any $ref property that cannot be resolved. This way unresolvable refs are cleaned up upstream, and parseRef() never encounters them.

Fixes #4084

Pullfrog  | View workflow run | via Pullfrog | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

When $RefParser.bundle() skips an unresolvable external $ref, the raw $ref
string remains in the bundled spec. The shared parser's parseRef() previously
called resolveRef() without error handling, causing hard failures on those
same refs. This wraps all six resolveRef() calls across the 2.0.x, 3.0.x, and
3.1.x parsers in try/catch, logging a console.warn and continuing instead of
throwing.
@bolt-new-by-stackblitz

Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@pullfrog
pullfrog Bot requested a review from mrlubos June 18, 2026 05:40
@vercel

vercel Bot commented Jun 18, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
web Ready Ready Preview, Comment Jun 18, 2026 12:08pm

Request Review

@changeset-bot

changeset-bot Bot commented Jun 18, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6c21d81

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@hey-api/json-schema-ref-parser Patch
@hey-api/openapi-ts Patch
@hey-api/openapi-python Patch
@hey-api/shared Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jun 18, 2026
@dosubot dosubot Bot added the bug 🔥 Broken or incorrect behavior. label Jun 18, 2026
@mrlubos

mrlubos commented Jun 18, 2026

Copy link
Copy Markdown
Member

@pullfrog wouldn't it be better if we caught any such issues before we even get to parser? Instead of having to deal with the same problem later, why not remove any unresolvable refs from the spec?

@pullfrog

pullfrog Bot commented Jun 18, 2026 •

Copy link
Copy Markdown
Contributor Author

Refactored the fix per your suggestion: instead of catching unresolvable ref errors in parseRef() across all three parser versions, the spec is now cleaned up before transforms via removeUnresolvableRefs() in transformOpenApiSpec. This walks the entire OpenAPI spec tree and deletes any $ref that can't be resolved by resolveRef(), so unresolvable refs never reach the parser.

All 526 shared package tests pass, typecheck is clean.

See #4087

Task list (8/8 completed)
  • Read current PR diff and understand the existing approach
  • Read learnings and parser code for context
  • Design approach: remove unresolvable $refs before parsing
  • Add removeUnresolvableRefs utility to ref.ts
  • Call removeUnresolvableRefs from transformOpenApiSpec
  • Revert try/catch changes in parser schema files
  • Run tests and typecheck
  • Push and finalize

Pullfrog  | View workflow run | via Pullfrog | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pkg-pr-new

pkg-pr-new Bot commented Jun 18, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@hey-api/codegen-core

npm i https://pkg.pr.new/@hey-api/codegen-core@4087

@hey-api/json-schema-ref-parser

npm i https://pkg.pr.new/@hey-api/json-schema-ref-parser@4087

@hey-api/nuxt

npm i https://pkg.pr.new/@hey-api/nuxt@4087

@hey-api/openapi-ts

npm i https://pkg.pr.new/@hey-api/openapi-ts@4087

@hey-api/shared

npm i https://pkg.pr.new/@hey-api/shared@4087

@hey-api/spec-types

npm i https://pkg.pr.new/@hey-api/spec-types@4087

@hey-api/types

npm i https://pkg.pr.new/@hey-api/types@4087

@hey-api/vite-plugin

npm i https://pkg.pr.new/@hey-api/vite-plugin@4087

commit: 6c21d81

@codecov

codecov Bot commented Jun 18, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 38.54%. Comparing base (7c9463f) to head (6c21d81).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
packages/json-schema-ref-parser/src/bundle.ts 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4087      +/-   ##
==========================================
- Coverage   38.79%   38.54%   -0.26%     
==========================================
  Files         630      633       +3     
  Lines       21896    22028     +132     
  Branches     6481     6525      +44     
==========================================
- Hits         8495     8491       -4     
- Misses      10844    10938      +94     
- Partials     2557     2599      +42     
Flag Coverage Δ
unittests 38.54% <0.00%> (-0.26%) ⬇️

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.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Jun 18, 2026
@mrlubos

mrlubos commented Jun 18, 2026

Copy link
Copy Markdown
Member

@pullfrog this is already better. Any reason not to clean the spec in json-schema-ref-parser?

@pullfrog

pullfrog Bot commented Jun 18, 2026 •

Copy link
Copy Markdown
Contributor Author

Analyzed whether to clean unresolvable refs in json-schema-ref-parser instead of transformOpenApiSpec.

Recommendation: keep it in transformOpenApiSpec. The bundler already warns when it skips a ref. Deleting it at that point loses the only diagnostic trace and changes behavior for all general-purpose consumers of the package (not just this codebase). The efficiency win is negligible — the removeUnresolvableRefs walk is O(n) and trivial next to bundling. That said, moving it is a one-line change if you prefer consolidating the logic.

Task list (4/4 completed)
  • Read PR diff to understand current implementation
  • Research json-schema-ref-parser package and learnings
  • Analyze whether cleaning spec in json-schema-ref-parser is feasible/better
  • Respond via report_progress

Pullfrog  | View workflow run | via Pullfrog | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@mrlubos

mrlubos commented Jun 18, 2026

Copy link
Copy Markdown
Member

@pullfrog yes, move it, why not? We still get the warning and we skip one unnecessary walk in the later stage...

@pullfrog

pullfrog Bot commented Jun 18, 2026 •

Copy link
Copy Markdown
Contributor Author

Moved the unresolvable $ref cleanup from transformOpenApiSpec (post-bundle walk) into json-schema-ref-parser's bundle() at the exact point where unresolvable refs are already detected (inventory$Ref line 180). Just delete $ref.$ref right after the existing warning — avoids the separate O(n) walk entirely.

Removed removeUnresolvableRefs from packages/shared/src/utils/ref.ts and its call site in transforms/index.ts. All 526 shared tests + 20 json-schema-ref-parser tests pass, typecheck clean.

Task list (6/6 completed)
  • Read current PR diff and understand the implementation
  • Read json-schema-ref-parser bundle.ts to understand inventory$Ref parameters
  • Move removeUnresolvableRefs from transformOpenApiSpec into json-schema-ref-parser bundle()
  • Remove removeUnresolvableRefs function and call from transformOpenApiSpec
  • Run tests and typecheck
  • Push and finalize

Pullfrog  | View workflow run | via Pullfrog | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@dosubot dosubot Bot added size:XS This PR changes 0-9 lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Jun 18, 2026
@mrlubos

mrlubos commented Jun 18, 2026

Copy link
Copy Markdown
Member

@pullfrog can you confirm this fixes the original issue #4084? I wonder if we shouldn't do more checks for cases when $ref was the only "meaningful" field, e.g., when it was the only keyword. What would happen in those cases? Is that still a valid OpenAPI schema?

@pullfrog

pullfrog Bot commented Jun 18, 2026 •

Copy link
Copy Markdown
Contributor Author

Yes, this PR fixes #4084. The change in bundle.ts:180 does delete $ref.$ref; right after the "Skipping unresolvable $ref" warning, removing the dangling reference before the parser ever sees it. Since schemaToIrSchema() dispatches to parseRef() only when schema.$ref is truthy, the unresolvable ref is never reached — no crash.

What happens when $ref was the only meaningful field?

After deletion, the schema becomes {} (or has only non-keyword properties). The parser falls through all the early dispatch checks ($ref, enum, allOf, anyOf, oneOf, type/properties, contentMediaType) and reaches parseUnknown() at schema.ts:1377, which produces { type: 'unknown' }. No crash.

Is that still valid OpenAPI?

Technically yes — an empty/opaque schema is valid and represents "accept anything". Semantically it's a loss compared to what the spec author intended, but the reference was already unresolvable, so the intent can't be honored either way. Generating unknown is the correct graceful degradation: the user gets a working client instead of a hard failure.

The only thing to be aware of: if the unresolvable ref lives in a component schema, that component will still be registered and other parts of the spec that reference it (via a resolvable $ref) will still resolve — they'll just resolve to unknown. That's consistent and safe.

Task list (4/4 completed)

Pullfrog  | View workflow run | via Pullfrog | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Jun 18, 2026
@mrlubos
mrlubos merged commit 7d4b2e8 into main Jun 18, 2026
10 of 12 checks passed
@mrlubos
mrlubos deleted the pullfrog/4084-handle-unresolvable-refs branch June 18, 2026 12:12
@hey-api hey-api Bot mentioned this pull request Jun 18, 2026

This branch was successfully deployed

1 active deployment
Preview — 6c21d818 Deployed Jun 18, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug 🔥 Broken or incorrect behavior. lgtm This PR has been approved by a maintainer size:XS This PR changes 0-9 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Parser hard-fails on external $refs that bundle() already skipped as unresolvable

1 participant