feat(compilers/openapi): fill typed fields from vendor extensions - #355
Merged
Conversation
lowering.New now takes three caller policies: the budgets and the streaming media list arrived on main (#349, #352), the promotion mapping here. The field comment naming Grouping "one of the two" no longer counts them, and both policy types had called themselves "the second injectable-policy seam" — with Grouping there are three, so neither is second. Two lowerings this branch hooked into were refactored under it. An operation's extensions are gathered by applyOperationExtensions now, and a security scheme's by applySchemeExtensions, so the promotion runs after each rather than beside the inline reads it used to follow — it reads the assembled map, so it has to. The conformance table gained its capability column on main (#342); extension-promotion names the deprecation row, whose members are the fields it fills. It does not name vendor-extensions: extensions-x witnesses that already, and the x-* here are the means rather than the subject.
A target is a constant and an applier, and only the constant was held to anything. Declaring one without the other compiles, maps cleanly and promotes nothing — no field written, no diagnostic, no marker — which is precisely the shape every follow-up target arrives in: Pagination, Idempotency, Sensitive and the rest are constants waiting for an applier apiece. The declared set is read off the source rather than restated, so a constant added to the vocabulary reaches the check without anyone remembering to list it twice, and the read is held to finding something so a moved declaration fails instead of passing vacuously. A target whose family this package cannot yet apply fails here on purpose: adding one means adding its applier and teaching this test which applier answers for it, the way a new census keyword means adding its arm.
…ing it The guard read one file with a regex, so it missed a target declared in another file of the package and one spelled with different spacing — silently, in both cases. That is the failure the guard exists to prevent, reproduced inside the guard: a check that does not reach reads exactly like a check that passes. It parses the package now and takes every const whose declared type is ExtensionTarget, which is independent of file, spacing and grouping. All three spellings that slipped past the regex are caught, and the read is still held to finding something, so renaming the type fails rather than passing vacuously.
The parse took the declared type only, so `X = ExtensionTarget("...")` — legal
Go, and a target like any other — was read as no target at all. A vocabulary
entry added that way would have promoted nothing with nothing to say about it,
which is the defect this check exists for, missed by the check for the third
time in three spellings.
It reads both forms now. All four ways a target can be declared are caught: the
typed const the file uses, the same with odd spacing, a var in another file of
the package, and the conversion. The read is still held to finding something,
so renaming the type fails rather than passing on an empty set.
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
Several typed IR fields model information OpenAPI gives no keyword for, so the only way a document
can state it is a vendor extension — and nothing connected the two.
x-deprecated-reasonlanded inUnmodeledandir.Deprecation.Messagestayed empty at every one of the seven places the compilerbuilds a
Deprecation, with no diagnostic either way. Any emitter wanting the value had to reachinto
Unmodeledwith an ad-hocx-*lookup, which is exactly what an emitter must not do.The deliverable is the mechanism, not the two keys that demonstrate it.
The rule, recorded first.
docs/ir-design.md§12 now states what promoting a vendor extensioninto a typed field means: the mapping is injectable policy, default on and disableable; the
extension stays in
Unmodeledwith itsvendor_extensionreason, so a promotion is a secondreading and never a move; the node records the heuristic in its own
Provenance.Inferred; and anode with no
Provenanceis not promoted into, because a guess nobody can audit is worse than anempty field. Reading an extension is a heuristic — the format assigns
x-*no semantics at all —so invariant 6 governs all of it.
The seam.
Options.Promotionsmaps an extension key to a member of a closedExtensionTargetvocabulary. Adding a target later is a constant and an applier, not a change to the policy shape or
to any call site. The default mapping is deliberately small and obviously a default:
x-deprecated-reason,x-deprecated-sinceandx-sunsetare conventions no specification defines,and a caller may replace the mapping outright rather than extend it.
What it fills today: the three
ir.Deprecationmembers, at every carrier that can record thepromotion — operation, response header, type, property and security scheme.
Parameteris the onecarrier left out and the reason is in the code beside it:
ir.Parameterhas noProvenance, sorule 3 cannot be satisfied there, and giving it one is a change to
ir-design.mdrather than tothis compiler. The other extension-sourced fields —
Pagination.*,LongRunning,Idempotency,ErrorCase.Retryable/Throttling,Enum.Flags,EnumMember.Name,Sensitive,Secret— arefollow-up targets for this vocabulary;
ErrorCase.Retryable/Throttlingadditionally needs #275,since response extensions are not read at all until it lands.
Merge ordering: #341 adds
DecodeOptionsto the compiler contract, and #345 and #352 also rewriteunwitnessed.golden.txt. Whichever lands first, the others rebase; once #341 is in,Promotionsshould gain textual option names there so the CLI's
-optpath can reach it.Test plan
extension-promotionnames all five carriers individually, so a construction sitethat stops promoting fails on its own row rather than being covered by a neighbour. It also pins
the two refusals: an extension beside no
deprecated: trueannotates nothing, and a value that isnot text fills no field and is reported.
unwitnessed.golden.txtloses three entries —Deprecation.Message,Deprecation.SinceandDeprecation.RemovalVersion.Provenance.Inferredwas the fourth when this branch was written;the streaming media-type heuristic witnesses it already, so it left the list with that change.
heuristic was already recorded and is not repeated when a node is annotated twice, and that both
the exported default mapping and a caller's own map are copied rather than shared.
extending them.
Closes #252