multipartkit
A Gleam library for parsing, querying, validating, and building
multipart messages on Erlang/BEAM and JavaScript targets. Primary
target: multipart/form-data. Secondary: multipart/mixed and
multipart/related per RFC 2046 ยง5.1.1 grammar.
- Pure Gleam โ no FFI; runs identically on Erlang/BEAM and JavaScript.
-
Full-body parser, opaque
Formbuilder, and incremental streaming parser with safe per-chunkmax_body_bytesenforcement. Content-Dispositionparser including RFC 5987 / RFC 8187filename*(UTF-8 and ISO-8859-1).-
Pluggable content-type inference โ wire
nao1215/mimetype(or any other inferer) without changing this library. - Conservative default limits, runtime-tunable.
Install
gleam add multipartkit
For the streaming API you also need
gleam_yielder:
gleam add gleam_yielderQuick start
The snippet below is the source of examples/quick_start verbatim;
copy it into src/main.gleam, run gleam add multipartkit, then
gleam run.
import gleam/io
import gleam/option.{None, Some}
import multipartkit
import multipartkit/form
import multipartkit/query
pub fn main() {
let request_form =
form.new()
|> form.add_field("title", "hello")
|> form.add_file("avatar", "cat.png", "image/png", <<137, 80, 78, 71>>)
let #(content_type, body) = multipartkit.encode_form(request_form)
let assert Ok(parts) = multipartkit.parse(body, content_type)
let assert Ok(title) = query.required_field(parts, "title")
let assert Ok(avatar) = query.required_file(parts, "avatar")
io.println("Content-Type: " <> content_type)
io.println("title=" <> title)
case avatar.filename {
Some(filename) -> io.println("avatar filename=" <> filename)
None -> io.println("avatar has no filename")
}
}Examples
The examples/ directory has four self-contained Gleam
projects you can clone, run, and modify:
| Example | Use case |
|---|---|
quick_start |
Encode a Form, parse it back, pull a field and a file. |
parse_request |
Parse an incoming HTTP request body, with strict Limits and validate.allowed_content_types. |
streaming_parse |
Feed input through parse_stream chunk-by-chunk and observe max_body_bytes being enforced incrementally. |
mimetype_inference |
Wire nao1215/mimetype into add_file_auto_with to infer Content-Type. |
cd examples/<name>
gleam run
Run them all from the repo root with just examples.
Streaming caveat (v0.1.0)
parse_stream pulls input chunks lazily and enforces max_body_bytes
incrementally, so an oversized stream is rejected at the chunk that
crosses the limit, not after the whole body is buffered. However,
each StreamPart.body is materialised as a single buffered chunk
before the part is yielded; per-part memory is bounded by
max_part_bytes rather than by an arbitrary chunk size. True
chunk-by-chunk body streaming is on the roadmap but is not part of
v0.1.0.
Public modules
multipartkit (facade), multipartkit/parser, multipartkit/encoder,
multipartkit/form, multipartkit/query, multipartkit/stream,
multipartkit/validate, multipartkit/content_disposition,
multipartkit/header, multipartkit/infer, multipartkit/limit,
multipartkit/error, multipartkit/part.
Contributing / development
The repo uses mise for toolchain pinning and
just for task running:
mise trust .mise.toml
mise install
just deps
just cijust recipes source scripts/lib/mise_bootstrap.sh so mise activate
is not required in the current shell. See
CONTRIBUTING.md for the full workflow.
License
Released under the MIT License โ see the file for details.