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
16pub struct RequestExecutor;
19
20impl RequestExecutor {
21 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 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 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 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 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 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 if let Some(params) = query_params {
176 api_req.query_params = params;
177 }
178
179 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 Transport::request(api_req, config, option).await
187 }
188
189 #[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 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 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], None,
267 Some(body),
268 option,
269 )
270 .await
271 }
272
273 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], 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 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}