open_lark/core/
request_executor.rs

1use std::collections::HashMap;
2
3use reqwest::Method;
4use serde::Serialize;
5
6use crate::core::{
7    api_req::ApiRequest,
8    api_resp::{ApiResponseTrait, BaseResponse},
9    config::Config,
10    constants::AccessTokenType,
11    http::Transport,
12    req_option::RequestOption,
13    SDKResult,
14};
15
16/// 通用请求执行器,统一处理API调用逻辑
17/// 消除重复的请求-响应处理代码,提供统一的API调用入口
18pub struct RequestExecutor;
19
20impl RequestExecutor {
21    /// 执行GET请求
22    pub async fn get<T: ApiResponseTrait>(
23        config: &Config,
24        path: &str,
25        supported_tokens: Vec<AccessTokenType>,
26        query_params: Option<HashMap<String, String>>,
27        option: Option<RequestOption>,
28    ) -> SDKResult<BaseResponse<T>> {
29        Self::execute(
30            config,
31            Method::GET,
32            path,
33            supported_tokens,
34            query_params,
35            None::<()>,
36            option,
37        )
38        .await
39    }
40
41    /// 执行POST请求
42    pub async fn post<T: ApiResponseTrait, B: Serialize>(
43        config: &Config,
44        path: &str,
45        supported_tokens: Vec<AccessTokenType>,
46        body: Option<B>,
47        option: Option<RequestOption>,
48    ) -> SDKResult<BaseResponse<T>> {
49        Self::execute(
50            config,
51            Method::POST,
52            path,
53            supported_tokens,
54            None,
55            body,
56            option,
57        )
58        .await
59    }
60
61    /// 执行PUT请求
62    pub async fn put<T: ApiResponseTrait, B: Serialize>(
63        config: &Config,
64        path: &str,
65        supported_tokens: Vec<AccessTokenType>,
66        body: Option<B>,
67        option: Option<RequestOption>,
68    ) -> SDKResult<BaseResponse<T>> {
69        Self::execute(
70            config,
71            Method::PUT,
72            path,
73            supported_tokens,
74            None,
75            body,
76            option,
77        )
78        .await
79    }
80
81    /// 执行DELETE请求
82    pub async fn delete<T: ApiResponseTrait>(
83        config: &Config,
84        path: &str,
85        supported_tokens: Vec<AccessTokenType>,
86        option: Option<RequestOption>,
87    ) -> SDKResult<BaseResponse<T>> {
88        Self::execute(
89            config,
90            Method::DELETE,
91            path,
92            supported_tokens,
93            None,
94            None::<()>,
95            option,
96        )
97        .await
98    }
99
100    /// 执行PATCH请求
101    pub async fn patch<T: ApiResponseTrait, B: Serialize>(
102        config: &Config,
103        path: &str,
104        supported_tokens: Vec<AccessTokenType>,
105        body: Option<B>,
106        option: Option<RequestOption>,
107    ) -> SDKResult<BaseResponse<T>> {
108        Self::execute(
109            config,
110            Method::PATCH,
111            path,
112            supported_tokens,
113            None,
114            body,
115            option,
116        )
117        .await
118    }
119
120    /// 通用请求执行器核心方法
121    ///
122    /// # 参数
123    /// - `config`: 应用配置,包含认证信息
124    /// - `method`: HTTP方法
125    /// - `path`: API路径
126    /// - `supported_tokens`: 支持的访问令牌类型
127    /// - `query_params`: 查询参数(可选)
128    /// - `body`: 请求体(可选)
129    /// - `option`: 请求选项(可选)
130    ///
131    /// # 返回值
132    /// 返回标准的`BaseResponse<T>`格式响应
133    ///
134    /// # 示例
135    /// ```rust,ignore
136    /// // GET请求
137    /// let response: BaseResponse<MessageList> = RequestExecutor::execute(
138    ///     &config,
139    ///     Method::GET,
140    ///     "/open-apis/im/v1/messages",
141    ///     vec![AccessTokenType::Tenant, AccessTokenType::User],
142    ///     Some(query_params),
143    ///     None::<()>,
144    ///     None,
145    /// ).await?;
146    ///
147    /// // POST请求
148    /// let response: BaseResponse<Message> = RequestExecutor::execute(
149    ///     &config,
150    ///     Method::POST,
151    ///     "/open-apis/im/v1/messages",
152    ///     vec![AccessTokenType::Tenant, AccessTokenType::User],
153    ///     None,
154    ///     Some(create_request),
155    ///     None,
156    /// ).await?;
157    /// ```
158    pub async fn execute<T: ApiResponseTrait, B: Serialize>(
159        config: &Config,
160        method: Method,
161        path: &str,
162        supported_tokens: Vec<AccessTokenType>,
163        query_params: Option<HashMap<String, String>>,
164        body: Option<B>,
165        option: Option<RequestOption>,
166    ) -> SDKResult<BaseResponse<T>> {
167        let mut api_req = ApiRequest {
168            http_method: method,
169            api_path: path.to_string(),
170            supported_access_token_types: supported_tokens,
171            ..Default::default()
172        };
173
174        // 设置查询参数
175        if let Some(params) = query_params {
176            api_req.query_params = params;
177        }
178
179        // 序列化请求体
180        if let Some(body_data) = body {
181            api_req.body = serde_json::to_vec(&body_data)
182                .map_err(|e| crate::core::error::LarkAPIError::DeserializeError(e.to_string()))?;
183        }
184
185        // 执行请求
186        Transport::request(api_req, config, option).await
187    }
188
189    /// 带路径参数的请求执行器
190    /// 支持在路径中动态替换参数,如 `/open-apis/im/v1/messages/{message_id}`
191    ///
192    /// # 参数
193    /// - `path_template`: 包含占位符的路径模板,如 "/open-apis/im/v1/messages/{message_id}"
194    /// - `path_params`: 路径参数映射,如 HashMap::from([("message_id", "om_xxx")])
195    /// - 其他参数同 `execute` 方法
196    ///
197    /// # 示例
198    /// ```rust,ignore
199    /// let path_params = HashMap::from([("message_id", "om_xxx")]);
200    /// let response = RequestExecutor::execute_with_path_params(
201    ///     &config,
202    ///     Method::GET,
203    ///     "/open-apis/im/v1/messages/{message_id}",
204    ///     path_params,
205    ///     vec![AccessTokenType::Tenant],
206    ///     None,
207    ///     None::<()>,
208    ///     None,
209    /// ).await?;
210    /// ```
211    #[allow(clippy::too_many_arguments)]
212    pub async fn execute_with_path_params<T: ApiResponseTrait, B: Serialize>(
213        config: &Config,
214        method: Method,
215        path_template: &str,
216        path_params: HashMap<&str, &str>,
217        supported_tokens: Vec<AccessTokenType>,
218        query_params: Option<HashMap<String, String>>,
219        body: Option<B>,
220        option: Option<RequestOption>,
221    ) -> SDKResult<BaseResponse<T>> {
222        // 替换路径参数
223        let mut path = path_template.to_string();
224        for (key, value) in path_params {
225            path = path.replace(&format!("{{{key}}}"), value);
226        }
227
228        Self::execute(
229            config,
230            method,
231            &path,
232            supported_tokens,
233            query_params,
234            body,
235            option,
236        )
237        .await
238    }
239
240    /// 简化的JSON请求执行器
241    /// 自动序列化JSON请求体并设置标准的租户/用户令牌支持
242    ///
243    /// # 示例
244    /// ```rust,ignore
245    /// // 创建消息
246    /// let response = RequestExecutor::json_request::<CreateMessageResponse, _>(
247    ///     &config,
248    ///     Method::POST,
249    ///     "/open-apis/im/v1/messages",
250    ///     &create_message_request,
251    ///     None,
252    /// ).await?;
253    /// ```
254    pub async fn json_request<T: ApiResponseTrait, B: Serialize>(
255        config: &Config,
256        method: Method,
257        path: &str,
258        body: &B,
259        option: Option<RequestOption>,
260    ) -> SDKResult<BaseResponse<T>> {
261        Self::execute(
262            config,
263            method,
264            path,
265            vec![AccessTokenType::Tenant, AccessTokenType::User], // 默认支持租户和用户令牌
266            None,
267            Some(body),
268            option,
269        )
270        .await
271    }
272
273    /// 简化的查询请求执行器
274    /// 用于GET请求,自动设置标准的租户/用户令牌支持
275    ///
276    /// # 示例
277    /// ```rust,ignore
278    /// // 获取消息列表
279    /// let mut query_params = HashMap::new();
280    /// query_params.insert("container_id".to_string(), chat_id);
281    /// let response = RequestExecutor::query_request::<MessageListResponse>(
282    ///     &config,
283    ///     "/open-apis/im/v1/messages",
284    ///     Some(query_params),
285    ///     None,
286    /// ).await?;
287    /// ```
288    pub async fn query_request<T: ApiResponseTrait>(
289        config: &Config,
290        path: &str,
291        query_params: Option<HashMap<String, String>>,
292        option: Option<RequestOption>,
293    ) -> SDKResult<BaseResponse<T>> {
294        Self::get(
295            config,
296            path,
297            vec![AccessTokenType::Tenant, AccessTokenType::User], // 默认支持租户和用户令牌
298            query_params,
299            option,
300        )
301        .await
302    }
303}
304
305#[cfg(test)]
306mod tests {
307    use super::*;
308    use crate::core::api_resp::ResponseFormat;
309    use serde::{Deserialize, Serialize};
310
311    #[derive(Debug, Serialize, Deserialize)]
312    struct TestRequest {
313        message: String,
314    }
315
316    #[derive(Debug, Deserialize)]
317    #[allow(dead_code)]
318    struct TestResponse {
319        id: String,
320        status: String,
321    }
322
323    impl ApiResponseTrait for TestResponse {
324        fn data_format() -> ResponseFormat {
325            ResponseFormat::Data
326        }
327    }
328
329    #[tokio::test]
330    async fn test_request_executor_path_params() {
331        let path_template = "/open-apis/im/v1/messages/{message_id}/replies/{reply_id}";
332        let path_params = HashMap::from([("message_id", "om_123"), ("reply_id", "reply_456")]);
333
334        let expected_path = "/open-apis/im/v1/messages/om_123/replies/reply_456";
335
336        // 测试路径参数替换
337        let mut path = path_template.to_string();
338        for (key, value) in path_params {
339            path = path.replace(&format!("{{{key}}}"), value);
340        }
341
342        assert_eq!(path, expected_path);
343    }
344
345    #[test]
346    fn test_request_body_serialization() {
347        let request = TestRequest {
348            message: "Hello, World!".to_string(),
349        };
350
351        let serialized = serde_json::to_vec(&request).unwrap();
352        let expected = br#"{"message":"Hello, World!"}"#;
353
354        assert_eq!(serialized, expected);
355    }
356}