Repository navigation
openapi: derive a node's name from its coordinate in one function, and diff it against the names the lowering gives #980
Description
Problem
A node's name is derived twice. Lowerers thread a hint down and extend it with compile.SubHint (40 signatures in schema and operation declare hint string). For a $ref that reaches a coordinate before its declaration, positionHint in schema/resolve.go replays the same names from the pointer. The replay is exact beneath /components/schemas and stops elsewhere, because the owner's name is not in the pointer ("Elsewhere the enclosing hint is not in the pointer").
Whether one function of the document and the coordinate can name every node is not known. The change that removes the second derivation (#981) depends on the answer. This issue answers it with a comparison and changes no behaviour.
How far the replay reaches today
For every t/anon/… node with a source pointer, compare Naming.Hint with compile.NamingHint(positionHint(pointer)). Under /components/{requestBodies,responses,headers,parameters}/<name>/…, start the replay at the entry name and continue with positionStep from the schema token. Over 1,952 compiled specs: 115 under testdata/ and 1,837 from a local corpus of single-file OpenAPI 3 specs that is not committed (morphic @3a706a7b).
| Region | Anonymous nodes | Stored hint equals replay |
|---|---|---|
components/schemas |
20,713 | 99.76% |
components/requestBodies |
265 | 100% |
components/responses |
44 | 100% |
components/headers |
2 | 100% |
components/parameters |
73 | 58.90% |
components/pathItems |
2 | 0% |
paths |
13,117 | 22.98% |
webhooks |
10 | 30.00% |
The same comparison on cloudflare/api-schemas @843eae2f: components/schemas 6,844 nodes at 98.58%, components/requestBodies 60 at 100%, components/responses 183 at 92.90%, components/parameters 66 at 6.06%, paths 27,025 at 66.16%.
Nothing under paths is named by the pointer alone, but the owners are few. Grouping the paths and webhooks nodes by the owner path that precedes the schema token gives 13,127 nodes (same corpus):
| Owner shape | Nodes | Cumulative |
|---|---|---|
paths/{key}/{method}/responses/{code}/content/{mediaType}/schema |
5,202 | 39.63% |
paths/{key}/{method}/requestBody/content/{mediaType}/schema |
4,253 | 72.03% |
paths/{key}/{method}/parameters/{i}/schema |
3,535 | 98.96% |
paths/{key}/{method}/responses/{code}/headers/{header}/schema |
75 | 99.53% |
paths/{key}/{method}/parameters/{i}/content/{mediaType}/schema |
37 | 99.81% |
On Cloudflare three shapes cover 99.99% of its 27,025 paths nodes: response schema (18,591), parameter schema (5,012) and request body schema (3,418).
What the replay lacks is what each lowerer already reads today:
- a response:
ids.DeclarationHint(responsePointer, "response"), which is the component's entry name when the response is a$ref; - a request body:
requestBodyHint(the operation ID plus_request) or the component's entry name; - a parameter: its
namemember, not its component key; - a composition branch: its ordinal, or the name of the node its
$refresolves to (branchHint). The sampled misses undercomponents/schemasare of this kind: anallOfbranch stored asbasewhere the replay givesvariant_0.
Direction
A draft.
- Add
nameAt(doc, pointer), a pure function of the loaded document and a coordinate. Its rules are the owner shapes above, each taking the owner's name from the document the way the lowerers do, followed by thepositionSteptable for the structure beneath it. - Run it as an oracle beside the lowering: for each node of every compiled
testdata/spec, and of a corpus named by an environment variable so the uncommitted one can be used, requirenameAtto equalNaming.Hint, and report mismatches grouped by owner shape. - Post the resulting table here.
The decision gate for #981 is that at least 99% of nodes are equal on testdata/ and the corpus, and that every remaining mismatch is assigned to a rule or named as needing state only the lowering holds. If more than 1% of nodes need such state, close this issue and #981 with the reason.
Out of scope
Changing any lowerer; #981 does that. Callbacks, itemSchema and composed variants (named from their branch target) may be left to #981 if together they stay under 0.1% of anonymous nodes, or are named by a rule already in the function.
Related: #747, #729, #917, #981
Priority: P3, rule 1 (a test that changes no behaviour), raised to the tier of the work it unblocks (the naming change, P3). The comparison above over 1,952 specs → 99.76% of 20,713 components/schemas nodes equal, and five owner shapes cover 99.81% of 13,127 paths nodes (spec @843eae2f, morphic @3a706a7b).
Activity
OmarAlJarrah commentedon Oct 23, 2026
Measured with
naming.Namer, the function this proposes asnameAt, run beside the lowering ofmainat 2683dee, before the lowering adopted it: for every anonymous node and union variant a compile builds, the namer's name against theNaming.Hintthe lowering gave. The corpus is the 338 specs undertestdata/, 1,881 real-world OpenAPI 3 documents and the Cloudflare schema at e3ada35b; 1,263 of them build anonymous nodes or union variants.The first run matched 80,594 of 80,826 nodes (99.71%) and 8,862 of 8,862 variants. The 232 mismatches fell to two rules the function lacked, neither needing state only the lowering holds: the literal node an enum member lowers to when no enumeration holds the enum (
…/enum/<i>, named as aprefixItemsslot is), and a schema at the root of a document that a$refreads as a schema, which no owner names. With both rules:pathscomponents/schemascomponents/requestBodiescomponents/responsescomponents/parametersOn the Cloudflare schema alone:
paths27,054,components/schemas7,079,components/responses185,components/parameters66 andcomponents/requestBodies60 nodes, all equal.The gate holds, so #1106 makes the function the only source of names. Its corpus sweep against
mainchanges no name in any of these documents.