1use crate::{Result, SmartString};
4use reqwest::{Client, ClientBuilder, StatusCode, header::HeaderMap};
5use std::time::Duration;
6
7pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
9
10pub fn create_http_client() -> Result<Client> {
12 create_http_client_with_timeout(DEFAULT_TIMEOUT)
13}
14
15pub 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
29pub 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
37pub 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
47pub 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 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}