Repository navigation
fix(compilers/openapi): keep unmounted path-item docs and params - #1322
Merged
Merged
Conversation
A path item that mounts no operation, under paths, webhooks or a callback expression, keeps its servers, extensions and undeclared keys on the nearest node that holds an Unmodeled map. Its summary, description and parameters reached the IR in no form, and an item writing only those drew no diagnostic at all. The summary and description were dropped because the fixed keys an operation keeps them under would collide between two such items on one carrier. They now take the item's scoped key, as its extensions do: openapi:pathItem/<item pointer>/summary and .../description. The warning that announces the item covers them, so they draw no info finding of their own. Precedence across the layers of a $ref'd item now counts them whether or not the mount lowers an operation, so a summary written on both sides of a $ref is reported as a collision and the use site's is kept. Item-level parameters lower only by merging into an operation, so an item with none kept nothing of them. Each entry is now kept as written, a $ref included, under openapi:pathItem/<item pointer>/parameters/ <name>/<in>, the path its parameter ID would be derived from, a repeat numbered as its ID would be. The parameter ledger's counting is split out of next so both share it, which also reports a repeat in a list another document writes, as the operation path does. The question of a typed IR home for path-level documentation moves to its own issue; the citations in pathitem.go now point there.
A path item that mounts no operation now keeps its parameters, and a repeat in a list another document writes is reported where they are kept. DuplicateParameter's doc comment, paramLedger's and the parameters rule each listed the places a repeat is checked, and named only load and the parameter lowering. Each now names the third.
TestPathItem_WithNoOperationKeepsEachMountApart built its expected entries by appending in a loop, which prealloc reports. Writing the four entries out also makes each mount's keys readable at a glance.
The tests of a path item mounting no operation compared a kept entry's provenance by its pointer alone, so a position, a node or an inference set on it passed unseen. They now compare the whole provenance.
Two layered specs' comments said the referent declares nothing, or the same constructs as the use site; the referent's summary is now kept and the collision spec gained fields only the referent declares. paramLedger.count said its context lowers another document's content, which the path that keeps an item's parameters does not.
TestPathItem_WithNoOperationKeepsItsDocs held the service to not holding openapi:pathItemSummary, the key an operation keeps its path's summary under. That checked one spelling of the defect: a key shared between items under any other name, openapi:pathItem/summary say, passed it. The test now holds every key on each carrier, the callback's included, beneath the scope of an item that carrier holds.
5 of 6 tasks
OmarAlJarrah
added a commit
that referenced
this pull request
Nov 2, 2026
main now keeps an unmounted path item's summary and description beneath its scope (#1322), keyed by annotation.EntryKey, while an operation took the fixed pathItemDocFields keys, told apart by a carrier.operation flag. This branch keys an operation's pair beneath its scope as well, so every carrier takes annotation.EntryKey(into.scope, keyword), and the note an operation draws is keepOn's. The flag has no reader left and goes, as the provenance field and keepsDocs did; itemConstructs reads pathItemDocKeywords. The path-item-docs rule states both carriers' keys and lists both branches' tests.
17 tasks done
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 that mounts no operation, whether under
paths,webhooksor a callback expression,already keeps its servers, extensions and undeclared keys. They go on the nearest node that holds an
Unmodeledmap: the service, or the parent operation's HTTP binding for a callback. Itssummary,descriptionandparametersreached the IR in no form, though. An item that wrote only those drewno diagnostic at all:
On
main,morphic compileexits 0 with an empty stderr, and none of the three appears in thedocument.
Summary and description. They were dropped on purpose. The fixed keys an operation holds them
under (
openapi:pathItemSummary,openapi:pathItemDescription) would collide when two such itemsshare one carrier. They now take the item's scoped key, the way its extensions do:
openapi:pathItem/<item pointer>/summaryand.../description, with reasonno_ir_home. Thewarning that announces the item covers them, so they draw no info finding of their own.
Precedence across the layers of a
$ref'd item now counts them whether or not the mount lowers anoperation. So a summary written on both sides of a
$refis reported as anopenapi/path-item-ref-collision, and the use site's summary is the one kept.Parameters. Item-level parameters lower only by merging into each operation the item mounts, so
an item with no operation kept nothing of them. Each entry is now kept as written, a
$refand thefields beside it included. The key is
openapi:pathItem/<item pointer>/parameters/<name>/<in>,which is the path its parameter ID would be derived from (
ids.ParamPath, whichids.Paramnowuses too), and a repeat is numbered the same way its ID would be. Per-entry keys are needed because
one mount's list can come from several layers, and the nearer layer wins slot by slot.
paramLedger's counting is split out ofnextso both paths share it. That also reports a repeatin a list another document writes, as the operation path already does. An entry whose
$refresolves to nothing has no name or location to key it by. The resolver reports it, and it is kept
nowhere, as on an operation.
Rules and corpus. The
path-item-docs,path-item-serversandparametersrules indocs/compilers/openapi.mdnow say what an item with no operation keeps, andpath-item-docsgains a Tests line.
path-item-docs.yamlgains an item with no operation, andparam-identity.yamlgains a component item with no operation that two paths mount. This way the corpus sweep reaches
both. Its order check compares their findings but reads no node's
Unmodeled, so the kept entriesrest on their keys instead. Each key holds its item's pointer, the unit tests assert each item's
entries apart, and the docs test holds every key on a carrier beneath the scope of an item that
carrier holds. A key two items shared would fail in either order.
DuplicateParameter's doccomment,
paramLedger's and theparametersrule each list where a repeat is checked, and nowname the list such an item keeps.
Closes #383
Closes #1080
Left as they were, each stated where the next reader meets it:
Unmodeled, isir: decide whether a path item's summary and description get a typed home #1319. The
path-item-docsStatus andapplyPathItemDocsnow cite it, in place of openapi: a path item mounting no operation drops its summary and description in silence #383.$refreaches is still unreported (openapi: a list only a $ref reaches is never checked for repeats #880), for an item with nooperation just as for one with an operation. The
parametersrule's Status already says so.as
preserveUnmountedPathItemsays.While sweeping the fields a path item can write, I found that a null
additionalOperationsentrymakes the whole document undecodable, on
mainas here. It is a decoding defect apart from thischange, and is filed as #1324.
This overlaps #1327, which keys an operation's pair beneath its scope too
(
openapi:pathItem/summary) and builds a mount's entries once. The two edit the same lines ofpathitem.go,pathitemref.goand thepath-item-docsrule, so whichever lands second resolvesthem. The resolution can key every carrier by
annotation.EntryKey(into.scope, keyword), and dropthe carrier's
operationfield, which only chooses that key and the operation's info finding.Test plan
New tests in
operation:TestPathItem_WithNoOperationKeepsItsDocsandTestPathItem_WithNoOperationKeepsItsParameterscover each of the three routes. Each route has its own text, and each entry is checked for key,
value, reason and provenance. They also check that a
$refentry is kept as written and that aname holding
/and~is escaped as one segment. The docs test holds every key on eachcarrier beneath the scope of an item that carrier holds, so a key two items share fails under
any spelling.
TestPathItem_WithNoOperationNumbersARepeatedParameterTestPathItem_WithNoOperationReportsARepeatInAnotherDocumentTestPathItem_WithNoOperationKeepsNoUnresolvedParameterTestPathItem_WithNoOperationKeepsEachMountApartUpdated tests:
TestPathItem_WithNoOperationAndNothingToKeepIsSilenthad pinned a docs-only item as silent.It now holds an empty pair to that instead.
TestPathItemRefSiblings_UnmountedCollisionKeepsTheUseSitehad pinned "neither summary iskept". It now holds the use site's summary and parameter, and the referent's description and
other parameter, each at its own pointer, with each collision reported.
TestPathItem_UnmountedAnnouncementSaysWhatLandedgains a docs-only row and a parameter-onlyrow.
Twelve mutants were planted, one at a time, each against
go test ./compilers/openapi/internal/operation ./compilers/openapi/e2e -run 'TestPathItem|TestParams|TestConformance|TestGolden', and every onereddened at least one named test:
keepItemParamscallThe kept-entry checks compare an entry's provenance whole. Two more plants each set a
Nodeonthe provenance of a kept parameter or a kept summary, and each reddened the tests that cover it.
A plain pointer check would have missed both.
Three more plants each wrote, beside the scoped pair, a key no item's pointer scopes:
openapi:pathItemSummary,openapi:pathItem/summary, and the latter on a callback's carrieralone. Each reddened
TestPathItem_WithNoOperationKeepsItsDocs. The check the scope checkreplaced named the first spelling alone, and passed the second.
go test ./compilers/openapi/e2e -run 'TestGolden|TestConformance' -updatechanged only the twoextended cases. Their diffs hold the new entries and one warning per item with no operation.
The spec above, compiled with
morphic compile --verify, now holdsopenapi:pathItem/paths/~1p/summary,.../descriptionand.../parameters/trace/header, anddraws the one warning. The same item under
webhooks, and as a callback expression, lands on theservice and on the parent operation's binding respectively.
A spec with seven items that mount no operation compiled the same with every declaration reversed:
identical kept entries and the same findings. The items sit under
paths,webhooksand twocallbacks, and include a
$refwith fields beside it and a component two paths mount.make gateis left to CI.