Skip to main content

mesa_dev/backends/
reqwest_client.rs

1//! Async HTTP backend powered by [reqwest](https://docs.rs/reqwest).
2//!
3//! This is the default backend, enabled by the `reqwest-client` Cargo feature.
4//! It requires an async runtime (typically Tokio).
5
6use std::time::Duration;
7
8use crate::error::HttpClientError;
9use crate::http_client::{HttpClient, HttpRequest, HttpResponse};
10
11/// Async HTTP backend powered by [reqwest](https://docs.rs/reqwest).
12///
13/// This is the default backend, enabled by the `reqwest-client` feature flag.
14/// It requires an async runtime (typically Tokio).
15///
16/// # Construction
17///
18/// Usually you don't need to construct this directly — [`Mesa::new`](crate::Mesa::new)
19/// and [`ClientBuilder::build`](crate::ClientBuilder::build) create it for you.
20///
21/// If you need a custom `reqwest::Client` (e.g., with a proxy or custom TLS):
22///
23/// ```rust,no_run
24/// use mesa_dev::{ClientBuilder, ReqwestClient};
25///
26/// let reqwest_client = reqwest::Client::builder()
27///     .danger_accept_invalid_certs(true) // for development only!
28///     .build()
29///     .unwrap();
30///
31/// let client = ClientBuilder::new("my-api-key")
32///     .build_with(ReqwestClient::from_client(reqwest_client));
33/// ```
34#[derive(Debug, Clone)]
35pub struct ReqwestClient {
36    client: reqwest::Client,
37}
38
39impl ReqwestClient {
40    /// Create a new `ReqwestClient` with the given timeout.
41    #[must_use]
42    pub fn new(timeout: Duration) -> Self {
43        let client = reqwest::Client::builder()
44            .timeout(timeout)
45            .build()
46            .unwrap_or_default();
47        Self { client }
48    }
49
50    /// Create a `ReqwestClient` from an existing [`reqwest::Client`].
51    #[must_use]
52    pub fn from_client(client: reqwest::Client) -> Self {
53        Self { client }
54    }
55}
56
57impl HttpClient for ReqwestClient {
58    async fn send(&self, request: HttpRequest) -> Result<HttpResponse, HttpClientError> {
59        let mut builder = self.client.request(request.method, &request.url);
60        builder = builder.headers(request.headers);
61
62        if let Some(body) = request.body {
63            builder = builder.body(body);
64        }
65
66        let response = builder.send().await.map_err(map_reqwest_error)?;
67
68        let status = response.status();
69        let headers = response.headers().clone();
70        let body = response.bytes().await.map_err(map_reqwest_error)?;
71
72        Ok(HttpResponse {
73            status,
74            headers,
75            body,
76        })
77    }
78}
79
80/// Map a reqwest error to our [`HttpClientError`].
81fn map_reqwest_error(err: reqwest::Error) -> HttpClientError {
82    if err.is_timeout() {
83        HttpClientError::Timeout
84    } else if err.is_connect() {
85        HttpClientError::Connection(err.to_string())
86    } else {
87        HttpClientError::Other(Box::new(err))
88    }
89}