hopr_chain_rpc/
transport.rs

1use alloy::transports::{http::Http, utils::guess_local_url};
2use async_trait::async_trait;
3pub use reqwest::Client as ReqwestClient;
4use tracing::{debug, trace};
5use url::Url;
6
7use crate::errors::HttpRequestError;
8
9/// Abstraction for an HTTP client that performs HTTP GET with serializable request data.
10#[async_trait]
11pub trait HttpRequestor: std::fmt::Debug + Send + Sync {
12    /// Performs HTTP GET query to the given URL and gets the JSON response.
13    async fn http_get(&self, url: &str) -> std::result::Result<Box<[u8]>, HttpRequestError>;
14}
15
16/// Local wrapper for `Http`
17#[derive(Clone, Debug)]
18pub struct HttpWrapper<T> {
19    client: T,
20    url: Url,
21}
22
23impl<T> HttpWrapper<T> {
24    /// Create a new [`Http`] transport with a custom client.
25    pub const fn with_client(client: T, url: Url) -> Self {
26        Self { client, url }
27    }
28
29    /// Set the URL.
30    pub fn set_url(&mut self, url: Url) {
31        self.url = url;
32    }
33
34    /// Set the client.
35    pub fn set_client(&mut self, client: T) {
36        self.client = client;
37    }
38
39    /// Guess whether the URL is local, based on the hostname.
40    ///
41    /// The output of this function is best-efforts, and should be checked if
42    /// possible. It simply returns `true` if the connection has no hostname,
43    /// or the hostname is `localhost` or `127.0.0.1`.
44    pub fn guess_local(&self) -> bool {
45        guess_local_url(&self.url)
46    }
47
48    /// Get a reference to the client.
49    pub const fn client(&self) -> &T {
50        &self.client
51    }
52
53    /// Get a reference to the URL.
54    pub fn url(&self) -> &str {
55        self.url.as_ref()
56    }
57}
58
59impl<T: Clone> From<Http<T>> for HttpWrapper<T> {
60    fn from(value: Http<T>) -> Self {
61        Self {
62            client: value.client().clone(),
63            url: Url::parse(value.url()).unwrap(),
64        }
65    }
66}
67
68impl<T: Clone> From<HttpWrapper<T>> for Http<T> {
69    fn from(value: HttpWrapper<T>) -> Self {
70        Self::with_client(value.client().clone(), Url::parse(value.url()).unwrap())
71    }
72}
73
74#[async_trait]
75impl HttpRequestor for ReqwestClient {
76    #[inline]
77    async fn http_get(&self, url: &str) -> std::result::Result<Box<[u8]>, HttpRequestError> {
78        let res = self
79            .get(url)
80            .send()
81            .await
82            .map_err(|e| HttpRequestError::TransportError(e.to_string()))?;
83
84        let status = res.status();
85
86        debug!(%status, "received response from server");
87
88        let body = res
89            .bytes()
90            .await
91            .map_err(|e| HttpRequestError::UnknownError(e.to_string()))?;
92
93        debug!(bytes = body.len(), "retrieved response body. Use `trace` for full body");
94        trace!(body = %String::from_utf8_lossy(&body), "response body");
95
96        if !status.is_success() {
97            return Err(HttpRequestError::HttpError(
98                http::StatusCode::try_from(status.as_u16()).unwrap_or(http::StatusCode::INTERNAL_SERVER_ERROR),
99            ));
100        }
101
102        Ok(body.to_vec().into_boxed_slice())
103    }
104}