Skip to main content

Module multipart

Module multipart 

Source
Expand description

multipart/mixed reader used by streaming clients.

Translates a byte stream into a stream of typed items, one JSON item per body part. This is streaming response framing only — multipart/form-data requests, mixed per-part content types and Content-Disposition are all out of scope, and every part is assumed to carry a JSON document decodable as T.

§Framing is length-driven, not parse-driven

A part’s extent is decided by its MIME headers, never by where its JSON payload happens to become syntactically complete. Two modes, in this precedence:

  1. Content-Length: N present — consume exactly N body bytes, decode, then expect the delimiter. Exact, one part per part, no lookahead, so a part is yielded the instant its own bytes have arrived — the reader never runs a part behind a live stream.
  2. Absent — strict RFC 2046 delimiter scan. Correct, but a part is only emitted once the next delimiter (or the close delimiter) arrives, so a live producer that writes part N+1’s delimiter only when item N+1 exists leaves the reader one part behind.

Content-Length on a body part is unusual but well-formed: RFC 2046 §5.1 makes the delimiter the authoritative boundary mechanism, so a strict parser that ignores the header still reads the stream correctly. The header is a fast path, not a protocol fork.

§A missing close delimiter is a truncation

A complete multipart body ends with the --<boundary>-- close delimiter (RFC 2046). A graceful end of the byte stream that did not see it is a truncation — a proxy idle-timeout, an LB half-close, or a rolling deploy that closed the connection mid-body — and the streaming client surfaces it as a TransportError::Framing end-of-stream error, exactly as the SSE path errors on a close without a terminal event: done.

This is reported by the transport rather than left to the consumer because on the public path the consumer cannot detect it: send_streaming / open_streaming return a boxed stream that erases the concrete MultipartStream, so MultipartStream::saw_close_delimiter is unreachable there, and a generic item type carries no terminal marker of its own. (saw_close_delimiter remains available to a caller holding the concrete stream, as diagnostic detail about the framing.) A partially buffered part at end of stream is discarded rather than surfaced, mirroring how the SSE parser discards an unterminated trailing event.

An aborted body — the peer’s byte stream erroring, which is what a truncated chunked encoding looks like — is a different thing and is surfaced as TransportError::Network. That is reserved for a genuine transport fault (a server item that would not serialize at all).

A post-open domain failure is not an abort: the framer sends it as a typed error part — one application/problem+json part whose body is an RFC 9457 Problem — followed by the close delimiter. This reader decodes such a part into TransportError::Problem (which the generated client recovers as a typed CanonicalError), so a mid-stream domain error arrives as a typed Err item, not a truncation. An error part is terminal: the reader stops after it and treats it as a clean end, so a non-conforming peer cannot smuggle further data items past a reported error.

Accumulated buffers are bounded by MAX_ACCUMULATED_BYTES for the same reason the SSE parser bounds its own: otherwise a peer that streams an unterminated construct grows the buffer without limit for the lifetime of a self-healing, indefinitely-reconnecting client.

Structs§

MultipartStream
Stream yielded by parse_multipart_stream.

Constants§

MAX_ACCUMULATED_BYTES
Maximum bytes the reader accumulates for a single not-yet-complete construct — the preamble, a part’s header block, a length-less part body, or a delimiter — before treating the peer as protocol-violating and terminating the stream with TransportError::Framing. Same value and same rationale as the SSE parser’s own guard.

Functions§

boundary_from_content_type
Extract the boundary parameter from a Content-Type header value.
parse_multipart_stream
Parse a multipart/mixed byte stream into a stream of typed items.