Repository navigation
fix(compilers/openapi): lower the fields beside a path item $ref - #591
Open
fuad-daoud wants to merge 6 commits into
Open
fuad-daoud wants to merge 6 commits into
fuad-daoud wants to merge 6 commits into
Conversation
… $ref (#577) A Path Item Object that is a $ref may carry sibling fields, and the library's reference wrapper erases every one of them: operations, item parameters, servers, x-* and undeclared keys reached the IR in no form at all, with no diagnostic. They are now read raw through nodeview, modelled so their operations can be lowered, their references resolved at the mount under the compile's own external-reference policy, and their item-level constructs carried onto every operation mounted at that path. A construct both sides declare is reported under openapi/path-item-ref-collision, sited at the use site's key and naming the referent's, and the use site's declaration wins.
…rpus and the harness (#577) Adds a capability-conformance case for the fields a path item writes beside its $ref, with a planted deletion proven to redden it, the operationId and two-order collision cases, and a fixture the harness sweeps through every oracle.
…, extra routes and external policy (#577) Item 1: the reference-kind fixture gains a $ref'd header, link and example declared in components and reached from an operation written beside the $ref, each asserted on the IR; a companion case pins that a link reference in the sibling subtree is resolved (a Link Object has no IR home, so the map reaches the IR verbatim and the diagnostic is the resolution's only evidence). Item 2: the webhook and callback route tests assert the full item-level construct set the paths route asserts, through one shared helper, with the pointers under each route's own use site. Item 3: a fixture pair whose sibling reference leaves the document (resolve_main_path_item_sibling.yaml -> resolve_target_path_item_sibling.yaml), compiled with and without allow-external-refs: the target lands with the option, and the unresolved reference is reported without it. The main fixture joins harness knownInvalid with its reason. Item 4 (report only): the branch binary and a main binary lower testdata/conformance/openapi/path-item-operations.yaml byte-identically.
fuad-daoud
force-pushed
the
relevo/path-item-ref
branch
from
September 30, 2026 15:01
f869a4d to
d180afc
Compare
Collaborator
Author
|
Rebased onto |
The sibling lowering now runs under main's cross-document scoping. Each route keeps main's Within for the referenced path item and reads the fields beside the $ref in the use site's own context: siblingRefMount, the use-side operations and their unmounted remainder take that context, and applyPathItem applies each side's item constructs in its own. Because one operation lowers both sides' item parameters, each entry combinedPathParams pairs carries the scope it is read in, which lowerParameters takes up; without it a referent's parameter on a use-side operation bound the source's same-named schema, and a use-site parameter on the referent's operation lost its declaration's pointer. TestPathItemRefSiblings_EachSideReadsInItsOwnDocument pins both, and the use-side operation's own references. Groups are keyed by main's GroupID, so the webhook grouping names ids.WebhookGroup. WithExternalRefs stays beside WithMappingTargets: the sibling subtree is modelled after loading, so the loader's resolution never reaches it and its references are resolved under the same policy. corpus_test takes main's inline reasons with this branch's entry added, the doc comments the cap now covers are shortened, and the new conformance golden gains main's irVersion, idSpaces and group ID.
resolveSiblingRefs resolves with bare soa.ResolveOptions, so with external references allowed the library opens those documents itself and MaxSourceBytes and MaxSourceNodes are never charged. The doc comment now says so and points at #818, which tracks handing the loader's reader to lowering.
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
A Path Item Object that is a
$refmay carry fields of its own — OpenAPI 3.0 and 3.1 both describe a field that "appears both in the defined object and the referenced object", and leave only a same-name collision undefined. The library's reference wrapper erases every sibling:GetCore().Objectis nil on the$refbranch, so whole operations (with their parameters, bodies, responses and callbacks), the item'ssummary/description,servers, itemparameters,x-*and undeclared keys reached the IR in no form at all, with one unrelated remount warning as the only diagnostic.The issue's document now lowers
putAatop/openapi/paths/~1a/putwith provenance/paths/~1a/put, its summary underopenapi:pathItemSummaryat/paths/~1a/summary, andgetBstill mounts at both/aand/b.Mechanism
nodeview(MappingPairsfollows an alias and expands a<<merge key exactly as the resolver does), with$refremoved. Nothing is read when the source wrote nothing beside the reference, so a sibling-free item keeps the path it had.RawChildNodeand line/column survive — and unmarshalled into a*soa.PathItem, because an operation can be lowered only from a*soa.Operation.*soa.ReferencedPathItemwrapper (the document-level walk does not descend into a$ref, and a$ref'd callback's expressions are reached only from the mount), under the compile's own external-refs policy, threaded ontolowering.CtxbyWithExternalRefs. A failure is reported asopenapi/unresolved-refat the use-site pointer.mountOperation/mountedOperations) is now the only place a path-item operation is lowered, so the paths, webhooks and callbacks routes and both sides of a$refreach it alike. A sibling operation carries its own mount and declaration pointer (/paths/~1a/put), which is what keeps a repeatedoperationIda conflict (openapi/conflicting-operation-id) rather than a remount.openapi/path-item-ref-collision(warning), sited at the use site's key and naming the referent's; the use site's declaration is the one lowered at that mount, the precedence the compiler already applies to a$ref-adjacent schema sibling.No IR field, ID grammar or new ID namespace. #383, #384/#395, #396 and #414 are deliberately untouched.
Deviations worth a reviewer's eye
mountOperation/mountedOperationswere introduced so the three routes share one operation-lowering path rather than three near-copies;internal/archtest/recursion_test.gogains them in the callback recursion set.mergeParametersis now reached only from its own internal test (production callers usemergeRefParams); whether the two names should collapse is a reviewer's call.$refwhose intermediate item itself carries siblings, an external target document holding the siblings, and an alias bringing$ref+siblings to a second mount (existing alias/remount semantics, openapi: a path item mounted twice by a YAML alias is refused where the $ref form only warns #502).Merged with
mainmaingained cross-document scoping (#763), group IDs (#801) and the doc-comment cap after this branch was cut. The merge (33c1f98) combines them:Within. Each route keepsmain'slowering.Withinfor the referenced path item, and reads what is written beside the$refin the use site's own context.siblingRefMount, the use-side operations and their unmounted remainder take that context, andapplyPathItemapplies each side's item constructs in its own (pathItemMount.refIn/useIn).combinedPathParamsbuilds carries the scope it is read in (sourcedParam.in), whichlowerParametersapplies throughCtx.InScope. Without it, across documents, the referent's parameter on a use-side operation bound the source's same-named schema, and a use-site parameter on the referent's operation lost its declaration's pointer.TestPathItemRefSiblings_EachSideReadsInItsOwnDocumentpins both, along with the use-side operation's own references. Removing either scope, or lowering the use side in the referent's scope, reddens it (planted on the paths route).GroupID. The new conformance golden gainsmain'sirVersion,idSpacesand group ID, and nothing else.MaxSourceBytesandMaxSourceNodesdo not apply to them. This is stated onresolveSiblingRefsand tracked in openapi: a $ref beside a path item's $ref is read outside the external-document budgets #818. The default compile, with external references refused, is unaffected.The first
make gateline in the test plan predates this merge; the one after it is the gate on the merged head.Test plan
$ref'd parameter/request-body/response/callback inside the sibling subtree, the unresolved report, the unmounted case on all three routes.x-*, undeclared key, item parameter, with the pointers under the route's own use site), through one shared assertion helper the paths route also uses, so "the same set" is true by construction.$ref'd parameter, request body, response, callback, header and example each assert onto the lowered IR. A link is asserted as the preservedopenapi:linksentry, with a dedicated dangling-link case proving the reference is genuinely walked and resolved — no Link Object lowers to a node of its own in this compiler (GitHub openapi: response extensions dropped everywhere, links dropped on error responses #275, pinned byTestResponses_LinksPreserved), so a typed assertion is impossible today; if a later change gives it one, this fixture should gain it.resolve_main_path_item_sibling.yaml+resolve_target_path_item_sibling.yaml): a parameter written by an operation beside the$refreferences a second document. Compiled with the default options it reportsopenapi/unresolved-refat/paths/~1a, naming the target and the seam ("parameter written beside the path item's$ref… could not be resolved: external reference not allowed") — reported, not dropped; compiled with--opt allow-external-refs=trueit resolves and the parameter reaches the IR. The main fixture joins the harness corpus's known-invalid list with its reason. by CLI, both directions.TestSiblingRefMatcher_CoversTheLibraryVocabularyderives the reference-kind fields fromsoa.Matcherby reflection and requires the matcher to cover every one.irverifyclean, no duplicateOpID, and a two-order invariance case.openapi/conflicting-operation-id, never a remount.path-item-ref-siblings.yaml+ golden + table entry, with a planted deletion proven to redden it;testdata/openapi/path_item_ref_siblings.yamlswept bygo run ./cmd/morphic-harness(no-panic → no error diagnostic →irverify→ round-trip → determinism → order-invariance).make gategreen on the branch before the merge: all 7751 statements covered,golangci-lint0 issues, nolint gate, fuzz and bench-smoke pass; no existing golden changed.make gategreen on b51cd8f, the merged head with the limitation note: 100% statement coverage,golangci-lint0 issues, fuzz and bench-smoke pass.mainbinary compiletestdata/conformance/openapi/path-item-operations.yamlto identical bytes (bothsha256 0fe3ccdaf3a39123242c29926fadb51dfc4f2c4377e9adb9433fb0d45ae6cf2c, 5098 bytes), so "no golden changed" is an observation rather than only an assertion.Closes #577.