///|
/// Buffers and decodes form pairs with the model's forgiving UTF-8 and
/// percent decoding. Both max_body_bytes and max_pairs apply.
pub fn as_form(
max_pairs~ : Int32 = 256,
) -> ResponseAs[Result[Array[(String, String)], ResponseFailure]] {
as_string().map_ok(text => @http.form_urldecode(text, max_pairs~))
}
///|
/// Buffers once within max_body_bytes, then reads the same immutable bytes
/// with both interpretations, left first. Streaming readers are excluded by
/// the argument types. Selectors run once each; rejected bodies stay bounded.
pub fn[A, B] as_both(
left : ResponseAs[A],
right : ResponseAs[B],
) -> ResponseAs[(A, B)] {
{
mode: Buffered::{ },
any_status: false,
select: (head, any) => {
let a = (left.select)(head, any || left.any_status)
let b = (right.select)(head, any || right.any_status)
{
streaming: false,
prepare: None,
excerpt: a.excerpt && b.excerpt,
read: (head, body, limit) => {
let bytes = if a.excerpt && b.excerpt {
error_excerpt(body)
} else {
read_body(body, limit)
}
let first = (a.read)(head, Bytes(bytes), limit)
let second = (b.read)(head, Bytes(bytes), limit)
(first, second)
},
}
},
}
}
///|
/// Streams LF-separated UTF-8 lines, stripping CR in CRLF and replacing
/// malformed UTF-8. A final nonempty unterminated line is delivered; a lone
/// CR is data. Limits content bytes per line (default one MiB), retaining
/// at most that limit plus a pending CR. Callback errors close the body and
/// never retry an already-delivered stream.
pub fn as_lines(
receive : async (String) -> Unit,
max_line_bytes~ : Int32 = 1024 * 1024,
) -> StreamResponseAs[Result[Unit, ResponseFailure]] {
as_stream((_, stream) => {
if max_line_bytes < 0 {
raise @http.TransportError(
kind=Invalid,
phase=Body,
detail="negative line limit",
)
}
let line = Buffer()
let mut pending_cr = false
fn append(byte : Byte) -> Unit raise @http.TransportError {
if line.length() >= max_line_bytes {
raise @http.TransportError(
kind=TooLarge(limit=max_line_bytes),
phase=Body,
detail="line exceeds limit",
)
}
line.write_byte(byte)
}
while stream.read_some() is Some(chunk) {
for byte in chunk {
if byte == b'\n' {
receive(@utf8.decode_lossy(line.to_bytes()))
line.reset()
pending_cr = false
} else {
if pending_cr {
append(b'\r')
}
pending_cr = byte == b'\r'
if !pending_cr {
append(byte)
}
}
}
}
if pending_cr {
append(b'\r')
}
if !line.is_empty() {
receive(@utf8.decode_lossy(line.to_bytes()))
}
})
}