1use hitt_parser::HittRequest;
2
3pub struct HittResponse {
4 pub url: String,
5 pub method: String,
6 pub status_code: reqwest::StatusCode,
7 pub headers: reqwest::header::HeaderMap,
8 pub body: String,
9 pub http_version: http::version::Version,
10 pub duration: core::time::Duration,
11}
12
13#[inline]
14pub async fn send_request(
15 http_client: &reqwest::Client,
16 input: &HittRequest,
17 timeout: Option<&core::time::Duration>,
18) -> Result<HittResponse, reqwest::Error> {
19 let url = input.uri.to_string();
20
21 let mut partial_req = http_client.request(input.method.clone(), &url);
22
23 if let Some(http_version) = input.http_version {
24 partial_req = partial_req.version(http_version);
25 }
26
27 if !input.headers.is_empty() {
28 partial_req = partial_req.headers(input.headers.clone());
29 }
30
31 if input.body.is_some()
32 && let Some(body) = input.body.clone()
33 {
34 partial_req = partial_req.body(body);
35 }
36
37 if timeout.is_some()
38 && let Some(dur) = timeout
39 {
40 partial_req = partial_req.timeout(dur.to_owned());
41 }
42
43 let request = partial_req.build()?;
44
45 let start = std::time::Instant::now();
47 let response = http_client.execute(request).await?;
48 let duration = start.elapsed();
49
50 Ok(HittResponse {
51 url,
52 method: input.method.to_string(),
53 status_code: response.status(),
54 headers: response.headers().to_owned(),
55 http_version: response.version(),
56 duration,
57 body: response.text().await?,
58 })
59}
60
61#[cfg(test)]
62mod test_send_request {
63 use core::{str::FromStr, time::Duration};
64
65 use http::{HeaderMap, StatusCode};
66
67 use crate::send_request;
68
69 #[tokio::test]
70 async fn it_should_return_a_response() {
71 let http_client = reqwest::Client::new();
72
73 let timeout = None;
74
75 let uri = http::Uri::from_static("https://dummyjson.com/products/1");
76
77 let method = http::Method::GET;
78
79 let input = hitt_parser::HittRequest {
80 method: method.clone(),
81 uri: uri.clone(),
82 headers: HeaderMap::default(),
83 body: None,
84 http_version: None,
85 };
86
87 let result = send_request(&http_client, &input, timeout.as_ref())
88 .await
89 .expect("it to be successful");
90
91 assert_eq!(result.url, uri.to_string());
92
93 assert_eq!(result.status_code, StatusCode::OK);
94
95 assert_eq!(
96 http::Method::from_str(&result.method).expect("it to be a valid method"),
97 method
98 );
99
100 assert!(!result.body.is_empty());
101 }
102
103 #[tokio::test]
104 async fn it_should_set_http_version() {
105 let http_client = reqwest::Client::new();
106
107 let timeout = None;
108
109 let uri = http::Uri::from_static("https://dummyjson.com/products/1");
110
111 let method = http::Method::GET;
112
113 let input = hitt_parser::HittRequest {
114 method: method.clone(),
115 uri: uri.clone(),
116 headers: HeaderMap::default(),
117 body: Some("hello world".to_owned()),
118 http_version: Some(http::Version::HTTP_11),
119 };
120
121 let result = send_request(&http_client, &input, timeout.as_ref())
122 .await
123 .expect("it to be successful");
124
125 assert_eq!(result.url, uri.to_string());
126
127 assert_eq!(result.status_code, StatusCode::OK);
128
129 assert_eq!(
130 http::Method::from_str(&result.method).expect("it to be a valid method"),
131 method
132 );
133
134 assert!(!result.body.is_empty());
135 }
136
137 #[tokio::test]
138 async fn it_should_set_headers() {
139 let http_client = reqwest::Client::new();
140
141 let timeout = None;
142
143 let uri = http::Uri::from_static("https://dummyjson.com/products/1");
144
145 let mut headers = HeaderMap::new();
146
147 let header_key = http::HeaderName::from_static("mads");
148
149 let header_value = http::HeaderValue::from_static("hougesen");
150
151 headers.insert(header_key, header_value);
152
153 let method = http::Method::DELETE;
154
155 let input = hitt_parser::HittRequest {
156 method: method.clone(),
157 uri: uri.clone(),
158 headers,
159 body: Some("hello world".to_owned()),
160 http_version: None,
161 };
162
163 let result = send_request(&http_client, &input, timeout.as_ref())
164 .await
165 .expect("it to be successful");
166
167 assert_eq!(result.url, uri.to_string());
168
169 assert_eq!(result.status_code, StatusCode::OK);
170
171 assert_eq!(
172 http::Method::from_str(&result.method).expect("it to be a valid method"),
173 method
174 );
175
176 assert!(!result.body.is_empty());
177 }
178
179 #[tokio::test]
180 async fn it_should_set_body() {
181 let http_client = reqwest::Client::new();
182
183 let timeout = None;
184
185 let uri = http::Uri::from_static("https://dummyjson.com/products/1");
186
187 let input = hitt_parser::HittRequest {
188 method: http::Method::GET,
189 uri: uri.clone(),
190 headers: HeaderMap::default(),
191 body: Some("hello world".to_owned()),
192 http_version: None,
193 };
194
195 let result = send_request(&http_client, &input, timeout.as_ref())
196 .await
197 .expect("it to be successful");
198
199 assert_eq!(result.url, uri.to_string());
200
201 assert_eq!(result.status_code, StatusCode::OK);
202
203 assert!(!result.body.is_empty());
204 }
205
206 #[tokio::test]
207 async fn timeout_should_work() {
208 let http_client = reqwest::Client::new();
209
210 let timeout = Some(Duration::from_millis(5));
211
212 let uri = http::Uri::from_static("https://dummyjson.com/products/1");
213
214 let input = hitt_parser::HittRequest {
215 method: http::Method::GET,
216 uri: uri.clone(),
217 headers: HeaderMap::default(),
218 body: None,
219 http_version: None,
220 };
221
222 let response = send_request(&http_client, &input, timeout.as_ref())
223 .await
224 .err()
225 .expect("it to throw an error");
226
227 assert!(response.is_timeout());
228 }
229}