Skip to content

fix: handle no-body / optional-body endpoints and empty 2xx responses#5

Merged
Tam2 merged 2 commits into
mainfrom
fix/no-body-command-schema
Jul 15, 2026
Merged

fix: handle no-body / optional-body endpoints and empty 2xx responses#5
Tam2 merged 2 commits into
mainfrom
fix/no-body-command-schema

Conversation

@Tam2

@Tam2 Tam2 commented Jul 15, 2026

Copy link
Copy Markdown
Owner

Follow-up to #3 (the PATCH/PUT bare-body fix). While wiring the generated client into a real app, several related gotchas surfaced around endpoints that have no request body or an optional one, plus empty successful responses. All are fixed here with tests, including large real-world fixtures.

Fixes

1. Generator: stop requiring a body for no-body endpoints

hasBody was assumed for every POST/PATCH/PUT, so the command schema always emitted a body field. For an endpoint with no request body that's body: never, forcing callers into as any. Now the generator detects the actual requestBody per operation and emits:

  • path + body → z.object({ path, body })
  • path, no body → z.object({ path })
  • no path + body → body-directly
  • no path, no body → z.void() (no-argument action command)

Detection resolves operations["Name"] refs against the operations interface, including quoted operationIds like "activity/star-repo-for-authenticated-user" (GitHub/Stripe style).

2. Runtime: don't forward { path } as the request body

buildRequestOptions now resolves the body as hasBody ? input.body : hasPath ? undefined : input, so a path-only input sends params with no body (previously it would have sent { path } as the body).

3. Runtime: empty 2xx responses are success, not 404

handleResponse treated any response with no data as a 404 — breaking 204 No Content and void/action endpoints. It now returns success on an ok response with an empty body, and uses response.status (works for any API) instead of a Nest-specific error.statusCode.

4. Types: GetRequestBody matches optional bodies

It only matched a required requestBody:, so every optional-body operation (requestBody?:) typed to never490 such ops in the Stripe fixture, 66 in GitHub. Now matches both required and optional. Verified required + optional resolve correctly via tsc.

Tests

  • Unit + generator + type tests for every case above.
  • New real-world fixtures: test/fixtures/{stripe,github}.d.ts (api.d.ts from the public Stripe & GitHub OpenAPI specs, ~1,800 endpoints). These caught the quoted-operationId gap and guard against regressions at scale.
  • 110 tests pass. CodeRabbit review of src/: no findings.

No version bump — the release workflow handles it.

Note (not fixed here)

The expectTypeOf type tests aren't currently type-checked by vitest run (no typecheck config; tsconfig excludes test/). Worth enabling vitest --typecheck in a follow-up so the type assertions actually run in CI.

Tam2 added 2 commits July 15, 2026 16:51
…body

hasBody was assumed for every POST/PATCH/PUT, so the generated command always
emitted a body field (`body: never` for no-body endpoints), forcing callers into
`as any`. Now detect the actual requestBody per operation (resolving operations[]
refs, incl. quoted operationIds like "activity/star-repo") and emit:
  - path + body  -> z.object({ path, body })
  - path, no body -> z.object({ path })
  - no path, body -> body-directly
  - no path/body  -> z.void() (no-arg action)
Runtime: a path-only input no longer forwards { path } as the request body.

Adds Stripe + GitHub api.d.ts fixtures (real specs, ~1800 endpoints) which caught
the quoted-operationId gap. 107 tests pass. No version bump (CI release handles it).
…l bodies

Gotchas found while validating the no-body fix against real specs:
- handleResponse treated an empty 2xx body (204 No Content / void endpoints) as a 404;
  now returns success. Also uses response.status for errors instead of a Nest-specific
  error.statusCode, so any API's status codes surface.
- GetRequestBody only matched a required `requestBody:`, typing every optional-body
  endpoint (`requestBody?:`) as `never` — 490 such ops in the Stripe fixture, 66 in
  GitHub. Now matches both. Validated required+optional resolve via tsc.

110 tests pass. No version bump.
@Tam2 Tam2 merged commit 4843a03 into main Jul 15, 2026
3 checks passed
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