aarambh_net/
http.rs

1// http.rs
2use reqwest::{header::HeaderMap, Client, Response, Url};
3use std::error::Error;
4#[cfg(feature = "logger")]
5use tracing::info;
6
7/// The `HttpClient` struct in Rust represents an HTTP client with a base URL, optional default headers,
8/// and a client instance.
9/// 
10/// # Properties:
11/// 
12/// * `base_url`: The `base_url` property in the `HttpClient` struct represents the base URL that will
13/// be used for making HTTP requests. This URL serves as the starting point for constructing full URLs
14/// for the requests sent by the HTTP client.
15/// * `default_headers`: The `default_headers` property in the `HttpClient` struct is an optional field
16/// that can hold a `HeaderMap`. This field can be used to store default headers that will be included
17/// in every request made by the `HttpClient`. If no default headers are provided, this field will be
18/// `None`.
19/// * `client`: The `client` property in the `HttpClient` struct is of type `Client`. This likely
20/// represents an HTTP client that can be used to make HTTP requests to a server. The `Client` type is
21/// commonly used in Rust libraries like `reqwest` for sending HTTP requests and handling responses.
22pub struct HttpClient {
23    base_url: Url,
24    default_headers: Option<HeaderMap>,
25    client: Client
26}
27
28/// The `impl HttpClient { ... }` block in the Rust code snippet is implementing methods for the
29/// `HttpClient` struct. Here's a breakdown of what each method is doing:
30impl HttpClient {
31    /// The function `new` creates a new instance of an `HttpClient` with a base URL, default headers,
32    /// and a new client.
33    /// 
34    /// # Arguments:
35    /// 
36    /// * `base_url`: The `base_url` parameter is a string reference (`&str`) that represents the base
37    /// URL for the HTTP client. This is the URL that will be used as the starting point for making HTTP
38    /// requests.
39    /// * `default_headers`: The `default_headers` parameter in the `new` function is an optional
40    /// parameter of type `Option<HeaderMap>`. It allows you to provide a set of default headers to be
41    /// included in each HTTP request made by the `HttpClient`. If no default headers are provided, you
42    /// can pass `None
43    /// 
44    /// # Returns:
45    /// 
46    /// The `new` function is returning a `Result` containing an instance of `HttpClient` if the URL
47    /// parsing is successful and the `HttpClient` struct is properly initialized with the provided base
48    /// URL, default headers, and a new `Client` instance.
49    pub fn new(base_url: &str, default_headers: Option<HeaderMap>) -> Result<Self, Box<dyn Error>> {
50        #[cfg(feature = "logger")]
51        info!("Initializing HttpClient with base URL: {}", base_url);
52        Ok(HttpClient {
53            base_url: Url::parse(base_url)?,
54            default_headers,
55            client: Client::new(),
56        })
57    }
58
59    /// The function `merge_headers` merges default headers with any extra headers provided and returns
60    /// the resulting `HeaderMap`.
61    /// 
62    /// # Arguments:
63    /// 
64    /// * `headers`: Option<HeaderMap>
65    /// 
66    /// # Returns:
67    /// 
68    /// The `merge_headers` function returns a `HeaderMap` which contains the merged headers from
69    /// `self.default_headers` and the `headers` provided as an argument.
70    fn merge_headers(&self, headers: Option<HeaderMap>) -> HeaderMap {
71        let mut merged_headers = self.default_headers.clone().unwrap_or_else(HeaderMap::new);
72        if let Some(extra_headers) = headers {
73            for (key, value) in extra_headers.iter() {
74                merged_headers.insert(key.clone(), value.clone());
75            }
76        }
77        #[cfg(feature = "logger")]
78        info!("Merged headers: {:?}", merged_headers);
79        merged_headers
80    }
81
82    /// This Rust function performs an asynchronous HTTP GET request with specified headers.
83    /// 
84    /// # Arguments:
85    /// 
86    /// * `endpoint`: The `endpoint` parameter in the `get` function represents the specific endpoint or
87    /// path that you want to access on the base URL. It is a string that typically corresponds to a
88    /// specific resource or action on the server.
89    /// * `headers`: The `headers` parameter in the `get` function is an optional `HeaderMap` type. It
90    /// allows you to pass additional headers that will be merged with the default headers before making
91    /// the HTTP request. If no additional headers are needed, you can pass `None` as the value for this
92    /// parameter
93    /// 
94    /// # Returns:
95    /// 
96    /// The `get` function returns a `Result` containing a `Response` if the request is successful, or a
97    /// `Box<dyn Error>` if an error occurs during the request.
98    pub async fn get(&self, endpoint: &str, headers: Option<HeaderMap>) -> Result<Response, Box<dyn Error>> {
99        let url = self.base_url.join(endpoint)?;
100        #[cfg(feature = "logger")]
101        info!("Sending GET request to {}", url);
102        let merged_headers = self.merge_headers(headers);
103        let response = self.client.get(url).headers(merged_headers).send().await?;
104        Ok(response)
105    }
106
107    /// The function `post` sends an asynchronous POST request with optional headers and body, returning
108    /// a Result containing the response.
109    /// 
110    /// # Arguments:
111    /// 
112    /// * `endpoint`: The `endpoint` parameter in the `post` function represents the specific endpoint
113    /// or route that you want to send a POST request to. It is a string that typically comes after the
114    /// base URL of the API you are interacting with.
115    /// * `headers`: The `headers` parameter in the `post` function is an optional `HeaderMap` type. It
116    /// allows you to pass additional headers to be included in the HTTP request. If you don't need to
117    /// include any extra headers, you can pass `None` as the value for this parameter. If
118    /// * `body`: The `body` parameter in the `post` function represents the payload or data that you
119    /// want to send in the HTTP request body. It is an optional parameter, meaning you can choose to
120    /// include a body or not when making a POST request. If you provide a body, it should be a string
121    /// 
122    /// # Returns:
123    /// 
124    /// The `post` function returns a `Result` containing a `Response` if the operation is successful,
125    /// or a `Box` containing a dynamic error trait object if an error occurs.
126    pub async fn post(&self, endpoint: &str, headers: Option<HeaderMap>, body: Option<&str>) -> Result<Response, Box<dyn Error>> {
127        let url = self.base_url.join(endpoint)?;
128        #[cfg(feature = "logger")]
129        info!("Sending POST request to {}", url);
130        let merged_headers = self.merge_headers(headers);
131        let mut request = self.client.post(url).headers(merged_headers);
132
133        // If a body is provided, add it to the request
134        if let Some(b) = body {
135            request = request.body(b.to_string());
136        }
137
138        let response = request.send().await?;
139        Ok(response)
140    }
141
142    /// The function `put` sends an HTTP PUT request with optional headers and body, and returns the
143    /// response asynchronously.
144    /// 
145    /// # Arguments:
146    /// 
147    /// * `endpoint`: The `endpoint` parameter is a string that represents the specific endpoint or
148    /// route that you want to send a PUT request to. It is typically a part of the URL path after the
149    /// base URL.
150    /// * `headers`: The `headers` parameter is an optional `HeaderMap` type, which represents a
151    /// collection of HTTP headers. It allows you to pass additional headers along with the request. If
152    /// no headers are needed, you can pass `None` as the value for this parameter.
153    /// * `body`: The `body` parameter in the `put` function is an optional reference to a string. It
154    /// represents the body content that will be sent in the HTTP PUT request. If a value is provided
155    /// for the `body` parameter, it will be included in the request; otherwise, the request will be
156    /// 
157    /// # Returns:
158    /// 
159    /// The `put` function returns a `Result` containing a `Response` if the operation is successful, or
160    /// a `Box` containing a trait object that implements the `Error` trait if an error occurs.
161    pub async fn put(&self, endpoint: &str, headers: Option<HeaderMap>, body: Option<&str>) -> Result<Response, Box<dyn Error>> {
162        let url = self.base_url.join(endpoint)?;
163        #[cfg(feature = "logger")]
164        info!("Sending PUT request to {}", url);
165        let merged_headers = self.merge_headers(headers);
166        let mut request = self.client.put(url).headers(merged_headers);
167
168        if let Some(b) = body {
169            request = request.body(b.to_string());
170        }
171
172        let response = request.send().await?;
173        Ok(response)
174    }
175
176    /// The function `delete` sends a DELETE request to a specified endpoint with optional headers and
177    /// returns the response asynchronously.
178    /// 
179    /// # Arguments:
180    /// 
181    /// * `endpoint`: The `endpoint` parameter in the `delete` function is a reference to a string that
182    /// represents the specific endpoint or resource path that you want to delete on the server. It is
183    /// used to construct the complete URL for the DELETE request.
184    /// * `headers`: The `headers` parameter in the `delete` function is an optional `HeaderMap` type.
185    /// It allows you to pass additional headers to be included in the HTTP request. If no headers are
186    /// needed, you can pass `None` as the value for this parameter. If you do need to include
187    /// 
188    /// # Returns:
189    /// 
190    /// The `delete` function returns a `Result` containing a `Response` if the operation is successful,
191    /// or a `Box<dyn Error>` if an error occurs.
192    pub async fn delete(&self, endpoint: &str, headers: Option<HeaderMap>) -> Result<Response, Box<dyn Error>> {
193        let url = self.base_url.join(endpoint)?;
194        #[cfg(feature = "logger")]
195        info!("Sending DELETE request to {}", url);
196        let merged_headers = self.merge_headers(headers);
197        let response = self.client.delete(url).headers(merged_headers).send().await?;
198        Ok(response)
199    }
200
201    /// This Rust function sends a HEAD request to a specified endpoint with optional headers and
202    /// returns the response asynchronously.
203    /// 
204    /// # Arguments:
205    /// 
206    /// * `endpoint`: The `endpoint` parameter in the `head` function represents the specific path or
207    /// resource on the server that you want to send a HTTP HEAD request to. It is typically a string
208    /// that specifies the endpoint URL relative to the base URL of the API.
209    /// * `headers`: The `headers` parameter in the `head` function is an optional `HeaderMap` type. It
210    /// allows you to pass additional headers to be included in the HTTP request. If you don't need to
211    /// include any extra headers, you can pass `None` as the value for this parameter. If
212    /// 
213    /// # Returns:
214    /// 
215    /// The `head` function returns a `Result` containing a `Response` if the operation is successful,
216    /// or a `Box<dyn Error>` if an error occurs.
217    pub async fn head(&self, endpoint: &str, headers: Option<HeaderMap>) -> Result<Response, Box<dyn Error>> {
218        let url = self.base_url.join(endpoint)?;
219        #[cfg(feature = "logger")]
220        info!("Sending HEAD request to {}", url);
221        let merged_headers = self.merge_headers(headers);
222        let response = self.client.head(url).headers(merged_headers).send().await?;
223        Ok(response)
224    }
225
226    /// The function `patch` sends a PATCH request to a specified endpoint with optional headers and body,
227    /// and returns the response asynchronously.
228    /// 
229    /// # Arguments:
230    /// 
231    /// * `endpoint`: The `endpoint` parameter in the `patch` function is a string that represents the
232    /// specific endpoint or route that you want to send a PATCH request to. It is typically a part of the
233    /// URL after the base URL.
234    /// * `headers`: The `headers` parameter in the `patch` function is an optional `HeaderMap` type. It
235    /// allows you to pass additional headers to be included in the HTTP request. If you don't need to
236    /// include any extra headers, you can pass `None` as the value for this parameter. If
237    /// * `body`: The `body` parameter in the `patch` function is an optional reference to a string
238    /// (`Option<&str>`). It represents the body content that will be sent in the HTTP request when making a
239    /// PATCH request to the specified `endpoint`. If a value is provided for the `body`, it will
240    /// 
241    /// # Returns:
242    /// 
243    /// The `patch` function returns a `Result` containing either a `Response` or a boxed trait object
244    /// implementing the `Error` trait.
245    pub async fn patch(&self, endpoint: &str, headers: Option<HeaderMap>, body: Option<&str>) -> Result<Response, Box<dyn Error>> {
246        let url = self.base_url.join(endpoint)?;
247        #[cfg(feature = "logger")]
248        info!("Sending PATCH request to {}", url);
249        let merged_headers = self.merge_headers(headers);
250        let mut request = self.client.patch(url).headers(merged_headers);
251
252        if let Some(b) = body {
253            request = request.body(b.to_string());
254        }
255
256        let response = request.send().await?;
257        Ok(response)
258    }
259
260}
261
262
263#[cfg(test)]
264mod test {
265    use super::*;
266
267    fn setup_client() -> HttpClient {
268        let base_url = "https://httpbin.org";
269        HttpClient::new(base_url, None).unwrap()
270    }
271
272    #[tokio::test]
273    async fn test_get_request() {
274        let client = setup_client();
275        let endpoint = "/get";
276
277        match client.get(endpoint, None).await {
278            Ok(response) => {
279                assert_eq!(response.status(), 200); // Assert that the status is 200 OK
280                let body = response.text().await.unwrap();
281                assert!(body.contains("\"url\": \"https://httpbin.org/get\"")); // Assert the response contains the expected URL
282                println!("GET Response: {}", body);
283            },
284            Err(e) => panic!("GET request failed: {}", e),
285        }
286    }
287
288}