coman/core/
http_client.rs1use crate::core::errors::HttpError;
7use crate::core::http_request::HttpRequest;
8use crate::core::http_response::HttpResponse;
9use crate::CollectionManager;
10use std::time::Duration;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum HttpMethod {
15 Get,
16 Post,
17 Put,
18 Delete,
19 Patch,
20}
21
22impl std::fmt::Display for HttpMethod {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 match self {
25 HttpMethod::Get => write!(f, "GET"),
26 HttpMethod::Post => write!(f, "POST"),
27 HttpMethod::Put => write!(f, "PUT"),
28 HttpMethod::Delete => write!(f, "DELETE"),
29 HttpMethod::Patch => write!(f, "PATCH"),
30 }
31 }
32}
33
34impl From<crate::models::collection::Method> for HttpMethod {
35 fn from(method: crate::models::collection::Method) -> Self {
36 match method {
37 crate::models::collection::Method::Get => HttpMethod::Get,
38 crate::models::collection::Method::Post => HttpMethod::Post,
39 crate::models::collection::Method::Put => HttpMethod::Put,
40 crate::models::collection::Method::Delete => HttpMethod::Delete,
41 crate::models::collection::Method::Patch => HttpMethod::Patch,
42 }
43 }
44}
45
46pub type HttpResult<T> = Result<T, HttpError>;
48
49#[derive(Debug, Clone, Default)]
51pub struct HttpClient {
52 default_headers: Vec<(String, String)>,
53 timeout: Option<Duration>,
54 follow_redirects: bool,
55}
56
57impl HttpClient {
58 pub fn new() -> Self {
60 Self::default()
61 }
62
63 pub fn with_default_headers(mut self, headers: Vec<(String, String)>) -> Self {
65 self.default_headers = headers;
66 self
67 }
68
69 pub fn with_timeout(mut self, timeout: Duration) -> Self {
71 self.timeout = Some(timeout);
72 self
73 }
74
75 pub fn with_follow_redirects(mut self, follow: bool) -> Self {
77 self.follow_redirects = follow;
78 self
79 }
80
81 pub fn get(&self, url: &str) -> HttpRequest {
83 self.request(HttpMethod::Get, url)
84 }
85
86 pub fn post(&self, url: &str) -> HttpRequest {
88 self.request(HttpMethod::Post, url)
89 }
90
91 pub fn put(&self, url: &str) -> HttpRequest {
93 self.request(HttpMethod::Put, url)
94 }
95
96 pub fn delete(&self, url: &str) -> HttpRequest {
98 self.request(HttpMethod::Delete, url)
99 }
100
101 pub fn patch(&self, url: &str) -> HttpRequest {
103 self.request(HttpMethod::Patch, url)
104 }
105
106 pub fn request(&self, method: HttpMethod, url: &str) -> HttpRequest {
108 let mut request = HttpRequest::new(method, url)
109 .headers(self.default_headers.clone())
110 .follow_redirects(self.follow_redirects);
111
112 if let Some(timeout) = self.timeout {
113 request = request.timeout(timeout);
114 }
115
116 request
117 }
118
119 pub async fn execute_endpoint(
121 &self,
122 manager: CollectionManager,
123 col_name: &str,
124 ep_name: &str,
125 ) -> HttpResult<HttpResponse> {
126 let col = manager
127 .get_collection(col_name)
128 .await
129 .map_err(|e| HttpError::Other(e.to_string()))?
130 .ok_or_else(|| HttpError::Other(format!("Collection '{}' not found", col_name)))?;
131
132 let req = col.get_request(ep_name).ok_or_else(|| {
133 HttpError::Other(format!(
134 "Endpoint '{}' not found in collection '{}'",
135 ep_name, col_name
136 ))
137 })?;
138
139 let url = format!("{}{}", col.url, req.endpoint);
140 let headers = req.headers;
141
142 let method: HttpMethod = req.method.into();
143
144 let mut request = HttpRequest::new(method, &url)
145 .headers(headers)
146 .follow_redirects(self.follow_redirects);
147
148 if let Some(body) = &req.body {
149 request = request.body(body);
150 }
151
152 if let Some(timeout) = self.timeout {
153 request = request.timeout(timeout);
154 }
155
156 request.send().await
157 }
158}
159
160#[cfg(test)]
161mod tests {
162
163 use crate::core::utils::build_header_map;
164
165 use super::*;
166
167 #[test]
168 fn test_http_method_display() {
169 assert_eq!(HttpMethod::Get.to_string(), "GET");
170 assert_eq!(HttpMethod::Post.to_string(), "POST");
171 assert_eq!(HttpMethod::Put.to_string(), "PUT");
172 assert_eq!(HttpMethod::Delete.to_string(), "DELETE");
173 assert_eq!(HttpMethod::Patch.to_string(), "PATCH");
174 }
175
176 #[test]
177 fn test_http_response_status_checks() {
178 let response = HttpResponse {
179 version: "HTTP/1.1".to_string(),
180 status: 200,
181 status_text: "OK".to_string(),
182 headers: Vec::new(),
183 body: String::new(),
184 elapsed_ms: 0,
185 url: String::new(),
186 };
187
188 assert!(response.is_success());
189 assert!(!response.is_redirect());
190 assert!(!response.is_client_error());
191 assert!(!response.is_server_error());
192 }
193
194 #[test]
195 fn test_build_header_map() {
196 let headers = vec![
197 ("Content-Type".to_string(), "application/json".to_string()),
198 ("Authorization".to_string(), "Bearer token".to_string()),
199 ];
200
201 let header_map = build_header_map(&headers);
202 assert_eq!(header_map.len(), 2);
203 }
204}