Skip to main content

openlark_core/api/
mod.rs

1//! API处理模块 - 独立版本
2//!
3//! 现代化、类型安全的API请求和响应处理系统。
4//! 完全独立,不依赖已弃用的api_req/api_resp模块。
5
6// ============================================================================
7// 核心类型定义
8// ============================================================================
9
10pub use responses::RawResponse;
11use std::{collections::HashMap, time::Duration};
12
13/// HTTP方法枚举
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum HttpMethod {
16    Get,
17    Post,
18    Put,
19    Delete,
20    Patch,
21    Head,
22    Options,
23}
24
25impl HttpMethod {
26    pub fn as_str(self) -> &'static str {
27        match self {
28            HttpMethod::Get => "GET",
29            HttpMethod::Post => "POST",
30            HttpMethod::Put => "PUT",
31            HttpMethod::Delete => "DELETE",
32            HttpMethod::Patch => "PATCH",
33            HttpMethod::Head => "HEAD",
34            HttpMethod::Options => "OPTIONS",
35        }
36    }
37}
38
39impl std::fmt::Display for HttpMethod {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        write!(f, "{}", self.as_str())
42    }
43}
44
45/// 请求数据枚举
46#[derive(Debug, Clone)]
47pub enum RequestData {
48    Json(serde_json::Value),
49    Text(String),
50    Binary(Vec<u8>),
51    Form(std::collections::HashMap<String, String>),
52}
53
54// 处理字符串类型 - 优先使用Text处理
55impl From<String> for RequestData {
56    fn from(value: String) -> Self {
57        RequestData::Text(value)
58    }
59}
60
61impl From<&str> for RequestData {
62    fn from(value: &str) -> Self {
63        RequestData::Text(value.to_string())
64    }
65}
66
67// 为JSON值类型提供直接转换
68impl From<serde_json::Value> for RequestData {
69    fn from(value: serde_json::Value) -> Self {
70        RequestData::Json(value)
71    }
72}
73
74// 为Vec<u8>提供二进制数据支持
75impl From<Vec<u8>> for RequestData {
76    fn from(value: Vec<u8>) -> Self {
77        RequestData::Binary(value)
78    }
79}
80
81// 为HashMap<String, String>提供表单数据支持
82impl From<std::collections::HashMap<String, String>> for RequestData {
83    fn from(value: std::collections::HashMap<String, String>) -> Self {
84        RequestData::Form(value)
85    }
86}
87
88// 重新导出响应类型
89pub use responses::{ApiResponseTrait, BaseResponse, ErrorInfo, Response, ResponseFormat};
90
91/// 简化的API请求结构
92#[derive(Debug, Clone)]
93pub struct ApiRequest<R> {
94    pub(crate) method: HttpMethod,
95    pub(crate) url: String,
96    pub(crate) headers: HashMap<String, String>,
97    pub(crate) query: HashMap<String, String>,
98    pub(crate) body: Option<RequestData>,
99    pub(crate) file: Option<Vec<u8>>,
100    pub(crate) timeout: Option<Duration>,
101    pub(crate) _phantom: std::marker::PhantomData<R>,
102}
103
104impl<R> ApiRequest<R> {
105    pub fn get(url: impl Into<String>) -> Self {
106        Self {
107            method: HttpMethod::Get,
108            url: url.into(),
109            headers: HashMap::new(),
110            query: HashMap::new(),
111            body: None,
112            file: None,
113            timeout: None,
114            _phantom: std::marker::PhantomData,
115        }
116    }
117
118    pub fn post(url: impl Into<String>) -> Self {
119        Self {
120            method: HttpMethod::Post,
121            url: url.into(),
122            headers: HashMap::new(),
123            query: HashMap::new(),
124            body: None,
125            file: None,
126            timeout: None,
127            _phantom: std::marker::PhantomData,
128        }
129    }
130
131    pub fn put(url: impl Into<String>) -> Self {
132        Self {
133            method: HttpMethod::Put,
134            url: url.into(),
135            headers: HashMap::new(),
136            query: HashMap::new(),
137            body: None,
138            file: None,
139            timeout: None,
140            _phantom: std::marker::PhantomData,
141        }
142    }
143
144    pub fn patch(url: impl Into<String>) -> Self {
145        Self {
146            method: HttpMethod::Patch,
147            url: url.into(),
148            headers: HashMap::new(),
149            query: HashMap::new(),
150            body: None,
151            file: None,
152            timeout: None,
153            _phantom: std::marker::PhantomData,
154        }
155    }
156
157    pub fn delete(url: impl Into<String>) -> Self {
158        Self {
159            method: HttpMethod::Delete,
160            url: url.into(),
161            headers: HashMap::new(),
162            query: HashMap::new(),
163            body: None,
164            file: None,
165            timeout: None,
166            _phantom: std::marker::PhantomData,
167        }
168    }
169
170    pub fn header<K, V>(mut self, key: K, value: V) -> Self
171    where
172        K: Into<String>,
173        V: Into<String>,
174    {
175        self.headers.insert(key.into(), value.into());
176        self
177    }
178
179    pub fn query<K, V>(mut self, key: K, value: V) -> Self
180    where
181        K: Into<String>,
182        V: Into<String>,
183    {
184        self.query.insert(key.into(), value.into());
185        self
186    }
187
188    /// 添加可选查询参数,如果值为None则跳过
189    pub fn query_opt<K, V>(mut self, key: K, value: Option<V>) -> Self
190    where
191        K: Into<String>,
192        V: Into<String>,
193    {
194        if let Some(v) = value {
195            self.query.insert(key.into(), v.into());
196        }
197        self
198    }
199
200    pub fn body(mut self, body: impl Into<RequestData>) -> Self {
201        self.body = Some(body.into());
202        self
203    }
204
205    /// 设置文件内容 (用于 multipart 上传)
206    pub fn file_content(mut self, file: Vec<u8>) -> Self {
207        self.file = Some(file);
208        self
209    }
210
211    /// 为任何可序列化的类型设置请求体
212    pub fn json_body<T>(mut self, body: &T) -> Self
213    where
214        T: serde::Serialize,
215    {
216        match serde_json::to_value(body) {
217            Ok(json_value) => self.body = Some(RequestData::Json(json_value)),
218            Err(e) => {
219                tracing::warn!(error = %e, "json_body 序列化失败");
220                self.body = Some(RequestData::Json(serde_json::Value::Null));
221            }
222        }
223        self
224    }
225
226    pub fn timeout(mut self, duration: Duration) -> Self {
227        self.timeout = Some(duration);
228        self
229    }
230
231    pub fn build_url(&self) -> String {
232        if self.query.is_empty() {
233            self.url.clone()
234        } else {
235            let query_string = self
236                .query
237                .iter()
238                .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
239                .collect::<Vec<_>>()
240                .join("&");
241            format!("{}?{}", self.url, query_string)
242        }
243    }
244
245    // 兼容性字段和方法,用于与现有http.rs代码兼容
246    pub fn method(&self) -> &HttpMethod {
247        &self.method
248    }
249
250    pub fn api_path(&self) -> &str {
251        // 从URL中提取路径部分
252        if let Some(start) = self.url.find(crate::constants::API_PATH_PREFIX) {
253            &self.url[start..]
254        } else {
255            &self.url
256        }
257    }
258
259    pub fn supported_access_token_types(&self) -> Vec<crate::constants::AccessTokenType> {
260        // 默认返回用户和租户令牌类型
261        vec![
262            crate::constants::AccessTokenType::User,
263            crate::constants::AccessTokenType::Tenant,
264        ]
265    }
266
267    pub fn to_bytes(&self) -> Vec<u8> {
268        match &self.body {
269            Some(RequestData::Json(data)) => serde_json::to_vec(data).unwrap_or_default(),
270            Some(RequestData::Binary(data)) => data.clone(),
271            Some(RequestData::Form(data)) => data
272                .iter()
273                .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
274                .collect::<Vec<_>>()
275                .join("&")
276                .into_bytes(),
277            Some(RequestData::Text(text)) => text.clone().into_bytes(),
278            None => vec![],
279        }
280    }
281
282    /// 获取 headers 的可变引用,用于直接插入多个 header
283    pub fn headers_mut(&mut self) -> &mut HashMap<String, String> {
284        &mut self.headers
285    }
286
287    /// 获取 query 的可变引用,用于直接插入多个查询参数
288    pub fn query_mut(&mut self) -> &mut HashMap<String, String> {
289        &mut self.query
290    }
291
292    pub fn file(&self) -> Vec<u8> {
293        self.file.clone().unwrap_or_default()
294    }
295
296    /// 应用请求选项(兼容方法)
297    pub fn request_option(mut self, option: crate::req_option::RequestOption) -> Self {
298        // 将 RequestOption 的头部信息添加到请求中
299        for (key, value) in option.header {
300            self = self.header(key, value);
301        }
302        self
303    }
304
305    /// 设置查询参数(兼容方法)
306    pub fn query_param<K, V>(mut self, key: K, value: V) -> Self
307    where
308        K: Into<String>,
309        V: Into<String>,
310    {
311        self.query.insert(key.into(), value.into());
312        self
313    }
314
315    /// 设置多个查询参数(兼容方法)
316    pub fn query_params<I, K, V>(mut self, params: I) -> Self
317    where
318        I: IntoIterator<Item = (K, V)>,
319        K: Into<String>,
320        V: Into<String>,
321    {
322        for (key, value) in params {
323            self.query.insert(key.into(), value.into());
324        }
325        self
326    }
327}
328
329impl<R> Default for ApiRequest<R> {
330    fn default() -> Self {
331        Self {
332            method: HttpMethod::Get,
333            url: String::default(),
334            headers: HashMap::new(),
335            query: HashMap::new(),
336            body: None,
337            file: None,
338            timeout: None,
339            _phantom: std::marker::PhantomData,
340        }
341    }
342}
343
344// 类型别名,保持兼容性
345pub type ApiResponse<R> = Response<R>;
346
347// ============================================================================
348// 子模块
349// ============================================================================
350
351pub mod prelude;
352pub mod responses;
353pub mod traits;
354
355// ============================================================================
356// 重新导出
357// ============================================================================
358
359pub use traits::{AsyncApiClient, SyncApiClient};
360
361// ============================================================================
362// 测试
363// ============================================================================
364
365#[cfg(test)]
366mod tests {
367    use super::*;
368
369    #[test]
370    fn test_patch_method() {
371        // 测试patch方法是否正确创建ApiRequest
372        let request: ApiRequest<()> = ApiRequest::patch("https://example.com/api/resource");
373
374        // 验证HTTP方法
375        assert_eq!(request.method, HttpMethod::Patch);
376
377        // 验证URL
378        assert_eq!(request.url, "https://example.com/api/resource");
379
380        // 验证HTTP方法字符串
381        assert_eq!(request.method.as_str(), "PATCH");
382
383        println!("✅ Patch method test passed!");
384    }
385
386    #[test]
387    fn test_all_http_methods() {
388        // 测试所有HTTP方法
389        let get_req: ApiRequest<()> = ApiRequest::get("https://example.com/api");
390        let post_req: ApiRequest<()> = ApiRequest::post("https://example.com/api");
391        let put_req: ApiRequest<()> = ApiRequest::put("https://example.com/api");
392        let patch_req: ApiRequest<()> = ApiRequest::patch("https://example.com/api");
393        let delete_req: ApiRequest<()> = ApiRequest::delete("https://example.com/api");
394
395        // 验证HTTP方法
396        assert_eq!(get_req.method, HttpMethod::Get);
397        assert_eq!(post_req.method, HttpMethod::Post);
398        assert_eq!(put_req.method, HttpMethod::Put);
399        assert_eq!(patch_req.method, HttpMethod::Patch);
400        assert_eq!(delete_req.method, HttpMethod::Delete);
401
402        // 验证HTTP方法字符串
403        assert_eq!(get_req.method.as_str(), "GET");
404        assert_eq!(post_req.method.as_str(), "POST");
405        assert_eq!(put_req.method.as_str(), "PUT");
406        assert_eq!(patch_req.method.as_str(), "PATCH");
407        assert_eq!(delete_req.method.as_str(), "DELETE");
408
409        println!("✅ All HTTP methods test passed!");
410    }
411}