Fix dangling OpenApiSchemaReference on net10 (#30) - #32
Merged
Conversation
Root-caused via local repro against Microsoft.AspNetCore.OpenApi 10.0.10: OpenApiSchemaTranslator constructs OpenApiSchemaReference with a null host document and FSharpSchemaTransformer never registers component schemas into the live OpenApiDocument, so references never resolve once ASP.NET 10.0.3+ started walking and resolving the schema tree strictly after transformers run. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…#30) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The floating 10.0.0-* reference resolved to exactly 10.0.0, the one version that predates the breaking schema-resolution strictness change in #30. Pinning to a current version so tests actually exercise the code path that broke.
Real WebApplication + MapOpenApi + HTTP GET /openapi/v1.json, for both a plain DU and a self-recursive DU. Fails on net10 with the exact #30 exception; passes on net9, confirming the bug and the fix are scoped to the net10 / Microsoft.OpenApi 2.x path. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds translateForDocument, which registers component schemas (including
the root schema itself under a supplied rootTypeId) into a live
OpenApiDocument and binds references to it via document.AddComponent /
OpenApiSchemaReference(id, document), so .Target actually resolves.
Also fixes self-ref ("#") ids: they previously always resolved to the
literal string "root" (rootSchema.Title was never set), which would
have collided across different self-recursive types sharing a document
once references were actually registered. translate(doc) is unchanged
for existing callers.
FSharpSchemaTransformer now calls translateForDocument with the live context.Document and the root type's id (via SchemaGeneratorConfig's existing TypeIdResolver) on net10, so component schemas are actually registered and references resolve instead of dangling. This is what made Microsoft.AspNetCore.OpenApi 10.0.3+ throw on any DU or self-recursive type.
Add e2e test proving two different self-recursive types on the same document register distinct components without collision. Document the AddComponent id-ownership assumption and the null-Document fallback's relationship to #30. Correct RELEASE_NOTES to scope the self-ref id fix to net10.
document.AddComponent is TryAdd (first writer wins, never throws), and component ids were bare case/definition names — so two different F# types sharing a case name (e.g. both having a "Leaf" or "Error" case) would silently collide in the same OpenAPI document, with the second type's schema never actually registering under its own definition. Qualify every definition id with its owning root type's name (rootTypeId + "." + typeId) when bound to a live document; the document-agnostic translate() entry point is unaffected (qualify is identity when document = None), so all pre-existing structural tests keep passing unchanged. The "." separator is required, not cosmetic: without it, different (rootTypeId, typeId) pairs can concatenate to the same string (e.g. "Order"+"LineItem" = "OrderLine"+"Item"). Also addresses the remaining deferred Minor findings from final review: precondition guard on rootTypeId, doc-comment scope fixes, restored net9 comment, and a WebApplication dispose leak in the e2e test fixtures. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The transitive default (2.0.0, floored by Microsoft.AspNetCore.OpenApi 10.0.10) is affected by GHSA-v5pm-xwqc-g5wc (stack overflow parsing circular schema refs in untrusted documents; patched at 2.7.5+). We only generate documents, never parse untrusted ones, so exposure was low, but pinning above the patched version costs nothing. net9's transitive Microsoft.OpenApi 1.x line is unaffected per the advisory and untouched. Verified: full OpenApi suite green on both net9.0 (22/22) and net10.0 (25/25) at the new pinned version; the AddComponent/OpenApiSchemaReference/ Target-resolution APIs this fix depends on behave identically. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Generator.CreateInternal's closure calls into Namotion.Reflection (via SchemaNameGenerator.Generate -> ty.ToCachedType(), and NJsonSchema's own base generator) with no synchronization. Namotion.Reflection keeps global, non-thread-safe type-metadata caches, so concurrent schema generation for different types -- exactly what Expecto's parallel test runner does -- intermittently threw "_type is not initialized" from CachedType.get_Type(), a different specific test failing each run. Reproduced on unmodified master (a2f4557, before any #30 work): 3/8 runs failed, three different tests, same signature -- confirming this predates and is unrelated to the #30 branch. Wrapping the generation closure in a lock removes the concurrent-access trigger without touching the third-party dependency: 15/15 clean runs after the fix (vs 3/8 failures before), confirmed on net8.0/net9.0/net10.0. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.
Summary
Closes #30.
Test plan
Design doc: docs/superpowers/specs/2026-07-25-openapi-dangling-schema-ref-design.md