open_lark/core/
api_req.rs

1use std::collections::HashMap;
2
3use reqwest::Method;
4
5use crate::core::constants::AccessTokenType;
6
7/// API请求的核心数据结构 - 命令模式的体现
8///
9/// `ApiRequest` 是整个SDK架构的核心,采用命令模式(Command Pattern)设计。
10/// 它封装了发起一次飞书API调用所需的所有信息,充当服务层(Service)与传输层(Transport)之间的桥梁。
11///
12/// # 设计理念
13///
14/// - **解耦性**:服务层只负责构建请求,不关心HTTP细节
15/// - **统一性**:所有API调用都通过这个统一的结构体表示
16/// - **灵活性**:支持普通请求和multipart/form-data请求
17///
18/// # 使用流程
19///
20/// 1. 服务层方法创建并配置 `ApiRequest` 实例
21/// 2. 设置HTTP方法、路径、认证需求等基本信息
22/// 3. 根据请求类型填充 `body` 和/或 `file` 字段
23/// 4. 将配置好的请求传递给 `Transport::request` 进行处理
24///
25/// # 示例
26///
27/// ```rust,ignore
28/// // 普通JSON请求
29/// let mut api_req = ApiRequest {
30///     http_method: Method::POST,
31///     api_path: "/open-apis/drive/v1/files".to_string(),
32///     body: serde_json::to_vec(&request_data).unwrap(),
33///     ..Default::default()
34/// };
35///
36/// // 文件上传请求(multipart)
37/// let mut api_req = ApiRequest {
38///     http_method: Method::POST,
39///     api_path: "/open-apis/drive/v1/files/upload".to_string(),
40///     body: serde_json::to_vec(&metadata).unwrap(),  // JSON元数据
41///     file: file_bytes,  // 文件内容
42///     ..Default::default()
43/// };
44/// ```
45#[derive(Debug, Clone, Default)]
46pub struct ApiRequest {
47    /// HTTP请求方法(GET、POST、PUT、DELETE等)
48    ///
49    /// 由服务层根据具体API的要求设置。
50    /// 使用 `pub(crate)` 限制只能在crate内部修改,保证一致性。
51    pub(crate) http_method: Method,
52
53    /// API的相对路径
54    ///
55    /// 例如:`/open-apis/drive/v1/files/{file_id}`
56    ///
57    /// 路径中的动态参数(如 `{file_id}`)通常通过 `format!` 宏直接嵌入,
58    /// 而不是使用 `path_params` 字段。
59    pub api_path: String,
60
61    /// 请求体数据(序列化后的字节数组)
62    ///
63    /// # 在不同请求类型中的用途:
64    ///
65    /// - **普通请求**:包含完整的请求体,通常是JSON序列化后的数据
66    /// - **文件上传(multipart)**:仅包含JSON元数据部分,文件内容存储在 `file` 字段
67    /// - **无请求体的请求**:保持为空 `Vec`
68    ///
69    /// # 注意事项
70    ///
71    /// 服务层通常使用 `serde_json::to_vec()` 将请求结构体序列化到这个字段。
72    pub body: Vec<u8>,
73
74    /// URL查询参数
75    ///
76    /// 存储将被附加到URL末尾的查询参数。
77    /// 例如:`?page_size=10&page_token=xxx`
78    ///
79    /// # 示例
80    ///
81    /// ```rust,ignore
82    /// api_req.query_params.insert("page_size".to_string(), "10".to_string());
83    /// api_req.query_params.insert("page_token".to_string(), token);
84    /// ```
85    pub query_params: HashMap<String, String>,
86
87    /// URL路径参数(当前未使用)
88    ///
89    /// 原设计意图可能是用于路径模板替换,如将 `/files/{id}` 中的 `{id}` 替换。
90    /// 但当前实现中,路径参数都是通过 `format!` 宏直接嵌入到 `api_path` 中。
91    ///
92    /// # TODO
93    ///
94    /// 考虑移除此字段或实现路径模板功能以保持设计一致性。
95    pub path_params: HashMap<String, Vec<String>>,
96
97    /// 支持的访问令牌类型
98    ///
99    /// 指定此API端点接受哪些类型的访问令牌:
100    /// - `User`:用户访问令牌
101    /// - `Tenant`:租户访问令牌  
102    /// - `App`:应用访问令牌
103    ///
104    /// Transport层会根据这个列表和当前配置选择合适的令牌类型。
105    /// 使用 `pub(crate)` 确保只能由服务层设置。
106    pub(crate) supported_access_token_types: Vec<AccessTokenType>,
107
108    /// 文件内容(用于multipart/form-data请求)
109    ///
110    /// # 在不同请求类型中的用途:
111    ///
112    /// - **普通请求**:保持为空 `Vec`
113    /// - **文件上传(multipart)**:包含要上传的文件的二进制内容
114    ///
115    /// # 工作原理
116    ///
117    /// 当 `file` 字段非空时,Transport层会自动识别这是一个multipart请求:
118    /// 1. `body` 字段的内容作为multipart的JSON元数据部分
119    /// 2. `file` 字段的内容作为文件部分
120    /// 3. Content-Type自动设置为 `multipart/form-data`
121    ///
122    /// # 示例
123    ///
124    /// ```rust,ignore
125    /// // 文件上传请求
126    /// api_req.body = serde_json::to_vec(&FileMetadata {
127    ///     name: "document.pdf",
128    ///     parent_id: "folder123",
129    /// }).unwrap();
130    /// api_req.file = std::fs::read("path/to/document.pdf").unwrap();
131    /// ```
132    pub file: Vec<u8>,
133}