Skip to content

fix!: record an OpenAPI document's $self on its source entry - #732

Merged
OmarAlJarrah merged 11 commits into
mainfrom
relevo/build-614
Oct 14, 2026
Merged

OmarAlJarrah merged 11 commits into
mainfrom
relevo/build-614

Conversation

@fuad-daoud

@fuad-daoud fuad-daoud commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

OpenAPI 3.2 lets a document declare its own URI with $self. The compiler parsed the value and then dropped it without a diagnostic. Closes #614.

ir.SourceInfo gains SelfURI (JSON selfURI), and the OpenAPI compiler fills it from the parsed document.

  • Where it lives. $self describes one input document, not the API, so it goes on that document's Sources entry beside its format, path and hash. Two alternatives lose:

    No capability-matrix row is added, because the rows list what an API can express.

  • Recorded as written. A relative $self stays relative and is not resolved against Path. OpenAPI resolves it against the retrieval URI, which the compiler does not know, and the IR records the declaration.

  • Empty means none. $self: "" and $self: null record no selfURI. An empty reference resolves to the location it was read from (RFC 3986 §5.2.2), and a document that declares no $self is known by that same location, so the two states mean the same thing.

  • Every minor. The parser reads $self at 3.0 and 3.1 as well as 3.2, and the compiler records it at all three. That matches how it reads other 3.2 fields the parser models, such as a server's name, a tag's parent and kind, the query operation and itemSchema, none of which draws a warning below 3.2. Whether a 3.0 or 3.1 document that writes such a field should get one is a single question for all of them, filed as openapi: a 3.2 field the parser models lowers silently in a 3.0 or 3.1 document #864.

  • Overlays. The recorded value is the one that was lowered. An overlay that writes $self therefore sets it on the spec's entry and replaces the declared value. The overlay's own entry records nothing.

Out of scope: how $self takes part in reference resolution. With external references allowed, the parser already resolves a non-schema $ref against $self, while a schema $ref resolves against the load path. A reference that names the document by its own $self is fetched instead of being recognised as the document itself. This behaviour predates the change, is not affected by it, and is filed as #863.

Test plan

  • go test ./ir -run TestSourceInfo_JSONEncodesSelfURI checks the wire shape byte for byte, with and without a declared URI. Each row also decodes back.

  • go test ./compilers/openapi -run TestParse_RecordsSelfURI covers:

    • $self recorded at 3.2, 3.1 and 3.0;
    • a relative URI left unresolved against the source's path;
    • none, empty and null all recording nothing;
    • no error diagnostic in any row.
  • go test ./compilers/openapi -run TestCompile_OverlaySetsTheSpecsSelfURI checks that an overlay's $self lands on the spec's entry and that the overlay's entry records none.

  • Corpus: testdata/conformance/openapi/self-uri.yaml and its golden. The case witnesses no matrix row, for the reason above, and TestConformance_UnwitnessedIRFields counts SelfURI as witnessed through it.

  • Planted defects. Each of these turns at least one of the tests above red:

    • never setting the field;
    • dropping it at 3.0 only, or at 3.1 only;
    • resolving a relative URI against Path;
    • reading $self from the raw source bytes instead of the overlaid document;
    • writing it onto the overlay's entry;
    • spelling the key selfUri;
    • dropping omitempty.

    A no-op control stays green.

  • 1,840 real-world OpenAPI 3.x specs were compiled with main and with this branch. IR (apart from irVersion), diagnostics and exit codes are identical. The one spec that declares $self gains selfURI and nothing else.

  • The full gate runs in CI. Locally I ran the touched packages, internal/archtest, engine, internal/harness and cmd/..., plus golangci-lint v2.14.0 over the whole repo. All are clean.

Breaking

ir.IRVersion moves from 0.11.0 to 0.12.0, and the ir-design §2.1 history gets an entry for it. SourceInfo gained selfURI, which changes a Document's JSON shape, so a consumer pinned to 0.11.0 refuses a 0.12.0 document. Every golden is regenerated, and each one changes only in its irVersion line.

ir.SourceInfo gains SelfURI, the URI a document declares for itself, and the
OpenAPI compiler fills it from doc.GetSelf(). It sits on the source entry rather
than on the Document because it is a fact about one input file: several may be
loaded, each may declare its own, and a document-level field could not say which
file declared it.

The URI is recorded at every supported minor, not gated to 3.2. The library
parses `$self` at every 3.x minor and reports no finding for it, so a version
gate would drop a declared value without reporting it — the defect this closes.

An empty `$self` and an unset one are one state and one field value: an empty
URI reference is not a base URI (RFC 3986 §5.1), so it identifies nothing when
documents are combined.

Out of scope, stated in the field's GoDoc: resolving relative references through
the URI (GitHub #74 owns that wiring), and reporting a `$self` written by a
dialect that does not define it. The field raises no diagnostic of its own.

IRVersion stays 0.6.0: a new optional member is none of the shape changes
docs/ir-design.md §2.1 moves the version for.
@fuad-daoud fuad-daoud self-assigned this Sep 30, 2026
@fuad-daoud fuad-daoud changed the title fix(compilers/openapi): record $self on the document's source fix(compilers/openapi): record $self on the document's source (#614) Sep 30, 2026
@fuad-daoud fuad-daoud changed the title fix(compilers/openapi): record $self on the document's source (#614) fix(compilers/openapi): record $self on the document's source Sep 30, 2026
fuad-daoud and others added 5 commits October 7, 2026 21:04
Plans and agent working notes are not part of the repository; what a
reviewer needs is in the PR body, the commits and the linked issues.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SourceInfo.SelfURI's doc comment was 144 words and assertSelfURI's 109,
so TestDocComments_StayUnderTheCap failed the gate. Both keep their
reasoning in fewer words.
The fixture predates the idSpaces declaration main introduced with IR
0.7.0. Only irVersion and idSpaces change.
SourceInfo gained selfURI. ir-design §2.1 moves the version on any change
to a Document's JSON shape, and a decoder pinned to 0.7.0 refuses the new
member, so a document carrying it is unreadable there.

BREAKING CHANGE: documents are stamped irVersion 0.8.0; consumers built
against 0.7.0 refuse them.
@fuad-daoud fuad-daoud changed the title fix(compilers/openapi): record $self on the document's source fix(compilers/openapi)!: record $self on the document's source Oct 11, 2026
main reached IRVersion 0.11.0 with #734 and #735, so SourceInfo.SelfURI
takes 0.12.0: the constant, TestCompatibleVersion's neighbour rows and the
ir-design history entry move with it, and every golden is regenerated.
Each golden changes only in its irVersion line.
SelfURI's doc said relative references resolve against where the source was
loaded from. With external references allowed, a non-schema $ref resolves
against $self when one is set, so the sentence was false; how $self takes
part in resolution is GitHub #863, and the field's doc no longer speaks to it.
It now says what the field holds: the value as written, a relative one not
resolved against Path, and the value after any overlay, on the patched
document's entry.

The reason an empty $self is recorded as none is restated. An empty
reference resolves to the location it was read from (RFC 3986 §5.2.2),
which is how a document declaring none is known; it was described as no
base URI at all.

Format, Path and Hash gain the one-line docs every other ir field has, and
the three places that listed SourceInfo's members as "format, path, content
hash" stop listing them. ir-design's lowering row says how $self is read
at every minor and points at #864 for whether a minor below 3.2 is warned.

The merge's resolution had joined SourceInfo's declaration onto TagDef's
closing brace in ir-design; it starts on its own line again.
TestParse_RecordsSelfURI claimed $self is recorded whatever minor declares
it, but its 3.0 row declared none, so dropping the value at 3.0 passed. It
now declares one at 3.2, 3.1 and 3.0, and adds rows for a relative URI that
must stay unresolved against the source's path and for a null $self.

TestCompile_OverlaySetsTheSpecsSelfURI pins where a $self an overlay writes
lands: on the spec's entry, replacing the declared one, and not on the
overlay's. The two test docs that explained the wire shape and the missing
matrix row say it in fewer words.
@OmarAlJarrah OmarAlJarrah changed the title fix(compilers/openapi)!: record $self on the document's source fix!: record an OpenAPI document's $self on its source entry Oct 14, 2026
SourceInfo's field docs state Format and Hash unconditionally, but the
table a refused compile's findings index names files nothing read and
sets Path alone (compilers.Compiler.SourceTable). The struct's doc now
says so once rather than hedging each field.

The test doc and ir-design's lowering row said every 3.2 field the
parser models is read at every minor. That was checked for a sample of
them, not all, and additionalOperations draws a validation error below
3.2, so both now say "other 3.2 fields". One wire-shape row is renamed
to read like its neighbour.
@OmarAlJarrah
OmarAlJarrah merged commit ca418c2 into main Oct 14, 2026
1 check passed
@OmarAlJarrah
OmarAlJarrah deleted the relevo/build-614 branch October 14, 2026 12:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

openapi: an OpenAPI 3.2 $self URI is dropped silently

2 participants