// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// A request body that is not known when the request is made: the steps are called as the
/// connection has room for more.
pub struct Source {
length : Int64?
step : async () -> Bytes?
}
///|
/// A body whose length is not known in advance; HTTP/1.1 sends it chunked, HTTP/2 as data
/// frames.
pub fn Source::new(step : async () -> Bytes?) -> Source {
{ length: None, step, }
}
///|
/// A body of exactly `length` octets: HTTP/1.1 and HTTP/2 both declare the length, so no
/// chunking and no close-delimited body. A source that ends early or runs long fails the
/// request.
pub fn Source::sized(length : Int64, step : async () -> Bytes?) -> Source {
{ length: Some(length), step, }
}
///|
/// The length the source promises, when it promises one.
pub fn Source::length(self : Source) -> Int64? {
self.length
}
///|
/// The next piece, or `None` at the end.
pub async fn Source::next(self : Source) -> Bytes? {
(self.step)()
}
///|
/// One part of a `multipart/form-data` body (RFC 7578): a form field when `filename` is `None`,
/// a file otherwise.
#derive(Eq)
pub struct Part {
name : String
content : Bytes
filename : String?
content_type : String?
}
///|
pub extend Part with Eq::{equal, not_equal}
///|
/// A form field.
pub fn Part::field(name : StringView, text : StringView) -> Part {
{
name: name.to_owned(),
content: @utf8.encode(text),
filename: None,
content_type: None,
}
}
///|
/// A file part. `content_type` defaults to `application/octet-stream`, as browsers do.
pub fn Part::file(
name : StringView,
filename : StringView,
content : Bytes,
content_type? : StringView,
) -> Part {
{
name: name.to_owned(),
content,
filename: Some(filename.to_owned()),
content_type: Some(
content_type.unwrap_or("application/octet-stream").to_owned(),
),
}
}
///|
pub fn Part::name(self : Part) -> String {
self.name
}
///|
pub fn Part::content(self : Part) -> Bytes {
self.content
}
///|
pub fn Part::filename(self : Part) -> String? {
self.filename
}
///|
pub fn Part::content_type(self : Part) -> String? {
self.content_type
}
///|
/// A request body. One value, so a request cannot carry two bodies for the library to choose
/// between.
#derive(Eq)
pub(all) enum Body {
Empty
Text(String)
Bytes(Bytes)
Json(Json)
/// `application/x-www-form-urlencoded`, in the order given.
Form(Array[(String, String)])
/// `multipart/form-data`, RFC 7578, in the order given.
Multipart(Array[Part])
/// Pieces produced while the request is sent.
Stream(Source)
}
///|
pub extend Body with Eq::{equal, not_equal}
///|
pub impl Debug for Source with fn to_repr(_self) {
Repr("streamed body")
}
///|
pub extend Source with Debug::{to_repr}
///|
pub impl Eq for Source with fn equal(self, other) {
physical_equal(self, other)
}
///|
pub impl Eq for Source with fn not_equal(self, other) {
!physical_equal(self, other)
}
///|
pub extend Source with Eq::{equal, not_equal}
///|
pub impl Debug for Body with fn to_repr(self) {
match self {
Empty => Repr("Empty")
Text(text) => Repr(text)
Bytes(bytes) => Repr(bytes)
Json(value) => Repr(value)
Form(pairs) => Repr(pairs)
Multipart(parts) => Repr(parts.map(p => p.name))
Stream(source) => source.to_repr()
}
}
///|
pub extend Body with Debug::{to_repr}