bes_canopy_api/transport.rs
1//! The HTTP layer underneath [`CanopyClient`](crate::CanopyClient).
2//!
3//! Everything above this layer (the wire types, the per-endpoint methods,
4//! gzipping, status handling, JSON parsing) is transport-agnostic: this crate
5//! does not decide how a request reaches canopy, and depends on no HTTP client.
6//! A consumer implements [`CanopyTransport`] and keeps the whole typed interface
7//! on top of it.
8
9use std::sync::Arc;
10
11use bytes::Bytes;
12
13use crate::Result;
14
15/// A request built by [`CanopyClient`](crate::CanopyClient), ready for a
16/// [`CanopyTransport`] to send.
17///
18/// The URI is the endpoint **path** in origin form (path plus query, no scheme
19/// or authority), e.g. `/backup-target` — resolving it against a base URL is the
20/// transport's job. The body is already serialised and gzipped when there is one
21/// (with `content-type` and `content-encoding` set to match) and empty when
22/// there isn't.
23pub type CanopyRequest = http::Request<Bytes>;
24
25/// A response handed back to [`CanopyClient`](crate::CanopyClient) by a
26/// [`CanopyTransport`], with its body buffered.
27///
28/// The status is interpreted by the client: a non-2xx becomes a
29/// [`CanopyHttpError`](crate::CanopyHttpError) carrying the body, and a success
30/// has its body parsed into the endpoint's response type.
31pub type CanopyResponse = http::Response<Bytes>;
32
33/// The HTTP transport a [`CanopyClient`](crate::CanopyClient) sends through.
34///
35/// Implement this to route canopy calls through whatever reaches canopy from
36/// where you are — an mTLS client, a tailnet address, a proxy that isn't a plain
37/// HTTP proxy, an in-process handler, a recorded fixture in tests — and pass it
38/// to [`CanopyClient::new`](crate::CanopyClient::new). The per-endpoint methods,
39/// the wire types, and the error handling all work unchanged on top.
40///
41/// # Contract
42///
43/// - Requests arrive with a path-only URI (see [`CanopyRequest`]); the transport
44/// decides what host, scheme, and authentication to use, and may rewrite the
45/// path.
46/// - Return canopy's response as-is, non-2xx included: statuses are the client's
47/// to interpret, since endpoints give meaning to specific codes.
48/// - [`Err`] is for a failure to obtain any response at all (connect, timeout,
49/// protocol error), which is distinct from a response reporting failure.
50#[async_trait::async_trait]
51pub trait CanopyTransport: Send + Sync {
52 /// Send `request` and return canopy's response.
53 async fn call(&self, request: CanopyRequest) -> Result<CanopyResponse>;
54}
55
56#[async_trait::async_trait]
57impl<T: CanopyTransport + ?Sized> CanopyTransport for Arc<T> {
58 async fn call(&self, request: CanopyRequest) -> Result<CanopyResponse> {
59 (**self).call(request).await
60 }
61}
62
63#[async_trait::async_trait]
64impl<T: CanopyTransport + ?Sized> CanopyTransport for Box<T> {
65 async fn call(&self, request: CanopyRequest) -> Result<CanopyResponse> {
66 (**self).call(request).await
67 }
68}