Skip to content

feat(generator): emit all documented responses (status codes + errors) via Tuyau rawResponse#1

Open
thetutlage wants to merge 1 commit into
mainfrom
feat/emit-all-responses
Open

feat(generator): emit all documented responses (status codes + errors) via Tuyau rawResponse#1
thetutlage wants to merge 1 commit into
mainfrom
feat/emit-all-responses

Conversation

@thetutlage

@thetutlage thetutlage commented Jul 8, 2026

Copy link
Copy Markdown
Owner

@outloud/adonis-openapi-generator — Emit all documented responses via Tuyau rawResponse

Problem

Route.getResponses() reads only the registry's response type and hardcodes it under status 200:

async getResponses() {
  const { type } = this.context.property("response");
  if (type.isUnknown()) return;
  const data = await this.resolve("response");
  return { 200: { description: "OK", content: { "application/json": { schema:  } } } };
}

Consequences in the generated spec:

  • Error responses are never emitted. errorResponse (401/403/422/…) is available in the Tuyau registry but nothing reads it.
  • Non-200 successes are wrong or missing. A 201 (response.created) is documented as 200; a 204 (response.noContent) resolves to an unknown body and is dropped entirely (the isUnknown() guard bails, emitting no responses at all).

Change

Drive getResponses() from Tuyau's normalized rawResponse union ({ status; response } across all status codes — see companion @tuyau/core PR). For each union member, read its status literal and body, group by status, and emit one OpenAPI response per code:

  • multiple shapes for the same code → oneOf;
  • empty / void / unknown body (e.g. 204) → response with a description and no content;
  • descriptions from a standard HTTP status-text map.

Falls back to the previous response200 behavior when rawResponse is absent (older @tuyau/core), so the generator keeps working across versions.

Implementation note (subtle)

Read each member's status/response with symbol.getTypeAtLocation(node), not symbol.getValueDeclaration()?.getType(). The latter returns the generic parameter (S / R) from the ExtractRawResponse definition rather than the instantiated literal (201) / concrete body — which silently drops every infer-derived status.

Before → after (example API)

Endpoint Before After
POST /auth/signup (response.created) 200 201, 422
POST /auth/login (two success states) 200 200 (oneOf), 422
POST /oauth/token (returns unauthorized) 200 200, 401, 422
POST /logout (response.noContent) (no responses) 204
DELETE /social-identities/:id (no responses) 204, 422

Error bodies now carry the real { errors: [...] } schema; multi-state 200s render as a discriminated oneOf; 204s have no content.

Compatibility

Requires @tuyau/core with rawResponse (companion PR). Without it, the this.context.property("rawResponse") lookup is caught and the generator uses the legacy 200-only path — no crash, no behavior change for old registries.

Known limitation (out of scope)

Only responses the controller returns through a typed helper (response.created, response.unauthorized, …) or that Tuyau appends (the auto 422) are captured. Errors that are thrown (e.g. E_INVALID_CREDENTIALS, rate-limit 429, self-handled exceptions) aren't in the controller's return type, so neither Tuyau nor this generator can see them. Surfacing those would need a separate mechanism (e.g. a decorator/annotation to declare thrown statuses).

Files touched

  • Route.getResponses() (compiled: build/index.js)

Depends on Julien-R44/tuyau#114 (adds ExtractRawResponse + rawResponse). Without it this PR no-ops gracefully via the legacy 200-only path.

🤖 Generated with Claude Code

getResponses() previously read only the `response` type and hardcoded it
under status 200. As a result error responses (401/403/422/…) were never
emitted, non-200 successes (201) were mislabeled 200, and empty-body 204s
were dropped entirely.

Drive responses from Tuyau's normalized `rawResponse` union
({ status; response } across all status codes): split the union, read each
member's instantiated status literal and body, group by status, and emit one
response per code (oneOf when a code has multiple shapes; no content for
empty 204 bodies). Falls back to the legacy `response`→200 path when
`rawResponse` is absent, so older @tuyau/core registries keep working.

Status/response literals are read via getTypeAtLocation so the instantiated
value (201 / concrete body) is used rather than the generic ExtractRawResponse
type parameters.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P3ZYnmGMEZwTuBuZviLDSq
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