Skip to main content

openlark_core/api/
mod.rs

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