rusty_common/
http.rs

1//! HTTP client utilities
2
3use crate::{Result, SmartString};
4use reqwest::{Client, ClientBuilder, StatusCode, header::HeaderMap};
5use std::time::Duration;
6
7/// Default timeout for HTTP requests
8pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
9
10/// Create a default HTTP client with standard settings
11pub fn create_http_client() -> Result<Client> {
12    create_http_client_with_timeout(DEFAULT_TIMEOUT)
13}
14
15/// Create an HTTP client with custom timeout
16pub fn create_http_client_with_timeout(timeout: Duration) -> Result<Client> {
17    ClientBuilder::new()
18        .timeout(timeout)
19        .pool_idle_timeout(Duration::from_secs(90))
20        .pool_max_idle_per_host(32)
21        .build()
22        .map_err(|e| {
23            crate::CommonError::Network(SmartString::from(format!(
24                "Failed to build HTTP client: {e}"
25            )))
26        })
27}
28
29/// Create headers for JSON requests
30pub fn json_headers() -> HeaderMap {
31    let mut headers = HeaderMap::new();
32    headers.insert("Content-Type", "application/json".parse().unwrap());
33    headers.insert("Accept", "application/json".parse().unwrap());
34    headers
35}
36
37/// Create headers for form-encoded requests
38pub fn form_headers() -> HeaderMap {
39    let mut headers = HeaderMap::new();
40    headers.insert(
41        "Content-Type",
42        "application/x-www-form-urlencoded".parse().unwrap(),
43    );
44    headers
45}
46
47/// Parse API response and check for errors
48pub async fn parse_api_response<T: for<'de> serde::Deserialize<'de>>(
49    response_status_code: StatusCode,
50    response_body: String,
51) -> Result<T> {
52    if !response_status_code.is_success() {
53        return Err(crate::CommonError::Api(SmartString::from(format!(
54            "({}): {}",
55            response_status_code.as_u16(),
56            response_body
57        ))));
58    }
59
60    crate::json::parse(&response_body)
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use crate::CommonError;
67    use serde::{Deserialize, Serialize};
68    use std::time::Duration;
69
70    #[derive(Debug, PartialEq, Serialize, Deserialize)]
71    struct TestResponse {
72        success: bool,
73        data: String,
74    }
75
76    #[test]
77    fn test_json_headers() {
78        let headers = json_headers();
79        assert_eq!(headers.get("Content-Type").unwrap(), "application/json");
80        assert_eq!(headers.get("Accept").unwrap(), "application/json");
81        assert_eq!(headers.len(), 2);
82    }
83
84    #[test]
85    fn test_form_headers() {
86        let headers = form_headers();
87        assert_eq!(
88            headers.get("Content-Type").unwrap(),
89            "application/x-www-form-urlencoded"
90        );
91        assert_eq!(headers.len(), 1);
92    }
93
94    #[test]
95    fn test_client_with_large_timeout() {
96        let timeout = Duration::from_secs(300);
97        let client = create_http_client_with_timeout(timeout);
98        assert!(client.is_ok());
99    }
100
101    #[tokio::test]
102    async fn parse_api_response_success() {
103        let response_status_code = StatusCode::OK;
104        let json_body = r#"{"success": true, "data": "test"}"#;
105        let result = parse_api_response(response_status_code, json_body.to_string()).await;
106        assert!(result.is_ok());
107        let parsed_result: TestResponse = result.unwrap();
108        assert!(parsed_result.success);
109        assert_eq!(parsed_result.data, "test");
110    }
111
112    #[tokio::test]
113    async fn parse_api_response_error() {
114        let response_status_code = StatusCode::INTERNAL_SERVER_ERROR;
115        let json_body = r#"{"success": false, "data": "test"}"#;
116        let result: Result<TestResponse> =
117            parse_api_response(response_status_code, json_body.to_string()).await;
118        assert!(result.is_err());
119        let error: CommonError = result.unwrap_err();
120        // Note: The exact JSON formatting may vary (with or without spaces)
121        let error_str = error.to_string();
122        assert!(error_str.starts_with("API error: (500): {"));
123        assert!(error_str.contains("\"success\""));
124        assert!(error_str.contains("false"));
125        assert!(error_str.contains("\"data\""));
126        assert!(error_str.contains("\"test\""));
127    }
128}