Skip to content

[Feature]: Support multipart/form-data request bodies (file uploads) in the HTTP client #458

Description

@ALagoni97

Summary

The generated OpenAPI HTTP client cannot send multipart/form-data. Any operation that uploads a file is ungeneratable today — the request body is always serialised as JSON.

Current behaviour

src/codegen/generators/typescript/channels/protocols/http/client.ts:

  • Lines 170-172 — the body is always const body = context.payload?.marshal();. marshal() produces a JSON string from the Modelina model; there is no FormData path anywhere in the HTTP generator.
  • Line 167 — request headers hardcode 'Content-Type': 'application/json'.

The only non-JSON request encoding in the whole HTTP generator is application/x-www-form-urlencoded, and it exists solely for the internal OAuth token exchange (security.ts:523, :577) — it isn't reachable from generated operations.

The result is that an operation declared as requestBody: content: multipart/form-data: generates a function that sends a JSON string with the wrong Content-Type. The server rejects it. Nothing in the generator warns the user.

Why this matters

File upload is a common operation in public APIs: document submission, identity verification, avatars, attachments, bulk imports, media ingestion. A generator that covers every verb and every auth scheme but cannot upload a file will be discarded during evaluation by anyone whose API has a single upload endpoint — and that's a large fraction of real specifications.

It's also a correctness problem rather than a missing nicety. We accept the spec, emit code without error, and produce requests the server cannot parse. Refusing to generate would at least be honest; silently emitting a broken call is the worst of the three options.

Proposed direction

Detect multipart/form-data (and, for completeness, application/x-www-form-urlencoded) on the request body in the OpenAPI input processor, and emit a FormData-based body preparation instead of marshal():

  • Build FormData from the payload model's properties, appending File/Blob/ReadableStream values directly and JSON-encoding nested object properties as OpenAPI's encoding rules describe.
  • Do not set Content-Type manually for multipart — fetch must set it so the boundary is generated correctly. This means line 167 needs to become conditional rather than unconditional, which is the main structural change.
  • Properties typed as string with format: binary should surface in the generated payload type as something a caller can actually pass (Blob | File | Uint8Array), not string.

The encoding object in OpenAPI also allows per-part content types and headers. Supporting the common case first (plain fields + binary parts) and leaving per-part encoding for a follow-up seems reasonable — worth deciding whether to error or ignore when an unsupported encoding is present, rather than silently dropping it.

Acceptance criteria

  • An operation with a multipart/form-data request body generates a function accepting file-like values and sends a correctly-encoded multipart request.
  • Content-Type is not hardcoded for multipart requests; the boundary is produced by the runtime.
  • format: binary properties are typed usefully in the generated payload model.
  • JSON-bodied operations are unaffected.
  • Covered at all three tiers per CLAUDE.md, including a runtime test that uploads a real file and asserts the server received the parts intact.

Following "Expected Output First", the desired generated signature for an upload operation should be written by hand in test/runtime/typescript/ before the generator work starts — the ergonomics of the payload type are the hard part here, not the encoding.

Depends on / relates to

Shares the conditional-Content-Type plumbing with #457 (non-JSON responses). Whichever lands first should make line 167 content-type-aware. #459 (SSE responses) is the third part of the same content-type gap.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions