fix(open-api): correctness fixes for ts-client multipart/cookies/response types and composite parameter types#613
Merged
Merged
Conversation
…view - `type: object` with no `additionalProperties` (or `additionalProperties: true`) used to emit `[key: string]: ;` — an empty value type that fails `tsc` parsing. Fall back to `unknown` when the link's typescriptType is empty so the generated code stays syntactically valid. - `multipart/form-data` request bodies pre-set `Content-Type` to a bare `multipart/form-data` string (no `boundary=…`), which `fetch()` preserves verbatim — the server then fails to parse the payload because RFC 7578 requires the boundary parameter. Skip the `Content-Type` assignment for multipart bodies so `fetch` auto-computes the boundary from the `FormData` value. - Cookie parameters (`in: cookie`) were typed on the request input and then silently dropped at send time — the user's cookie never reached the server. Serialise them into a percent-encoded `Cookie:` header (both node-side and cross-origin browser callers need this, since the browser only auto-attaches cookies on same-origin requests). - Multi-media-type request bodies emitted `Content-Type: 'application/json,application/xml,…'` — wire-incorrect (servers parse Content-Type as a single value). Pick a single wire media type, preferring `application/json` / `+json` variants and falling back to the first entry. Also normalises hey-api's `in: 'formData'` parameter to `in: 'body'` in `codegen-data.ts` so language-agnostic consumers see a single body kind (matches the py-client path). Adds regression tests covering each scenario.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #613 +/- ##
==========================================
+ Coverage 89.07% 89.10% +0.03%
==========================================
Files 107 107
Lines 3477 3487 +10
Branches 757 760 +3
==========================================
+ Hits 3097 3107 +10
Misses 176 176
Partials 204 204 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
… new regression tests
114d0e3 to
5492313
Compare
FastAPI / Pydantic v2 encode `Optional[T]` parameters as inline composite
schemas on the parameter (e.g.
`{anyOf: [{type: string}, {type: null}], title: 'X-Client'}`). The
hoisting pass walked request/response JSON schemas but not parameter
schemas, so hey-api-openapi synthesised an anonymous composite named
from the title (`XClient`, `Session`, …) without emitting a
declaration, and every generated FastAPI client failed to compile:
types.gen.ts: Cannot find name 'XClient'
pyright: '"Session" is not defined'
Extends the existing hoist to also move composite parameter schemas
into `components.schemas` under `<OpId>Request<In><Name>`.
5492313 to
1ab5629
Compare
cogwirrel
approved these changes
Apr 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reason for this change
Reviewing the generated TypeScript client against a variety of OpenAPI specs — petstore v3, an
Error-schema / oneOf spec, a multipart-upload spec, an edge spec with reserved keywords and readOnly fields, a streaming spec, and finally a real FastAPI service — surfaced five correctness bugs that either produce invalid TypeScript or silently break at runtime. Each is a small, localised fix and each gets a regression test.Description of changes
type: objectwith noadditionalPropertiesemitted invalid TypeScript. The template rendered[key: string]: <link.typescriptType>;unconditionally, and the emptytypescriptTypeproduced[key: string]: ;— atscsyntax error. Fall back tounknownso the emitted code stays valid.multipart/form-databodies were sent with a boundary-lessContent-Type. The generator pre-setContent-Type: multipart/form-databeforefetch()had a chance to compute the boundary from theFormDatabody; servers cannot parse the resulting request because RFC 7578 requires theboundary=parameter. Skip the assignment for multipart bodies so the runtime populates a correct header.Cookie parameters were silently dropped.
in: cookieparameters were typed on the request input but never serialised onto the wire — the user thought they were sending a session cookie and the server never saw it. Serialise them into a percent-encodedCookie:header (both node-side and cross-origin browser callers need this explicitly).Multi-media-type request bodies produced a comma-joined
Content-Type.Content-Type: 'application/json,application/xml,application/x-www-form-urlencoded'is wire-incorrect — servers parse the header as a single value. Pick one wire type (preferringapplication/json/+jsonvariants, falling back to the first entry).Composite parameter schemas were never hoisted. FastAPI / Pydantic v2 encode
Optional[T]parameters inline as{anyOf: [T, {type: 'null'}], title: '<name>'}. The existing hoisting pass walked request/response JSON schemas intocomponents.schemasbut didn't walkparameters[].schema, so hey-api-openapi synthesised an anonymous composite type named from the title (e.g.XClient,Session,Tag) but never emitted a declaration. Every FastAPI-generated client then failed to compile withCannot find name 'XClient'/'"Session" is not defined'. The fix extends the hoister to walk parameter schemas too; once hoisted, the existing primitive-inlining path produces the correctstring | nulletc. — no lossy rewrite.Also in the shared codegen data: normalise hey-api's
in: 'formData'toin: 'body'so downstream consumers see a single body kind.Description of how you validated changes
content-type,additional-propertiesandfast-apispec files.tsxagainst a mockfetch: petstore (7 tests), complex (4 tests, including 409 error via renamed_Error), advanced (multipart → auto-boundary, Blob octet-stream, cookies, deprecated, allOf) — all green after the fix.Issue # (if applicable)
Follow-up to the review in #586.
Checklist
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license