ddns_a/webhook/
client.rs

1//! Production HTTP client implementation using reqwest.
2
3use super::{HttpClient, HttpError, HttpRequest, HttpResponse};
4
5/// Production HTTP client using reqwest.
6///
7/// This is a thin wrapper around `reqwest::Client` that implements
8/// the [`HttpClient`] trait. It inherits reqwest's default configuration
9/// including connection pooling and reasonable timeouts.
10///
11/// # Example
12///
13/// ```no_run
14/// use ddns_a::webhook::{ReqwestClient, HttpClient, HttpRequest};
15/// use url::Url;
16///
17/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
18/// let client = ReqwestClient::new();
19/// let url = Url::parse("https://api.example.com/webhook")?;
20/// let request = HttpRequest::post(url).with_body(b"hello".to_vec());
21/// let response = client.request(request).await?;
22/// println!("Status: {}", response.status);
23/// # Ok(())
24/// # }
25/// ```
26#[derive(Debug, Clone)]
27pub struct ReqwestClient {
28    inner: reqwest::Client,
29}
30
31impl ReqwestClient {
32    /// Creates a new HTTP client with default configuration.
33    #[must_use]
34    pub fn new() -> Self {
35        Self {
36            inner: reqwest::Client::new(),
37        }
38    }
39
40    /// Creates an HTTP client from an existing reqwest client.
41    ///
42    /// Useful when you need custom configuration (timeouts, TLS, etc.).
43    #[must_use]
44    pub const fn from_client(client: reqwest::Client) -> Self {
45        Self { inner: client }
46    }
47}
48
49impl Default for ReqwestClient {
50    fn default() -> Self {
51        Self::new()
52    }
53}
54
55impl HttpClient for ReqwestClient {
56    async fn request(&self, req: HttpRequest) -> Result<HttpResponse, HttpError> {
57        // Build the reqwest request
58        let mut builder = self.inner.request(req.method, req.url.as_str());
59
60        // Add headers
61        for (name, value) in &req.headers {
62            builder = builder.header(name, value);
63        }
64
65        // Add body if present
66        if let Some(body) = req.body {
67            builder = builder.body(body);
68        }
69
70        // Send the request
71        let response = builder.send().await.map_err(|e| {
72            if e.is_timeout() {
73                HttpError::Timeout
74            } else if e.is_builder() {
75                HttpError::InvalidUrl(e.to_string())
76            } else {
77                HttpError::Connection(Box::new(e))
78            }
79        })?;
80
81        // Extract response parts
82        let status = response.status();
83        let headers = response.headers().clone();
84        let body = response
85            .bytes()
86            .await
87            .map_err(|e| HttpError::Connection(Box::new(e)))?
88            .to_vec();
89
90        Ok(HttpResponse::new(status, headers, body))
91    }
92}