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}