// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// One field of a message head: the name, and the value, as they go on the wire.
#derive(Eq, Debug)
pub(all) struct Field {
name : String
value : String
}
///|
pub extend Field with Eq::{equal, not_equal}
///|
pub extend Field with Debug::{to_repr}
///|
pub fn Field::new(name : String, value : String) -> Field {
{ name, value, }
}
///|
/// The name in the form a comparison uses: HTTP field names are case-insensitive
/// (RFC 9110 ยง5.1), and a lower-case copy is what every lookup compares.
pub fn Field::key(self : Field) -> String {
self.name.to_lower()
}
///|
/// Where a message's body comes from.
#derive(Eq, Debug)
pub(all) enum Body {
/// No body: the message ends where its head does.
Empty
/// A body that is at hand, sent in full before the reply is read.
Bytes(Bytes)
/// A body the caller sends with `send` after the exchange is open, in pieces whose
/// length is not known when the head goes out.
Streaming
}
///|
pub extend Body with Eq::{equal, not_equal}
///|
pub extend Body with Debug::{to_repr}
///|
/// A request: what to ask for, and what to carry along.
#derive(Eq, Debug)
pub(all) struct Request {
verb : String
target : String
fields : Array[Field]
body : Body
}
///|
pub extend Request with Eq::{equal, not_equal}
///|
pub extend Request with Debug::{to_repr}
///|
pub fn Request::new(
verb : String,
target : String,
fields~ : Array[Field] = [],
body~ : Body = Empty,
) -> Request {
{ verb, target, fields, body, }
}
///|
/// A reply head: the status and the fields. The body is read through the connection,
/// because it may be larger than anything worth holding at once.
#derive(Eq, Debug)
pub(all) struct Response {
status : Int32
fields : Array[Field]
}
///|
pub extend Response with Eq::{equal, not_equal}
///|
pub extend Response with Debug::{to_repr}
///|
/// One exchange on a connection, named by the connection rather than by the protocol
/// underneath: on a connection that carries one exchange at a time it is 1, and on one
/// that multiplexes it is whatever that connection gave out.
#derive(Eq, Debug)
pub(all) struct Stream {
id : Int32
}
///|
pub extend Stream with Eq::{equal, not_equal}
///|
pub extend Stream with Debug::{to_repr}
///|
/// What a connection will not do.
#derive(Eq, Debug)
pub(all) suberror Refused {
/// The peer's octets are not what the protocol allows.
Malformed(String)
/// The request, or the peer, asks for something this connection does not do.
Unsupported(String)
/// The connection ended before the exchange did.
Closed
/// The socket failed under the connection.
Broken(String)
}
///|
pub extend Refused with Eq::{equal, not_equal}
///|
pub extend Refused with Debug::{to_repr}
///|
/// The one contract a connection presents, whatever carries it: HTTP/1.1, HTTP/2,
/// HTTP/3 or WebSocket. Code written against this runs on any of them; an
/// implementation is all a new protocol needs.
///
/// An exchange is opened, the request body is sent through it, and the reply's head and
/// body are read from it. A connection that carries one exchange at a time says so
/// through `is_ready`, which a caller waits on before opening the next.
pub(open) trait Conn {
/// Open an exchange on `request`, and send its head and whatever body came with it.
async fn open(self : Self, request : Request) -> Stream
/// Send more of the request's body; `end` marks the last of it. Only an exchange
/// opened with `Body::Streaming` takes this.
async fn send(self : Self, stream : Stream, bytes : Bytes, end~ : Bool) -> Unit
/// The reply's head, once the peer has sent it.
async fn response(self : Self, stream : Stream) -> Response
/// The next run of the reply's body, read as it arrives; `None` ends it.
async fn read(self : Self, stream : Stream) -> Bytes?
/// The fields that came after the body, once `read` has ended it.
fn trailers(self : Self, stream : Stream) -> Array[Field]
/// Whether another exchange can start on this connection now. False while one is in
/// flight on a connection that carries them one at a time, and after it has ended.
fn is_ready(self : Self) -> Bool
/// The protocol the connection settled on, as ALPN names it: `http/1.1`, `h2`, `h3`.
fn kind(self : Self) -> String
/// End the connection, in whatever way the protocol under it asks for.
async fn close(self : Self) -> Unit
}
///|
/// The first field named `name`, matched without regard to case.
pub fn find(fields : Array[Field], name : String) -> String? {
let key = name.to_lower()
for field in fields {
if field.key() == key {
return Some(field.value)
}
}
None
}
///|
pub fn Request::field(self : Request, name : String) -> String? {
find(self.fields, name)
}
///|
pub fn Response::field(self : Response, name : String) -> String? {
find(self.fields, name)
}