Documentation
¶
Overview ¶
Package lowering holds the immutable context every OpenAPI lowering reads.
It is the substrate the lowering packages share rather than a stage of its own: it lowers nothing and reports nothing on its own behalf. What it owns is the answer to "what is being lowered, and what does the document say about itself" — the parsed document, the identity of the source, the indexes derived from it once at entry, and the two constructors that stamp provenance.
It is a package because the schema walk and the operation walk both need those answers and neither may reach the other (micro-compiler-design §5.1). Keeping the context with either one would make the other import it.
Index ¶
- Constants
- func DefaultExtensionPromotions() map[string]ExtensionTarget
- func DefaultStreamingMediaTypes() []string
- type Ctx
- func (c Ctx) DeclaresAuth(id ir.AuthID) bool
- func (c Ctx) DeclaresSchema(name string) bool
- func (c Ctx) DiagAt(sev ir.Severity, code, pointer, format string, args ...any) ir.Diagnostic
- func (c Ctx) ExclusiveBoundIsBoolean() bool
- func (c Ctx) MediaTypeStreams(mediaType string) bool
- func (c Ctx) NamesByReference() bool
- func (c Ctx) NamingByReference() Ctx
- func (c Ctx) PromoteDeprecation(unmodeled ir.Unmodeled, dep *ir.Deprecation, prov *ir.Provenance) []ir.Diagnostic
- func (c Ctx) ProvenanceAt(pointer string) ir.Provenance
- func (c Ctx) RefScope() resolve.Scope
- func (c Ctx) Sources() []ir.SourceInfo
- func (c Ctx) WithAuth(auth map[ir.AuthID]ir.AuthScheme) Ctx
- type ExtensionPromotions
- type ExtensionTarget
- type GroupingStrategy
- type Limits
- type StreamingMedia
Constants ¶
const ExtensionPromotionHeuristic = "extension-promotion"
ExtensionPromotionHeuristic is the name Provenance.Inferred carries on a node whose typed field was read out of a vendor extension. It is a constant because the marker is what an auditor greps for, and a spelling written at the producing site and again at a reading test can drift.
const StreamingMediaTypeHeuristic = "streaming-media-type"
StreamingMediaTypeHeuristic is the name Provenance.Inferred carries on an operation whose streaming was read out of a media type rather than declared. It is a constant because the marker is what an auditor greps for, and a spelling written at the producing site and again at a reading test can drift.
Variables ¶
This section is empty.
Functions ¶
func DefaultExtensionPromotions ¶
func DefaultExtensionPromotions() map[string]ExtensionTarget
DefaultExtensionPromotions is the mapping the policy uses when the caller states none. It is a default and not a standard: OpenAPI defines none of these keys, and each is simply the spelling that has become common for a field the format never gave a keyword. A document using another spelling is not wrong — it names its own mapping.
func DefaultStreamingMediaTypes ¶
func DefaultStreamingMediaTypes() []string
DefaultStreamingMediaTypes is the list the policy uses when the caller states none. It is a default and not a standard: `text/event-stream` is the only registered one of the three, and the two JSON-lines spellings are conventions that happen to be what generators in this space already look for. A document using a fourth spelling is not wrong — it names its own list.
Types ¶
type Ctx ¶
type Ctx struct {
// Doc is the parsed, reference-resolved source document. Lowering reads it
// and never writes through it.
Doc *soa.OpenAPI
// Source is the identity of the loaded source, stamped into Document.Sources.
Source ir.SourceInfo
// SrcIndex is this source's index within the compile, stamped into every
// Provenance.
SrcIndex int
// Grouping selects how operations are grouped into OperationGroups. It is one
// of the caller policies the context carries — the budgets, the streaming
// media list and the promotion mapping are the others; everything else here
// is a fact about the document.
//
// It arrives as the caller wrote it, normalized or not — the compiler's
// Options fills an unset one in before building a context, but nothing here
// enforces that. A strategy the operation lowering does not recognize groups
// by tags, which is what makes the unnormalized zero value harmless rather
// than a second spelling of the default to keep in step.
Grouping GroupingStrategy
// Limits is the caller's budget for the constructs the walk builds. It is the
// other fact about the caller, and like Grouping it arrives already resolved:
// the compiler's Options fills the unset budgets in and translates its own
// spelling of "unbounded" before building a context, so the zero value here
// simply bounds nothing.
Limits Limits
// contains filtered or unexported fields
}
Ctx is everything a lowering may read and none may change: the parsed document, the grouping policy the caller chose, the identity of the source being lowered, and the indexes derived from them once at entry.
It is a value rather than a pointer, so a function that takes one takes a copy. Every lowering takes it as a parameter (#177), which is what makes "immutable" enforceable rather than conventional: there is no shared holder left to write through. The maps below stay unexported for the other half of it — a copy shares a map rather than copying it, so an exported one would be the single part of a by-value context a callee could still reach.
Call sites bind it to c, never to ctx: the styleguide reserves that identifier for the context.Context a Compile takes, and operations.go spends it again on an opContext local, so a third meaning would be shadowed at both sites.
func New ¶
func New(srcIndex int, doc *soa.OpenAPI, src ir.SourceInfo, grouping GroupingStrategy, limits Limits, streaming StreamingMedia, promotions ExtensionPromotions, origin overlay.Origin) Ctx
New derives the immutable context for one loaded source.
It takes the document and its identity rather than the loader's own result type, which is what keeps this package below the loader for everything but the version grammar, and what lets a test build a context without a load.
The schema-name index is built here rather than on first use so that every reader sees the same set regardless of source order: a $ref or a discriminator mapping resolved mid-lowering must see a component declared later in the document as a valid target. It stays nil for a document that declares no components, which reads the same as an empty set.
The streaming policy is normalized into its lookup set here for a related reason: normalizing at each reader would be as many places for the comparison to differ as there are readers, and a media type that matched at one of them and not another would classify one direction of an operation and not the other.
The promotion policy is normalized here too, and copied rather than shared, so no lowering can write through the context into the map the caller passed.
The $dynamicAnchor index is deliberately not derived here, though GitHub #172 asked for it. Building it emits a diagnostic when the walk hits its bounds, so building it is a lowering action rather than context: done at entry, that warning would reach documents that never write $dynamicRef, changing what the compiler reports about them. It stays where it is, built on first use.
func (Ctx) DeclaresAuth ¶
DeclaresAuth reports whether the document declares the security scheme a requirement names. It is a predicate rather than a getter for the reason DeclaresSchema is: handing back the map would make it writable by every caller, which is the one thing keeping it unexported was for.
func (Ctx) DeclaresSchema ¶
DeclaresSchema reports whether the document declares a component schema of this name. A name it does not declare is not a resolvable $ref target.
func (Ctx) DiagAt ¶
DiagAt builds one diagnostic at pointer, stamped with this compile's source index.
It returns rather than records. Those are two different jobs, and separating them is what lets both rules hold at once: GitHub #86 wants provenance built in exactly one place, because hand-writing it is how a diagnostic shipped with none (GitHub #43); micro-compiler-design §4 wants diagnostics returned rather than accumulated through a handle, because accumulation is the side effect the conversion exists to remove. A constructor that stamps and hands back satisfies both, and a lowering that has no accumulator yet can still be sure of its provenance.
func (Ctx) ExclusiveBoundIsBoolean ¶
ExclusiveBoundIsBoolean reports whether this document's dialect spells exclusiveMinimum/exclusiveMaximum as a boolean modifier (OpenAPI 3.0) rather than a numeric bound (the 2020-12 dialect of 3.1 and 3.2). An unrecognized version defaults to the 2020-12 numeric form.
It reads the document version, which makes it a question about the context rather than about any schema — annotation.Constraints takes the answer as a parameter precisely so the reader never has to ask it.
The version is read through its accessor so a context with no document answers like an unrecognized version rather than panicking, which is how the other two readers behave on a zero value.
func (Ctx) MediaTypeStreams ¶
MediaTypeStreams reports whether the policy classifies mediaType as a stream of frames. It is a predicate rather than a getter for the reason DeclaresSchema is: handing back the set would make it writable through a copy of the context.
func (Ctx) NamesByReference ¶
NamesByReference reports whether names minted under c are placeholders a declaration replaces. See NamingByReference.
func (Ctx) NamingByReference ¶
NamingByReference returns a copy of c marking everything lowered under it as reached through a reference naming a coordinate rather than through the declaration that owns it.
A $ref can spell a pointer inside another declaration's body, and the node interned there is then named by whichever of the two lowerings arrives first. A name belongs to a declaration rather than to a reference to it, so a lowering running under this context names provisionally and the declaration replaces the name when it arrives (GitHub #372).
It marks the whole subtree, not just the referenced coordinate: a reference to an object body interns its children too, and their names are derived from the enclosing one, so they are placeholders for the same reason.
A copy, not a fresh context: everything below still needs the document, its identity and index, and the declared-name index.
func (Ctx) PromoteDeprecation ¶
func (c Ctx) PromoteDeprecation(unmodeled ir.Unmodeled, dep *ir.Deprecation, prov *ir.Provenance) []ir.Diagnostic
PromoteDeprecation fills dep's fields from the vendor extensions kept in unmodeled, and marks prov with the heuristic when it writes anything.
It reads the preserved Unmodeled entries rather than the source node, which is what makes "the extension survives its own promotion" structural instead of a rule each call site has to remember: there is nothing here that could consume an entry.
A nil dep is the whole answer for a node that is not deprecated — the field describes a deprecation, so an x-deprecated-reason beside no `deprecated: true` annotates nothing and stays where it is.
func (Ctx) ProvenanceAt ¶
func (c Ctx) ProvenanceAt(pointer string) ir.Provenance
ProvenanceAt is where a Provenance is built, and the only place this compiler spells the source index into one.
It covers the entities as well as the diagnostics. GitHub #86 scoped itself to diagnostic sites because that is where the defect it chased showed up, but the defect is hand-writing the pair at all: a Provenance whose source index is wrong misattributes a node just as surely as it misattributes a report.
Being the one place is also what makes an overlay's contribution traceable without touching a single lowering: a position the overlay introduced or rewrote names the overlay as its source, because the question is asked here rather than answered from a field each caller reads.
func (Ctx) RefScope ¶
RefScope is the context seen as a reference-resolution scope: the document's own path, and what it declares.
It is derived on use rather than stored beside the context. A stored copy would be a second place the same two facts live, free to disagree with the context after any change to it — and the whole point of the context is that there is one answer.
func (Ctx) Sources ¶
func (c Ctx) Sources() []ir.SourceInfo
Sources is the document's input files, in the order Provenance.Source indexes them: the source being lowered, then the overlay applied to it if there was one.
It is built here rather than by the compiler that assembles the Document because the two facts it joins are already the context's, and a second place that pairs a source index with a SourceInfo is a second place they can disagree — which would misattribute every node rather than fail.
func (Ctx) WithAuth ¶
WithAuth returns a copy of c carrying the resolved security schemes.
Only the service walk is given the extended value; the phases that run before it keep the plain one. That is what keeps "populated partway through" from being a trap: the lowerings that run before the schemes are resolved never hold a context that could answer this, so there is no window in which it reads empty.
A copy, not a fresh context: everything the service walk is about to lower — the document, its identity and index, and the declared-name index derived at entry — has to survive the extension.
type ExtensionPromotions ¶
type ExtensionPromotions struct {
// Disabled turns promotion off. Off means off: every extension is kept
// verbatim and no typed field is written from one.
Disabled bool `json:"disabled,omitempty"`
// Targets replaces the default map rather than extending it, so a caller who
// states a mapping gets exactly that mapping. Empty means the default. Keys
// are extension names as the document writes them, x- prefix included.
Targets map[string]ExtensionTarget `json:"targets,omitempty"`
}
ExtensionPromotions is the vendor-extension promotion policy: which x-* keys are read into which typed IR field.
It is a policy rather than a table in the lowering because OpenAPI assigns an x-* key no semantics whatsoever, so reading one as anything is a guess about a convention (architecture principle 6). A promoted field is marked ExtensionPromotionHeuristic in its node's provenance and the extension stays in Unmodeled untouched, which is what makes the guess auditable and reversible: a consumer that disagrees can ignore the typed field and read the entry itself.
type ExtensionTarget ¶
type ExtensionTarget string
ExtensionTarget names one typed IR field a vendor extension can be read into.
It is a closed vocabulary rather than a free-form path because a promotion has to be applied by code that knows the field's type, and a name nothing implements would be a policy that silently does nothing.
const ( // TargetDeprecationMessage fills ir.Deprecation.Message. TargetDeprecationMessage ExtensionTarget = "deprecation.message" // TargetDeprecationSince fills ir.Deprecation.Since. TargetDeprecationSince ExtensionTarget = "deprecation.since" // TargetDeprecationRemovalVersion fills ir.Deprecation.RemovalVersion. TargetDeprecationRemovalVersion ExtensionTarget = "deprecation.removalVersion" )
The typed fields promotion can fill today. Every other field an extension is the only OpenAPI spelling for — Pagination, LongRunning, Idempotency, ErrorCase.Retryable/Throttling, Enum.Flags, EnumMember.Name, Sensitive and Secret — is a target this vocabulary is meant to grow, not a decision against it (GitHub #252).
type GroupingStrategy ¶
type GroupingStrategy string
GroupingStrategy selects how operations are grouped into OperationGroups. It is the injectable-policy seam (architecture principle 6): grouping is inferred policy, not source semantics, and can be switched or disabled.
The vocabulary is declared here, below both walks, and re-exported by the compiler's public options rather than restated there. One declaration is what keeps the two spellings from drifting apart: a strategy the public type names and the lowering does not recognize would silently fall through to the default, and nothing would report it.
const ( // GroupByTags groups operations by their first OpenAPI tag (default). GroupByTags GroupingStrategy = "tags" // GroupByPathPrefix groups operations by the first path segment. GroupByPathPrefix GroupingStrategy = "path-prefix" )
Grouping strategies.
type Limits ¶
type Limits struct {
// MaxEnumMembers bounds the members of a single enum.
MaxEnumMembers int
}
Limits is the share of the compiler's resource budgets the lowering enforces: the ones measuring a construct the walk builds, rather than the source document the load phase measures before any of it is built.
It is a separate type from the compiler's public openapi.Limits for the reason load.Options is separate from openapi.Options — that type's shape is a published contract, and most of it describes phases this one cannot see. The compiler projects one onto the other at entry.
Zero is unbounded in every field, which is the opposite of the public type's spelling and deliberate: the projection resolves defaults and translates the public spelling of "unbounded" before anything reaches here, so a budget still zero at this point is one no caller set.
func (Limits) EnumMembersExceeded ¶
EnumMembersExceeded reports whether an enum declaring n members is past the budget. It is a predicate rather than a read of the field so that "zero is unbounded" is decided here once, and not restated by each site that asks.
type StreamingMedia ¶
type StreamingMedia struct {
// Disabled turns the inference off. Off means off: an operation then carries
// the streaming fields a 3.2 itemSchema declares and nothing else, which is
// what a caller who does not want guesses in their IR asked for.
Disabled bool `json:"disabled,omitempty"`
// MediaTypes replaces the default list rather than extending it, so a caller
// who states a list gets exactly that list. Empty means the default.
//
// Entries are matched against the media type alone: the comparison is
// case-insensitive and ignores parameters, because `text/event-stream` and
// `text/event-stream; charset=utf-8` name one type.
MediaTypes []string `json:"mediaTypes,omitempty"`
}
StreamingMedia is the media-type streaming policy: which media types mean "this body is a sequence of frames" in a document that declares nothing saying so.
It is a policy rather than a table in the lowering because the reading is a guess (architecture principle 6). OpenAPI below 3.2 has no keyword for a sequential body at all, so an SSE or NDJSON API says what it does only by naming a media type — and a media type is a content encoding, not a promise about framing. The vocabulary is declared here, below both walks, and re-exported by the compiler's public options for the reason GroupingStrategy is: one declaration cannot drift from itself.