Repository navigation
feat!: follow references into OpenAPI 3.2 components/mediaTypes - #1333
Merged
Merged
Conversation
OpenAPI 3.2 adds components/mediaTypes and lets a content entry be a Reference Object to one of its entries. The parser models neither, so the reference was never followed: the entry lowered with no schema, and the $ref and the section were kept verbatim with a warning. The load phase now models each components/mediaTypes entry and each content entry written as a $ref as a reference object of the parser's generic kind, walks them with the source, and resolves them through the parser's resolver as it resolves every other reference object, under the same reader, budgets and validation. A declaration lowers once, at its own pointer, however many content entries reach it, with the references it holds resolved where it is declared, and an entry nothing references joins the retained components. ir.Content gains Docs, which holds a media type's own description and the summary and description written beside a $ref to it. IRVersion moves to 0.27.0, and a consumer pinned to 0.26.0 refuses the documents this version writes.
A media type walk's doc comment ran one word past the cap, and a test built a slice it could preallocate.
A summary or description written empty or null beside a content entry's $ref reads as unset in a body's Content.Docs, so the declaration's own description stands. A content-style parameter or header, which keeps what that fold takes verbatim, took the empty value instead and dropped the declaration's. It now skips an unset value as the fold does. The entry's own context and pointer on mediaDecl are named for what they hold, keepContentDocs returns its keys before its diagnostics as its neighbours do, and the fault reporter is named for what it does.
This was referenced Nov 2, 2026
Three paths of the media type references had no test that a defect in them would redden. A parameter's kept docs and a body's Content.Docs could read a key apart, as an empty sibling did before the last commit; what a reference reaches in another document could go unjudged by the rule table; and a source rebuilt for an anchored document it reads could lower a reference into that document at its use site instead of at its declaration. One table test now holds the two docs readers to one answer over every spelling at the use site, the alias and the declaration. The test of each kind of reached object gains a media type, and an end-to-end test compares the IR of a rebuilt source with that of one not rebuilt.
A content entry written as a $ref lowers nothing, and nothing reports it, where it sits inside an object reached in raw YAML (#1320) or its $ref decodes as no string (#1280); the keys beside the $ref other than summary and description are not read (#1321). declaredMedia said the load phase or the rule table always reports why, and the rules named neither #1280 nor #1321. Both now state each limitation. walkSource no longer claims a document with no media type reference is walked as the library walks it: every walk yields each Media Type Object's model after it. The content rule now says a schema is named where its media type is declared, after its key only when that is in components/mediaTypes.
This was referenced Nov 2, 2026
A schema $ref whose pointer passes through a reference object's alias lowers what it reaches a second time, at the pointer it spells, beside the node of the declaration itself. Every kind such a pointer can pass does this on main; a content entry's alias into components/mediaTypes now does too, where main reported that $ref unresolved. The references rule now states it, and #1359 tracks the fix.
#1229 took IRVersion 0.27.0, so this change moves to 0.28.0: main's row and history entry stand, and this change's follow under the new number, with the shape hash of the merged tree. fillParamType returns the elected spelling, as #1229 has it, and reads its schema in the context declaring it, as this change has it.
A parameter or header whose content entry names a Media Type Object in another document reads that object's schema in the document declaring it. Two of the four calls doing so had no test: reading the parameter's schema in the entry's context placed what the parameter keeps verbatim in the wrong document, and filling the header's detail there copied a bound the schema's own node holds onto the header as well. The merge of main had to choose the first of these, between its own call and this change's. A test now compiles both carriers against another document's declarations and asserts where each kept key is placed and that a bound stays on the node alone.
At a parameter's or header's content entry, the summary the fold takes from the use site or an alias is kept under the entry's own Unmodeled key, and so is a summary the media type's declaration writes, which a Media Type Object does not define. When both are written, the census reports the declaration's as represented in no form. A body keeps both, one in Content.Docs and one in Content.Unmodeled. This is the namespace a carrier shares with what it lowers, which #396 tracks; keepContentDocs and the reference-object-siblings rule now say so.
contentOf looked its path up only to print the path item it found in a failure message, which renders as no path; response200 already checks the path, so contentOf now prints the path and media type it was asked for. A rules comment in internal/archtest ran past the wrap its neighbours keep.
This was referenced Nov 2, 2026
OmarAlJarrah
added a commit
that referenced
this pull request
Nov 2, 2026
main took IRVersion 0.29.0 for Content.Docs (#1333), so the renamed path item doc keys move to 0.30.0: a row after 0.29.0's with its new shape and the same names, and a history entry naming what a consumer pinned to 0.29.0 sees.
3 of 4 tasks
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
OpenAPI 3.2 adds
components/mediaTypesand lets a content entry be a Reference Object to one of its entries. The parser models neither, so the compiler never followed such a reference: the entry lowered with no schema, and the$refand the whole section were kept verbatim with a warning. This follows them, designed against the four defects #856 records of a first attempt.components/mediaTypesentry, and each content entry written as a$ref, as the parser's generic reference over a Media Type Object (resolve.ReferencedMediaType), walks them with the source, and hands them to the parser's resolver as it hands every other reference object: under the same reader, budgets and refusals, with what they reach held to the rule table. A content entry written as a$refinside another document is followed too.components/mediaTypeskey, however many content entries reach it (request and response bodies, content-style parameters and headers) and in whichever order they lower. The binder learns the section, so a schema$refinside an entry, or into one, is bound where it is declared.$refinside the entry lowers with what it names.$ref.ir.ContentgainsDocs: a 3.2 Media Type Object'sdescription, kept verbatim until now, and thesummaryanddescriptionbeside a$refto it, folded along the chain as every other position folds them. A content-style parameter or header, whose carrier has no docs field for its media type, keeps what the fold takes verbatim, each from the position writing it.openapi:components/mediaTypes/<name>, and the whole-section entry and both warnings go.Two library behaviours made this more than a new alias. Both are recorded as
docs/parser-library.md#media-type-referencesand pinned by a canary. The library's walk panics on a reference type it has no handler for, so the load phase yields these models itself. The resolver cannot take the model's own Media Type Object for a reference, so a$refto an inline content entry would fail. The load phase holds each of the source's Media Type Objects where the resolver looks before reading the model, and resolves the source's media type references once its walk has held them all, so such a$refreaches its target, under$selftoo.Out of scope, each filed with every site of its mechanism and stated at the code and rule that have it. The first three are shared with every other kind of reference object, and a content entry joins them by being read as one: on
mainsuch an entry lowered as written, with the warning that its$refwas not followed.$refthe model decodes no string from, such as a plain date or a scalar under a local tag, lowers nothing, and nothing reports it. A content entry written so now does the same.$refreaches in the source's raw YAML is never resolved. A media type reference inside such an object shares it, and its entry lowers noContent.$ref, other thansummaryanddescription, is dropped with no record. Content entries written as a$refnow share it with every other kind./and lands on raw YAML interns its target twice. Since the parser readscomponents/mediaTypesas raw YAML,#/components/mediaTypes/Pet/is one such pointer.$refwhose pointer passes through a reference object's alias lowers what it reaches a second time, at the pointer it spells. Every kind does this onmain; a pointer through acomponents/mediaTypesalias now does too, wheremainreported it unresolved.Unmodelednamespace with what it lowers. At a parameter's or header's content entry, asummarythe media type's declaration writes, which a Media Type Object does not define, loses its entry to the one the fold takes from the use site or an alias, and is reported as represented in no form; a body keeps both.#1229 landed with
IRVersion0.27.0 and #1325 with 0.28.0, so this change takes 0.29.0. #1269 also claims 0.27.0 from an older base; whichever of the two merges later takes the next version.Closes #856
Breaking
IRVersionmoves to 0.29.0, and a consumer pinned to 0.28.0 refuses the documents this version writes.Contentgainsdocs. A Media Type Object'sdescriptionmoves fromContent.Unmodeled["openapi:description"]toContent.Docs.Description; a content entry written as a$reflowers what it names and no longer carriesopenapi:$ref; andDocument.Unmodeled["openapi:components/mediaTypes"]gives way to one entry per entry nothing references.Test plan
operation: one declaration lowers to one type in both declaration orders, for a component of the source and of another document and for an inline content entry, with and without a reader; references inside the declaration resolve; docs fold along an alias chain at a body and at a parameter, and one an alias in another document writes is reported; a body'sContent.Docsand what a parameter keeps agree over every spelling of each key (left out, empty, null, text) at the use site, the alias and the declaration; a parameter and a header naming a media type in another document read its schema there, placing what they keep verbatim in that document and leaving a bound on the schema's own node; an unresolved reference lowers nothing; a$refholding no string draws one finding.load: the section's entries are modelled as the parser reads it; the source's walk yields each model once; a chain ends on the declaration's own model, by the key the resolver uses, under$self, and through a spelling that key misses; a fault building a reference is reported once, in whichever document writes it; what a reference reaches in another document is judged by the rule table.e2e: a source rebuilt for an anchored document it reads lowers a reference into that document as a source not rebuilt does.canary:TestLibraryBehaviour/media-type-references.component-media-types-32rewritten to exercise the feature; thelater-version-constructsandunreferenced-componentsgoldens regenerated.main,irVersionaside, as do the five OpenAPI 3.2 specs of a real-world spec corpus. Over every spec undertestdataand that corpus, 2,181 in all, only the three conformance cases this change rewrites differ frommain, with the same exit codes (9c3065c against 02c972b).$refnames a media type in another document, whose schema is deprecated andx-sensitive, are marked as ones naming that schema directly are.mainthere; the largest difference between the two builds' profiles is the runtime returning memory to the OS rather than compiler code, and such counts include that work, which moves with the machine's memory pressure. Content references scale linearly, about 3.95M instructions per operation of three references from 500 to 4,000 operations, and cost under a quarter of the same entries written inline, since a declaration lowers once.