You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Depends on #855 (wire-contract peek). This consumes #855's shared boundary-shape derivation (its DECISION C) to generate a standard OpenAPI document for a from http service. The export is the pivot artifact: it serves API consumers, client codegen, CI publishing, and a future in-editor service explorer + invoker (noted as a dependent follow-up).
Summary
Scope: Tooling / compiler output. A bynk openapi command (and library entry) that emits an OpenAPI 3.1 document for each from http service, derived from routes, handler params, boundary types, and refinements. No grammar / AST / checker-semantics / runtime changes; the emitter gains an alternate, opt-in output.
Addresses: A Bynk from http service is an HTTP API, but there is no machine-readable contract for consumers — no way to codegen a client, publish docs, or diff the API in CI. The boundary is fully specified in the source, yet unusable outside the toolchain.
Realises:bynk openapi produces a valid OpenAPI spec: paths/methods from the routes, request schemas from param types, response schemas keyed by the exact HTTP status codes the handlers return, and refinements expressed as JSON Schema constraints — no hand-maintained annotations.
Framing (why this is the language's to fix)
The spec is a lossless re-projection of facts the compiler already holds — and only it holds them consistently (the response status set comes from the HttpResult variant table plus a body walk; refinement constraints come from the same predicates the boundary re-validates). A hand-written OpenAPI file drifts from the handlers the moment either changes; a generated one cannot. This is the compiler publishing the contract it already enforces.
Routes and params are in the AST.HandlerKind::Http { method, path } (bynk-syntax/src/ast.rs:1144); handler params with the :segment path bindings; return type HttpResult[T].
The status-code table is static and complete.HttpVariant { name, status: u16, … } enumerates every HttpResult variant with its code (200/201/202/204/400/401/404/429/…, ast.rs:1226,1239). A handler's responses = the success (T on its 2xx) + the error variants its body constructs (a body walk) — so the responses object is exact, not annotated.
Refinements carry the constraints.String where NonEmpty → minLength: 1; numeric bounds → minimum/maximum; patterns → pattern — from the same Refinement/PredKind the codec re-validates (serialisation.rs).
The emitter already has an alternate-output structure (bynk-emit produces .ts, source maps, bynk-contracts.json, bynk-secrets.json); an OpenAPI document is one more derived output, opt-in.
The surface
CLI:bynk openapi [--service NAME] [--out FILE] writes an OpenAPI 3.1 document (JSON or YAML) for the project's from http service(s). Defaults to stdout so it composes in CI.
The document:
paths ← routes; path templating from :segment → {segment}.
parameters ← path/query params, with schema from the param type + refinement constraints.
responses ← the exact status set (success T + body-walked error variants), each with its body schema; error variant bodies (e.g. TooManyRequests's message) included.
components/schemas ← named boundary types, shared by $ref.
CI use: a --check mode (later) can diff the generated spec against a committed one to gate breaking API changes.
Decisions
[DECISION A] Fidelity of rich refinements (Recommended: standard JSON Schema core + x-bynk-* extensions + descriptions). Simple refinements map cleanly (minLength, minimum, maximum, pattern). Cross-field / implication predicates (e.g. status == Paid implies paymentRef.isSome(), ast.rs:931) have no JSON Schema equivalent — carry them as x-bynk-* extension keys plus a human-readable description, keeping the document valid OpenAPI while losing nothing. State the mapping explicitly.
[DECISION B] Responses: declared-only vs body-walked exact set (Recommended: body-walked exact set). List the precise responses a handler can produce — the 2xx from HttpResult[T] plus the error variants actually constructed in its body — rather than the full HttpResult vocabulary. This makes the spec a true description of behaviour, and it is derivable (the body walk #855 already needs for its response set).
[DECISION C] Output surface (Recommended: CLI + library, spec is the product; editor "export" command as a thin wrapper). The primary surface is bynk openapi (CI, codegen, publishing). The editor gets a "Bynk: Export OpenAPI" command that calls the same path. The spec — not an editor view — is the deliverable; the service explorer/invoker is a separate dependent proposal that consumes this output.
Emitter (bynk-emit). New OpenAPI generation module: routes/params/responses/schemas from the AST + the shared boundary-shape function; JSON and YAML rendering. Opt-in — never part of the normal .ts build.
CLI (bynkc/bynk). New bynk openapi subcommand (--service, --out, format flag; later --check).
Runtime. None.
VS Code extension (vscode-bynk). A thin bynk.exportOpenapi command wrapping the CLI path (optional; the CLI is the real surface).
Invalid OpenAPI from un-mappable predicates. → DECISION A: rich predicates go to x-bynk-* + description; validate generated output against an OpenAPI schema in tests.
Path-templating mismatch (:seg vs {seg}) or param location errors. → Fixtures cover path + query params and multi-segment routes; validate against the OpenAPI schema.
Docs delta
Reference / Guide / Spec: Document bynk openapi in the CLI reference; a recipe "publish your service's OpenAPI" and the refinement→JSON-Schema mapping table (incl. the x-bynk-* extensions). Note it in docs/docs/tooling.
Changelog + version history: advance the currency banner + appendix version history to the version this ships as; changelog entry under tooling.
Roadmap: record OpenAPI export shipped; note --check API-diff gating and the service explorer + invoker as follow-ups.
Tooling delta (ADR 0156 — silence is an oversight)
(The new surface is the bynk openapi output + CLI; the four LSP tooling surfaces are untouched.)
Done when
bynk openapi emits a valid OpenAPI 3.1 document for a project's from http service(s): paths/methods, path+query params with refinement-derived constraints, request bodies, and responses keyed by the exact status codes the handlers return.
Named boundary types appear once in components/schemas and are $ref'd.
Rich predicates that JSON Schema cannot express are carried as x-bynk-* + description; the document validates against the OpenAPI schema (DECISION A).
Responses reflect the body-walked exact set (DECISION B); path templating :seg → {seg} is correct.
Fixtures (next free indices) cover: the rate-limiter service (GET /check/{client} — client path param constrained minLength: 1; RateView 200 schema + 429 error body); a service with a request body (record → object) and a sum response (oneOf); a rich predicate rendered as x-bynk-* + description. Generated specs validated against the OpenAPI 3.1 schema.
Docs current per the delta above; tooling surfaces stated (LSP surfaces unchanged).
Version bump (scripts/bump-version.sh — assigned at implementation, per the stamp workflow) for a tooling increment.
A new ADR records DECISIONS A–C; its number is assigned when the implementing PR lands. The implementing PR closes this issue (Closes #<this>).
Follow-ups (noted, not in scope)
Service explorer + invoker — an in-editor Swagger-UI-style view (reusing the webview substrate) that renders this spec and can invoke endpoints against bynk dev, with refinement-aware pre-flight validation and response conformance checking. Its own proposal; the invoker's dev-vs-deployed target and effectful-method (non-GET) gating are first-class design decisions there.
bynk openapi --check — diff the generated spec against a committed baseline to gate breaking API changes in CI.
Depends on #855 (wire-contract peek). This consumes #855's shared boundary-shape derivation (its DECISION C) to generate a standard OpenAPI document for a
from httpservice. The export is the pivot artifact: it serves API consumers, client codegen, CI publishing, and a future in-editor service explorer + invoker (noted as a dependent follow-up).Summary
bynk openapicommand (and library entry) that emits an OpenAPI 3.1 document for eachfrom httpservice, derived from routes, handler params, boundary types, and refinements. No grammar / AST / checker-semantics / runtime changes; the emitter gains an alternate, opt-in output.from httpservice is an HTTP API, but there is no machine-readable contract for consumers — no way to codegen a client, publish docs, or diff the API in CI. The boundary is fully specified in the source, yet unusable outside the toolchain.bynk openapiproduces a valid OpenAPI spec: paths/methods from the routes, request schemas from param types, response schemas keyed by the exact HTTP status codes the handlers return, and refinements expressed as JSON Schema constraints — no hand-maintained annotations.Framing (why this is the language's to fix)
The spec is a lossless re-projection of facts the compiler already holds — and only it holds them consistently (the response status set comes from the
HttpResultvariant table plus a body walk; refinement constraints come from the same predicates the boundary re-validates). A hand-written OpenAPI file drifts from the handlers the moment either changes; a generated one cannot. This is the compiler publishing the contract it already enforces.What exists today (grounded)
HandlerKind::Http { method, path }(bynk-syntax/src/ast.rs:1144); handlerparamswith the:segmentpath bindings; return typeHttpResult[T].HttpVariant { name, status: u16, … }enumerates everyHttpResultvariant with its code (200/201/202/204/400/401/404/429/…,ast.rs:1226,1239). A handler's responses = the success (Ton its 2xx) + the error variants its body constructs (a body walk) — so theresponsesobject is exact, not annotated.String where NonEmpty→minLength: 1; numeric bounds →minimum/maximum; patterns →pattern— from the sameRefinement/PredKindthe codec re-validates (serialisation.rs).bynk-emitproduces.ts, source maps,bynk-contracts.json,bynk-secrets.json); an OpenAPI document is one more derived output, opt-in.The surface
bynk openapi [--service NAME] [--out FILE]writes an OpenAPI 3.1 document (JSON or YAML) for the project'sfrom httpservice(s). Defaults to stdout so it composes in CI.paths← routes; path templating from:segment→{segment}.parameters← path/query params, withschemafrom the param type + refinement constraints.requestBody← body param types → JSON Schema (records → objects, sums →oneOf).responses← the exact status set (successT+ body-walked error variants), each with its body schema; error variant bodies (e.g.TooManyRequests's message) included.components/schemas← named boundary types, shared by$ref.--checkmode (later) can diff the generated spec against a committed one to gate breaking API changes.Decisions
[DECISION A] Fidelity of rich refinements (Recommended: standard JSON Schema core +
x-bynk-*extensions + descriptions). Simple refinements map cleanly (minLength,minimum,maximum,pattern). Cross-field / implication predicates (e.g.status == Paid implies paymentRef.isSome(),ast.rs:931) have no JSON Schema equivalent — carry them asx-bynk-*extension keys plus a human-readabledescription, keeping the document valid OpenAPI while losing nothing. State the mapping explicitly.[DECISION B] Responses: declared-only vs body-walked exact set (Recommended: body-walked exact set). List the precise responses a handler can produce — the 2xx from
HttpResult[T]plus the error variants actually constructed in its body — rather than the fullHttpResultvocabulary. This makes the spec a true description of behaviour, and it is derivable (the body walk #855 already needs for its response set).[DECISION C] Output surface (Recommended: CLI + library, spec is the product; editor "export" command as a thin wrapper). The primary surface is
bynk openapi(CI, codegen, publishing). The editor gets a "Bynk: Export OpenAPI" command that calls the same path. The spec — not an editor view — is the deliverable; the service explorer/invoker is a separate dependent proposal that consumes this output.The deltas (concretely)
bynk-syntax). None.bynk-check). None beyond Wire-contract peek — surface boundary shape (contract form/hash, refinements-as-revalidation) #855's shared boundary-shape function.bynk-emit). New OpenAPI generation module: routes/params/responses/schemas from the AST + the shared boundary-shape function; JSON and YAML rendering. Opt-in — never part of the normal.tsbuild.bynkc/bynk). Newbynk openapisubcommand (--service,--out, format flag; later--check).vscode-bynk). A thinbynk.exportOpenapicommand wrapping the CLI path (optional; the CLI is the real surface).Risks & mitigations
x-bynk-*+ description; validate generated output against an OpenAPI schema in tests.:segvs{seg}) or param location errors. → Fixtures cover path + query params and multi-segment routes; validate against the OpenAPI schema.Docs delta
bynk openapiin the CLI reference; a recipe "publish your service's OpenAPI" and the refinement→JSON-Schema mapping table (incl. thex-bynk-*extensions). Note it indocs/docs/tooling.--checkAPI-diff gating and the service explorer + invoker as follow-ups.Tooling delta (ADR 0156 — silence is an oversight)
(The new surface is the
bynk openapioutput + CLI; the four LSP tooling surfaces are untouched.)Done when
bynk openapiemits a valid OpenAPI 3.1 document for a project'sfrom httpservice(s): paths/methods, path+query params with refinement-derived constraints, request bodies, and responses keyed by the exact status codes the handlers return.components/schemasand are$ref'd.x-bynk-*+ description; the document validates against the OpenAPI schema (DECISION A).:seg→{seg}is correct.GET /check/{client}—clientpath param constrainedminLength: 1;RateView200 schema + 429 error body); a service with a request body (record → object) and a sum response (oneOf); a rich predicate rendered asx-bynk-*+ description. Generated specs validated against the OpenAPI 3.1 schema.scripts/bump-version.sh— assigned at implementation, per the stamp workflow) for a tooling increment.Closes #<this>).Follow-ups (noted, not in scope)
bynk dev, with refinement-aware pre-flight validation and response conformance checking. Its own proposal; the invoker's dev-vs-deployed target and effectful-method (non-GET) gating are first-class design decisions there.bynk openapi --check— diff the generated spec against a committed baseline to gate breaking API changes in CI.