You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
@effect/platform-node: multipart request without boundary becomes a defect (Unhandled error InvalidBoundary) instead of MultipartError #9253
import{Readable}from"node:stream"import{Cause,Effect,Exit,Stream}from"effect"import*asNodeMultipartfrom"@effect/platform-node/NodeMultipart"constbody=Readable.from([Buffer.from('--x\r\nContent-Disposition: form-data; name="a"\r\n\r\n1\r\n--x--\r\n')])constexit=awaitEffect.runPromiseExit(Stream.runCollect(// multipart content type without a boundary parameterNodeMultipart.stream(body,{"content-type": "multipart/form-data"})))console.log(Cause.hasDies(exit.cause),Cause.pretty(exit.cause).split("\n")[0])// true Error: Unhandled error. ({ _tag: 'InvalidBoundary' })
The same happens through HttpServerRequest.multipartStream / .multipart on NodeHttpServer: a request with Content-Type: multipart/form-data and no boundary turns into a defect, so the server answers 500 instead of letting the handler map a MultipartError.
What is the expected behavior?
The stream fails with a typed Multipart.MultipartError (reason Parse or similar), like other malformed bodies do, so handlers can respond 400.
What do you see instead?
A defect: Error: Unhandled error. ({ _tag: 'InvalidBoundary' }).
Additional information
It looks like MultipartStream's constructor (packages/platform/node/src/NodeMultipartParser.ts) builds the parser, and the parser reports the invalid boundary synchronously through onError, which calls this.emit("error", error) before anyone has attached an "error" listener — so Node's EventEmitter throws inside NodeStream.fromReadable's evaluate (NodeMultipart.stream), and the throw becomes a defect instead of going through onError: convertError.
Workaround we use: check the boundary up front with MultipartParser.make(...) from effect (it parses the same content-type header) and return 400 before calling multipartStream.
Activity