///|
/// A `ChromaClient` transport backed by `moonbitlang/async/http`.
///
/// This is the piece that turns the client's request objects into real
/// sockets. It implements `AsyncTransport`, so it plugs into the `*_async`
/// methods, and it is deliberately thin: it maps the client's
/// method/header vocabulary onto `@ah`, sends one request, and reads the
/// whole body back.
///
/// It lives in the library rather than in a test or example package because
/// a Chroma client that cannot reach a server is not useful: `preferred_target
/// = "wasm"` describes where the *client* runs, and this transport is how it
/// talks to Chroma from native and wasm alike.
///
/// One connection per request is enough for a demo and for a test suite. A
/// long-lived application that cares about latency should keep an `@ah.Client`
/// open and reuse it, because `@ah.Client` keeps the connection alive across
/// requests; see the "Native transport" section of the README.
///
/// The transport holds no state: the client already puts the full URL in
/// every request, so there is nothing to join here.
pub struct AsyncHttpTransport {}
///|
/// Builds a transport.
pub fn AsyncHttpTransport::new() -> AsyncHttpTransport {
AsyncHttpTransport::{ }
}
///|
/// Maps the client's verb onto the HTTP stack's.
fn to_ah_method(verb : Method) -> @ah.RequestMethod {
match verb {
Get => @ah.RequestMethod::Get
Post => @ah.RequestMethod::Post
Put => @ah.RequestMethod::Put
Delete => @ah.RequestMethod::Delete
Patch => @ah.RequestMethod::Patch
}
}
///|
pub impl AsyncTransport for AsyncHttpTransport with fn send_async(self, request) {
ignore(self)
// `@ah.Headers` is a `Map[CaseInsensitiveString, String]`, and
// `CaseInsensitiveString` is implicitly convertible from `String`, so the
// header names the client produced can be used as map keys unchanged.
let headers : @ah.Headers = Map([])
for entry in request.headers {
let (key, value) = entry
headers[key] = value
}
// A GET carries no request body in the client, and `@aio.Data` has no
// "absent" case, so an empty byte string is the neutral value here.
let payload : &@aio.Data = match request.body {
Some(body) => body
None => ""
}
let (response, response_body) = @ah.request(
request.url,
to_ah_method(request.verb),
headers,
payload,
)
let out_headers : Map[String, String] = Map([])
for entry in response.headers {
let (key, value) = entry
// `Map[String, String]` is what the client's `Response` expects; the
// original casing is not preserved by the case-insensitive key, and
// nothing in the client reads a header by name, so lowercasing loses
// nothing.
out_headers[key.to_string().to_lower()] = value
}
{ status: response.code, headers: out_headers, body: response_body.text(), }
}
///|
pub extend AsyncHttpTransport with AsyncTransport::{send_async}