// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// Send `request` and read the whole reply, body included. The body is held in memory,
/// so this is for replies of a bounded size; a reply read as it arrives goes through the
/// methods of the contract one at a time.
pub async fn call(conn : &Conn, request : Request) -> (Response, Bytes) {
let stream = conn.open(request)
let response = conn.response(stream)
(response, read_to_end(conn, stream, limit=16384 * 1024))
}
///|
/// Read the rest of an exchange's body and join it into one run of octets. `limit` is
/// the most this will hold: a reply larger than that is refused rather than buffered
/// without end.
pub async fn read_to_end(
conn : &Conn,
stream : Stream,
limit~ : Int32 = 16384 * 1024,
) -> Bytes {
let buffer = @buffer.Buffer::Buffer(size_hint=1024)
while conn.read(stream) is Some(chunk) {
if buffer.length() + chunk.length() > limit {
raise Refused::Unsupported(
"a body larger than the limit this reader holds",
)
}
buffer.write_bytes(chunk)
}
buffer.to_bytes()
}
///|
/// Every field of a head as one lower-cased name to its value, the last one winning, as
/// a map lookup would.
pub fn table(fields : Array[Field]) -> Map[String, String] {
let table : Map[String, String] = Map([])
for field in fields {
table[field.key()] = field.value
}
table
}
///|
/// `name: value` lines, the form a head is shown in by a person reading a log.
pub fn lines(fields : Array[Field]) -> String {
let parts : Array[String] = []
for field in fields {
parts.push("\{field.name}: \{field.value}")
}
parts.join("\r\n")
}