Skip to content

feat!: lower a response's links to the operations they lead to - #1375

Open
OmarAlJarrah wants to merge 3 commits into
fix/ir-defaults-writers-overridesfrom
feat/ir-response-links
Open

OmarAlJarrah wants to merge 3 commits into
fix/ir-defaults-writers-overridesfrom
feat/ir-response-links

Conversation

@OmarAlJarrah

Copy link
Copy Markdown
Member

Summary

An OpenAPI link says which operation a caller can call next and where each of its inputs comes from, which docs need for "follow with getOrder" and an SDK needs for a follow call. The IR had no field for one, so the compiler kept a response's whole links map verbatim under openapi:links, and a consumer had to parse OpenAPI's runtime expressions and resolve them against the types itself.

  • IR. Response and ErrorCase gain Links []OperationLink. A link names the Operation it leads to, a LinkParam value for each of that operation's parameters, its Body, the Server it is called on, and its docs, provenance and extensions. A LinkValue is a kind beside the one field it selects, as Value is: a constant, the exchange's url, method or status, a request value as an InputPath (a parameter of the operation answered, or the request body and members within it), or a response value as a PropPath (a header, or the body and members within it). Members are named by ID, never by wire name. docs/ir/responses.md#links gives the design and the alternatives it rejects.
  • InputPath with neither a parameter nor a segment is the request body whole, which a link's requestBody: $request.body passes on; ir/input-path-empty, which refused it, is gone.
  • Compiler. A link is resolved once every operation has lowered and the type registry is whole, since it may name an operation declared after it. Targets resolve by operationId, or by operationRef to the operation mounted at that position, else the one declared there and mounted once, using the same same-document reading a $ref gets. Parameter keys resolve as <in>.<name> or a name one parameter carries. Body pointers walk the members of every content of the body. A link that does not resolve whole is kept verbatim under openapi:links/<name>, so no consumer reads a link missing a value its source states. It draws an openapi/invalid-link warning when it names nothing the document declares or breaks the expression grammar, and an openapi/untyped-link info when it is sound but reads what no link value spells, such as a member through an array index. Neither fails a compile. A $ref in a links map is now followed, so the components/links entry it names is no longer also kept on the document.
  • Checks. pass.Validate holds each LinkParam.Param to the link's target and each request value to the operation answered (pass/param-binding-mismatch). irverify holds a link value's field to its kind (ir/unknown-link-value-kind, ir/link-value-missing-payload, ir/link-value-stray-payload).
  • Elsewhere. The capability matrix gains operation-links (Smithy @references and Protobuf resource_reference are marked as references to a resource, a different fact). Correlation keys reach a server only a link names. docs/compilers/openapi.md#links is rewritten as the lowering rule.

Off the golden spec: it declares no response link (jq '[paths(objects) | select(.[-1]=="links" and .[-3]=="responses")] | length' → 0 on api-schemas @319dc756). Of 1,840 OpenAPI 3 specs in a real-world corpus, 2 declare links, 5 in all, each targeting by operationId and reading $response.body#/….

Closes #669

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.
  • testdata/conformance/openapi/response-links.yaml is rewritten to reach every value kind, a success response, an error case and a default, a $ref'd link, a server, a qualified key, a case-insensitive header and a link kept verbatim. Each link is declared before the operation it leads to, the order that would lose it if links resolved as their response lowered. The census shows it witnesses every new fact, and InputPath.param, InputPath.segments, PropPath.in and PropPath.segments, which no spec had set before.
  • Plants, each red: a header name compared case-sensitively and a request body's members walked from the response, against the conformance case; an index whose last declaration wins, against a new two-order diff of every operation's links (the harness's order oracle reads no operation); invalid-link back at error severity, against the findings table; three plants each against the irverify and pass checks.
  • operation.TestLinks_AFindingKeepsTheLinkWhole holds every invalid and untyped form the rule lists to the same outcome: no link, the link verbatim under its own key, one finding at its entry, and the severity. e2e.TestUnpreservable_AnnouncementNeverOutrunsTheEntry covers a link whose verbatim form cannot be kept. The finding then says nothing was kept, and a link that lowers converts no verbatim form at all.
  • Regenerated goldens differ from main only in links: extensions-x, yaml-merge-keys and unreferenced-components move from openapi:links to links.
  • go run ./cmd/morphic-harness passes on the fixture and on a probe of every resolved form.
  • Compiling the corpus spec whose link names request-body fields as parameters reports one invalid-link warning and lowers its other three links. Its exit status and error set match main's, two unrelated invalid references.

Breaking

IRVersion moves to 0.35.0. A consumer pinned to 0.34.0 refuses a document with a link, and finds no openapi:links entry where it read one.

An OpenAPI link says which operation a caller can call next and where
each of its inputs comes from. The IR had no field for one, so the
compiler kept a response's whole links map verbatim, and a consumer
wanting a follow call had to parse OpenAPI's runtime expressions and
resolve them against the types itself.

Response and ErrorCase now carry Links. An OperationLink names the
operation it leads to, the value it gives each of that operation's
parameters and its body, and the server it is called on. A LinkValue is
a constant, the URL, method or status of the exchange, or a value of the
request answered or of the response, resolved to an InputPath or a
PropPath, so members are named by ID rather than by wire name.

The OpenAPI compiler resolves links once every operation has lowered and
the type registry is whole, since a link may name an operation declared
after it. A link it cannot resolve whole is kept verbatim under
openapi:links/<name>, with an openapi/invalid-link warning when it names
nothing the document declares or breaks the expression grammar, and an
openapi/untyped-link info when it is sound but reads what no link value
spells, such as a member through an array index. Neither fails a
compile: nothing is lost. A $ref in a links map is now followed, so the
components/links entry it names is no longer also kept on the document.

An InputPath with neither a parameter nor a segment is now the request
body whole, which a link can pass on; ir/input-path-empty is gone.
pass.Validate holds a link's parameters to its target and its request
values to the operation answered, and irverify holds a link value's
field to its kind.

BREAKING CHANGE: IRVersion moves to 0.35.0. A consumer pinned to 0.34.0
refuses a document with a link, and finds no openapi:links entry where
it read one.
Two descriptions of a pagination input still said it was a parameter or a
body member, where an InputPath may now name the request body whole.
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