Skip to content

feat!: name what replaces a deprecated entity - #1376

Open
OmarAlJarrah wants to merge 2 commits into
feat/ir-response-linksfrom
feat/ir-deprecation-replacement
Open

OmarAlJarrah wants to merge 2 commits into
feat/ir-response-linksfrom
feat/ir-deprecation-replacement

Conversation

@OmarAlJarrah

Copy link
Copy Markdown
Member

Summary

A deprecation could carry a message, the version it began in and a scheduled removal, but not what to use instead, so a docs target could not link "use getOrderV2 instead" and an SDK could not name the replacement in the deprecation it emits.

  • IR. Deprecation gains ReplacedBy []EntityRef. An EntityRef names one entity of the document by its ID, in the field of its class, one field for each class of identity, so a consumer reads the kind of entity from the field and the reference check holds the ID as it holds any other (ir/dangling-*-ref). A replacement may be of another class than what it replaces, since a source can move a value from a query parameter into the body. docs/ir/annotations.md#replacements gives the design and the two alternatives it rejects: a bare ID string, which no check could read, and a class tag beside a string, which would untype the ID.
  • Checks. irverify holds a ref to exactly one field set (ir/entity-ref-empty, ir/entity-ref-ambiguous), and every node declaring an ID beside a Deprecation to never naming itself (ir/replaced-by-itself). A test holds EntityRef to one field per identity class, and the carrier list to every ir type declaring an ID beside a Deprecation; that pin found Message, which a first list left out. A replacement names an entity and does not use it, so ir.TypeEdges, usage and GraphQL reachability pass over it as they pass over a DocRef.
  • OpenAPI. No format states a replacement in a keyword, so the compiler fills the field only through a promotion mapping naming the new deprecation.replacedBy target. No default key names it, as none names union.open, since no spelling has become common; openapi: the default promotion mapping covers only deprecation and extensible enums #656 is where defaults are chosen. A mapped key's string or list names each replacement as the document names an entity of the node's class: an operationId, a parameter of the same operation (read as a link's key is, now one helper in lowering), a header of the same response, a property of the same model, a components/schemas name or a security scheme key. Names are resolved once the document is whole, since a replacement may be declared after what it replaces. They fill the field all or none: a name naming nothing, or the node itself, is an info openapi/degraded-construct at the key. A replacement is never lent by a $ref's target, whose class is another.
  • docs/compilers/openapi.md#extension-promotion and the promotion table in docs/ir/unmodeled.md gain the target.

Off the golden spec: no key there names a replacement (jq over every object key matching replac|supersed|successor|instead|alternative → none, api-schemas @319dc756). Of 1,840 OpenAPI 3 specs in a real-world corpus, one convention appears, in 2 test specs, naming an operationId on an operation and a parameter's name on a parameter, which is the reading this applies under a mapping.

Closes #670

Test plan

  • go test ./ir/... ./compilers/... ./pass/... ./engine/... ./internal/... ./cmd/..., make vet and golangci-lint run ./... pass; every touched package is at 100% statement coverage on its own tests.
  • replacement.TestResolve_NamesEachReplacementInItsNodesScope resolves a name on each kind of node: an operation, a parameter, a header of a success response, an error case and a default, a schema, a property and a security scheme. TestResolve_ANameNamingNothingFillsNothing covers a partial list, a self-reference and names from the wrong scope. Plants, each red: a self-reference let through; a partial list filled.
  • e2e.TestPromotion_ReplacementsResolveOnceTheDocumentIsWhole compiles a spec through Compile with a mapping. An operation names an operationId declared after it, a parameter names query.pageSize beside it, a schema names another, and a list holding a missing name fills nothing and reports at its key.
  • irverify: the shape and self-replacement checks over every carrier, plants red on the class match and the ambiguity bound, and a dangling replacement reported at its field. The walk-check cost tests stay at zero allocations on clean values: the carrier fields are indexed once from the types.
  • The harness over testdata fails exactly the 51 specs it fails on main, all deliberate negative fixtures.
  • The census lists Deprecation.replacedBy and each EntityRef member as unwitnessed: the target needs a mapping, and committed specs compile under the default one.

Breaking

IRVersion moves to 0.36.0. A consumer pinned to 0.35.0 refuses a document whose deprecation names a replacement. In Go, ir.Deprecation is no longer comparable with ==.

A deprecation could carry a message, the version it began in and a
scheduled removal, but not what to use instead, so a docs target could
not link "use X instead" and an SDK could not name the replacement in
the deprecation it emits.

Deprecation now carries ReplacedBy, a list of EntityRef values. An
EntityRef names one entity of the document by its ID, in the field of
its class, one field for each class of identity, so a consumer reads
the kind of entity from the field and the reference check holds the ID
like any other. irverify holds a ref to exactly one field set and an
entity to never naming itself; neither ir.TypeEdges, usage nor GraphQL
reachability follows a replacement, since naming a type does not use
it.

No format states a replacement in a keyword, so the OpenAPI compiler
fills the field only through a promotion mapping naming the new
deprecation.replacedBy target; no default key names it. A mapped key's
string or list names each replacement as the document names an entity
of the node's class: an operationId, a parameter of the same operation,
a header of the same response, a property of the same model, a
components schema or a security scheme. The names are resolved once the
document is whole and fill the field all or none, with an info finding
for one that names nothing or the node itself. The parameter lookup
links use moves to lowering so both read a name one way.

BREAKING CHANGE: IRVersion moves to 0.36.0. A consumer pinned to 0.35.0
refuses a document whose deprecation names a replacement. In Go,
ir.Deprecation is no longer comparable with ==.
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.

1 participant