Skip to main content

agentlink_sdk/http/
client.rs

1//! HTTP Client Implementation
2//!
3//! A pure HTTP client that handles AgentLink API communication.
4//! No dependencies on Tauri or SQLite.
5
6use std::sync::Arc;
7use std::time::Duration;
8
9use reqwest::{Client, Method};
10use serde::{de::DeserializeOwned, Serialize};
11use serde_json::Value;
12
13use crate::error::{SdkError, SdkResult};
14use crate::protocols::auth::{ApiError, ErrorDetail};
15use crate::token_manager::TokenManager;
16
17/// 生成 curl 命令字符串用于调试
18fn generate_curl_command(
19    method: &Method,
20    url: &str,
21    headers: &[(&str, &str)],
22    body: Option<&str>,
23) -> String {
24    let mut curl = format!("curl -X {} '{}'{}", method.as_str(), url, " \\\n  ");
25    
26    // 添加 headers
27    for (key, value) in headers {
28        curl.push_str(&format!("-H '{}: {}'{}", key, value, " \\\n  "));
29    }
30    
31    // 添加 body
32    if let Some(body_str) = body {
33        // 尝试格式化 JSON
34        if let Ok(json_value) = serde_json::from_str::<Value>(body_str) {
35            if let Ok(pretty_json) = serde_json::to_string(&json_value) {
36                curl.push_str(&format!("-d '{}'", pretty_json));
37            } else {
38                curl.push_str(&format!("-d '{}'", body_str));
39            }
40        } else {
41            curl.push_str(&format!("-d '{}'", body_str));
42        }
43    }
44    
45    curl
46}
47
48/// HTTP 客户端
49#[derive(Clone)]
50pub struct HttpClient {
51    client: Client,
52    base_url: String,
53    token_manager: Arc<TokenManager>,
54}
55
56impl HttpClient {
57    /// 创建新的 HTTP 客户端
58    pub fn new(base_url: &str) -> Self {
59        let client = Client::builder()
60            .timeout(Duration::from_secs(30))
61            .connect_timeout(Duration::from_secs(10))
62            .http1_only()
63            .user_agent("AgentLink-SDK/1.0")
64            .no_proxy()
65            .build()
66            .expect("Failed to create HTTP client");
67
68        Self {
69            client,
70            base_url: base_url.to_string(),
71            token_manager: Arc::new(TokenManager::new()),
72        }
73    }
74
75    /// 创建带有 TokenManager 的 HTTP 客户端
76    pub fn with_token_manager(base_url: &str, token_manager: Arc<TokenManager>) -> Self {
77        let client = Client::builder()
78            .timeout(Duration::from_secs(30))
79            .connect_timeout(Duration::from_secs(10))
80            .http1_only()
81            .user_agent("AgentLink-SDK/1.0")
82            .no_proxy()
83            .build()
84            .expect("Failed to create HTTP client");
85
86        Self {
87            client,
88            base_url: base_url.to_string(),
89            token_manager,
90        }
91    }
92
93    /// 获取 token manager
94    pub fn token_manager(&self) -> &TokenManager {
95        &self.token_manager
96    }
97
98    /// 发送 GET 请求
99    pub async fn get<T: DeserializeOwned>(&self, path: &str) -> SdkResult<T> {
100        self.request::<T, ()>(Method::GET, path, None).await
101    }
102
103    /// 发送 POST 请求
104    pub async fn post<T: DeserializeOwned, B: Serialize + Sync>(&self, path: &str, body: &B) -> SdkResult<T> {
105        self.request(Method::POST, path, Some(body)).await
106    }
107
108    /// 发送 PUT 请求
109    pub async fn put<T: DeserializeOwned, B: Serialize + Sync>(&self, path: &str, body: &B) -> SdkResult<T> {
110        self.request(Method::PUT, path, Some(body)).await
111    }
112
113    /// 发送 DELETE 请求
114    pub async fn delete<T: DeserializeOwned>(&self, path: &str) -> SdkResult<T> {
115        self.request::<T, ()>(Method::DELETE, path, None).await
116    }
117
118    /// 通用请求方法(自动从 TokenManager 获取 token)
119    async fn request<T: DeserializeOwned, B: Serialize + Sync>(
120        &self,
121        method: Method,
122        path: &str,
123        body: Option<&B>,
124    ) -> SdkResult<T> {
125        let url = format!("{}{}", self.base_url, path);
126
127        let mut request = self.client.request(method.clone(), &url);
128        request = request.header("Accept", "application/json");
129
130        // 从 TokenManager 获取 token 并添加到请求头
131        let mut headers_for_curl: Vec<(&str, String)> = vec![
132            ("Accept", "application/json".to_string()),
133        ];
134        
135        if let Some(token) = self.token_manager.get_token() {
136            let auth_header = format!("Bearer {}", token);
137            request = request.header("Authorization", &auth_header);
138            headers_for_curl.push(("Authorization", auth_header));
139        }
140
141        // 准备 body 字符串用于 curl 日志
142        let body_str = body.map(|b| serde_json::to_string(b).unwrap_or_default());
143        
144        if let Some(ref b) = body_str {
145            request = request.header("Content-Type", "application/json");
146            request = request.body(b.clone());
147            headers_for_curl.push(("Content-Type", "application/json".to_string()));
148        }
149
150        // 生成并输出 curl 命令
151        let headers_ref: Vec<(&str, &str)> = headers_for_curl
152            .iter()
153            .map(|(k, v)| (*k, v.as_str()))
154            .collect();
155        let curl_cmd = generate_curl_command(
156            &method,
157            &url,
158            &headers_ref,
159            body_str.as_deref(),
160        );
161        tracing::info!("[HTTP] curl command:\n{}", curl_cmd);
162
163        let response = request.send().await.map_err(SdkError::from)?;
164        let status = response.status();
165        let status_code = status.as_u16();
166        let response_text = response.text().await.map_err(SdkError::from)?;
167
168        self.handle_response(response_text, status_code).await
169    }
170
171    /// 处理响应
172    async fn handle_response<T: DeserializeOwned>(
173        &self,
174        response_text: String,
175        status_code: u16,
176    ) -> SdkResult<T> {
177        // 处理 HTTP 错误
178        if status_code >= 400 {
179            // 尝试解析为新格式错误响应
180            if let Ok(json_value) = serde_json::from_str::<Value>(&response_text) {
181                if let Some(success) = json_value.get("success").and_then(|v| v.as_bool()) {
182                    if !success {
183                        if let Some(error) = json_value.get("error") {
184                            if let Ok(error_detail) = serde_json::from_value::<ErrorDetail>(error.clone()) {
185                                return Err(SdkError::Http(format!(
186                                    "{}: {}",
187                                    error_detail.code, error_detail.message
188                                )));
189                            }
190                        }
191                    }
192                }
193
194                // 尝试解析为旧格式错误
195                if let Ok(api_error) = serde_json::from_str::<ApiError>(&response_text) {
196                    let error_msg = api_error.error.to_lowercase();
197                    let error_code = if error_msg.contains("invalid token") || error_msg.contains("token 无效") {
198                        "INVALID_TOKEN"
199                    } else if error_msg.contains("token expired") || error_msg.contains("token 已过期") {
200                        "TOKEN_EXPIRED"
201                    } else if error_msg.contains("user not found") || error_msg.contains("用户不存在") {
202                        "USER_NOT_FOUND"
203                    } else if status_code == 401 {
204                        "UNAUTHORIZED"
205                    } else if status_code == 403 {
206                        "FORBIDDEN"
207                    } else if status_code == 404 {
208                        "NOT_FOUND"
209                    } else if status_code >= 500 {
210                        "SERVER_ERROR"
211                    } else {
212                        "API_ERROR"
213                    };
214
215                    return Err(SdkError::Http(format!("{}: {}", error_code, api_error.error)));
216                }
217            }
218
219            return Err(SdkError::Http(format!(
220                "HTTP {}: {}",
221                status_code, response_text
222            )));
223        }
224
225        // 尝试解析为新格式响应
226        let json_value: Value = serde_json::from_str(&response_text).map_err(SdkError::from)?;
227
228        if let Some(success) = json_value.get("success").and_then(|v| v.as_bool()) {
229            if success {
230                if let Some(data) = json_value.get("data") {
231                    let parsed: T = serde_json::from_value(data.clone())
232                        .map_err(|e| SdkError::Serialization(e.to_string()))?;
233                    return Ok(parsed);
234                }
235            } else {
236                if let Some(error) = json_value.get("error") {
237                    if let Ok(error_detail) = serde_json::from_value::<ErrorDetail>(error.clone()) {
238                        return Err(SdkError::Http(format!(
239                            "{}: {}",
240                            error_detail.code, error_detail.message
241                        )));
242                    }
243                }
244            }
245        }
246
247        // 回退到旧格式(直接解析)
248        match serde_json::from_str::<T>(&response_text) {
249            Ok(data) => Ok(data),
250            Err(e) => Err(SdkError::Serialization(e.to_string())),
251        }
252    }
253}
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258
259    #[test]
260    fn test_http_client_new() {
261        let client = HttpClient::new("http://localhost:8080/api/v1");
262        assert_eq!(client.base_url, "http://localhost:8080/api/v1");
263    }
264
265    #[test]
266    fn test_http_client_clone() {
267        let client1 = HttpClient::new("http://localhost:8080/api/v1");
268        let client2 = client1.clone();
269        assert_eq!(client1.base_url, client2.base_url);
270    }
271}