coman/core/
http_client.rs

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