fix(compilers/openapi): elect content over a co-declared schema - #374
Open
OmarAlJarrah wants to merge 1 commit into
Open
fix(compilers/openapi): elect content over a co-declared schema#374OmarAlJarrah wants to merge 1 commit into
OmarAlJarrah wants to merge 1 commit into
Conversation
OpenAPI says a parameter — and a header, which follows the parameter rules — contains either a `schema` property or a `content` property, never both. A document writing both was accepted in silence: one keyword lowered, the other was dropped with no diagnostic in either channel. The two positions also disagreed about which survived. fillParamType tried `content` first and returned, so the schema was lost; the header path tried `schema` first, so the content map was. The same source shape lowered differently depending only on where the object sat. Both positions now share one election. `content` wins: a media-type entry carries a schema *and* the media type serializing it, and the IR models both here — HTTPParamBinding.ContentType and Property.Encoding.MediaType — so electing it leaves nothing modelled behind, where electing `schema` would push a declared wire fact into an opaque payload. The specification does not settle it: 3.1 names `schema` first in the sentence forbidding both and 3.2 names `content` first, and a prohibition states no precedence either way. Of the two behaviours that were here, only the parameter's was a decision — the header read `schema` first because it read nothing else until a content arm was appended below it. The passed-over keyword is kept verbatim under Unmodeled with ReasonDegradedLowering at its own pointer, and a warning names both spellings — the shape recordSkippedFamilies and singleContentEntry already use for the other cases of "the source declared more than this position can lower".
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 says a parameter — and a header, which follows the parameter rules — contains either a
schemaproperty or acontentproperty, never both. A document writing both was accepted insilence: one keyword lowered, the other was dropped, with no diagnostic in either channel. The two
positions also disagreed about which one survived.
fillParamTypetriedcontentfirst andreturned, so the co-declared schema was lost;
headerSchematriedschemafirst, so the contentmap was. The same source shape lowered differently depending only on where the object sat.
Both positions now share one election, and the keyword it passes over is kept verbatim under
Unmodeled(degraded_lowering, at its own pointer) with a warning naming both spellings — theshape
schema.dispatchOf/recordSkippedFamiliesandsingleContentEntryalready use for theother cases of "the source declared more than this position can lower".
Why
contentwins. A media-type entry carries a schema and the media type serializing it,and the IR models both at these positions —
HTTPParamBinding.ContentTypeandProperty.Encoding.MediaType. Electingcontenttherefore leaves nothing modelled behind, whereelecting
schemawould demote a declared wire fact the IR does hold into an opaqueUnmodeledpayload; either way the loser survives verbatim, so the only question is which one keeps more of
the document in modelled form.
The specification does not settle it, in either direction: 3.1 writes "A parameter MUST contain
either a
schemaproperty, or acontentproperty, but not both" and 3.2 writes "ParameterObjects MUST include either a
contentfield or aschemafield, but not both". The order flipsbetween versions, and a prohibition states no precedence in either spelling.
Of the two behaviours already here, only one was a decision.
fillParamTypereadcontentfirstfrom the day it was written; the header path read
schemafirst because it read nothing elseuntil a content arm was appended below it in #139 — its order records how the two arms were added,
not a choice between them.
An empty or unusable
contentmap runs the election the other way: nothing is elected from it, theschema lowers, and the
contentnode is kept and named on the same terms. That direction is onlyreachable for documents the library's own validator already rejects, but it keeps the rule
symmetric — and a
contentthe model layer failed to parse still has a raw node worth keeping.Test plan
gofmt,go vet ./...,golangci-lint run(0 issues),go build ./..., and./scripts/check-coverage.sh(100% of 4945 statements) all pass.The issue's reproducer, before and after
Compiled with
morphic compile repro.yaml -skip-validate.Before (
dbf0054) —.diagnosticsisnull, and the two positions disagree:{ "diagnostics": null, "params[0].type.target": "t/prim/string", "params[0].unmodeled": null, "responses[0].headers[0].type.target": "t/prim/integer", "responses[0].headers[0].unmodeled": null }After — one order at both positions, the loser kept, both reported:
{ "diagnostics": [ {"severity": "warning", "code": "openapi/degraded-construct", "message": "a parameter or header declares either schema or content, not both; this one declares both, so it lowered as its content, with schema kept verbatim under Unmodeled", "provenance": {"source": 0, "pointer": "/paths/~1x/get/parameters/0/schema"}}, {"severity": "warning", "code": "openapi/degraded-construct", "message": "a parameter or header declares either schema or content, not both; this one declares both, so it lowered as its content, with schema kept verbatim under Unmodeled", "provenance": {"source": 0, "pointer": "/paths/~1x/get/responses/200/headers/X-H/schema"}} ], "params[0]": { "type": {"target": "t/prim/string"}, "unmodeled": {"openapi:schema": {"reason": "degraded_lowering", "value": {"type": "integer"}, "provenance": {"source": 0, "pointer": "/paths/~1x/get/parameters/0/schema"}}} }, "bindings.http[0].paramBindings[0].contentType": "application/json", "responses[0].headers[0]": { "type": {"target": "t/prim/string"}, "encoding": {"mediaType": "application/json"}, "unmodeled": {"openapi:schema": {"reason": "degraded_lowering", "value": {"type": "integer"}, "provenance": {"source": 0, "pointer": "/paths/~1x/get/responses/200/headers/X-H/schema"}}} } }Coverage added
testdata/conformance/openapi/codeclared-schema-content.yamlputs the shape in the corpus, sothe two-order oracle, the JSON round-trip, determinism and
irverifyall run over it. Itdeclares the co-declaration twice in opposite keyword orders (
pwritesschemafirst,qwrites
contentfirst) and the golden shows the two lowering identically. Confirmed the sweepactually reaches the new file rather than merely running: a deliberate dangling
$refadded toit reddens
TestHarness_InRepoCorpus, and removing it goes green again.TestElectTypeSpelling_CoDeclaredSpellingsElectContentasserts the elected type, the modelledmedia type, the preserved payload and its reason and pointer, and the message text, at both
positions.
TestElectTypeSpelling_SoleSpellingReportsNothingis the control: a position writing onespelling keeps nothing and says nothing, so the change cannot fire on well-formed documents.
TestElectTypeSpelling_UnusableContentElectsSchemaAndKeepsItcovers the other direction.Both plants redden
The fix is two call sites, so the old behaviour was restored at each in turn and the tests run.
Restoring
fillParamType's content-first early return:Restoring the header path's schema-first early return:
Both were restored from a copy afterwards and the tree is green.
Closes #320