mesa_dev/http_client.rs
1//! HTTP client abstraction for pluggable backends.
2//!
3//! The [`HttpClient`] trait is the extension point for bringing your own HTTP
4//! transport. The SDK ships with two optional implementations:
5//!
6//! - [`ReqwestClient`](crate::backends::ReqwestClient) — async, via [reqwest](https://docs.rs/reqwest) (default)
7//! - [`UreqClient`](crate::backends::UreqClient) — blocking, via [ureq](https://docs.rs/ureq)
8//!
9//! To use a different HTTP library, implement [`HttpClient`] and pass your
10//! implementation to [`ClientBuilder::build_with`](crate::ClientBuilder::build_with).
11
12use bytes::Bytes;
13use http::{HeaderMap, Method, StatusCode};
14use std::future::Future;
15
16use crate::error::HttpClientError;
17
18/// An HTTP request to be sent by an [`HttpClient`] implementation.
19#[derive(Debug, Clone)]
20pub struct HttpRequest {
21 /// The HTTP method.
22 pub method: Method,
23 /// The fully-qualified URL.
24 pub url: String,
25 /// Request headers.
26 pub headers: HeaderMap,
27 /// Optional request body.
28 pub body: Option<Bytes>,
29}
30
31/// An HTTP response returned by an [`HttpClient`] implementation.
32#[derive(Debug)]
33pub struct HttpResponse {
34 /// The HTTP status code.
35 pub status: StatusCode,
36 /// Response headers.
37 pub headers: HeaderMap,
38 /// Response body bytes.
39 pub body: Bytes,
40}
41
42/// Trait for pluggable HTTP client backends.
43///
44/// Implement this trait to use any HTTP library with the Mesa SDK. The SDK
45/// handles authorization headers, JSON serialization, and retry logic — your
46/// implementation only needs to execute the raw HTTP request and return the
47/// response.
48///
49/// # What the SDK handles
50///
51/// Before calling [`send`](HttpClient::send), the SDK:
52/// - Sets the `Authorization: Bearer <api-key>` header
53/// - Sets `Content-Type: application/json` for requests with a body
54/// - Sets `Accept: application/json`
55/// - Serializes request bodies to JSON bytes
56///
57/// After [`send`](HttpClient::send) returns, the SDK:
58/// - Deserializes the response body from JSON
59/// - Checks the status code for errors
60/// - Retries on transient failures (429, 5xx, timeouts, connection errors)
61///
62/// # What you implement
63///
64/// Your [`send`](HttpClient::send) method should:
65/// 1. Execute the HTTP request described by [`HttpRequest`]
66/// 2. Return the status code, headers, and raw body bytes in [`HttpResponse`]
67/// 3. Map transport errors to [`HttpClientError`] variants
68///
69/// # Error mapping for retries
70///
71/// The SDK uses [`HttpClientError`] variants to decide whether to retry:
72/// - [`Timeout`](HttpClientError::Timeout) → retried
73/// - [`Connection`](HttpClientError::Connection) → retried
74/// - [`Other`](HttpClientError::Other) → **not** retried
75///
76/// Map your HTTP library's errors accordingly to get correct retry behavior.
77///
78/// # Example
79///
80/// ```rust
81/// use mesa_dev::{ClientBuilder, HttpClient, HttpRequest, HttpResponse, error::HttpClientError};
82///
83/// struct MyClient;
84///
85/// impl HttpClient for MyClient {
86/// async fn send(
87/// &self,
88/// request: HttpRequest,
89/// ) -> Result<HttpResponse, HttpClientError> {
90/// // Execute request.method against request.url with request.headers
91/// // and optional request.body, then return HttpResponse.
92/// todo!()
93/// }
94/// }
95///
96/// let client = ClientBuilder::new("my-api-key").build_with(MyClient);
97/// ```
98pub trait HttpClient: Send + Sync {
99 /// Send an HTTP request and return the response.
100 fn send(
101 &self,
102 request: HttpRequest,
103 ) -> impl Future<Output = Result<HttpResponse, HttpClientError>> + Send;
104}