1use std::{collections::HashSet, marker::PhantomData};
2
3use log::debug;
4use reqwest::RequestBuilder;
5
6use crate::core::{
7 api_req::ApiRequest,
8 api_resp::{ApiResponseTrait, BaseResponse},
9 app_ticket_manager::apply_app_ticket,
10 config::Config,
11 constants::*,
12 error::LarkAPIError,
13 improved_response_handler::ImprovedResponseHandler,
14 req_option::RequestOption,
15 req_translator::ReqTranslator,
16 SDKResult,
17};
18
19pub struct Transport<T> {
20 phantom_data: PhantomData<T>,
21}
22
23impl<T: ApiResponseTrait> Transport<T> {
24 pub async fn request(
25 mut req: ApiRequest,
26 config: &Config,
27 option: Option<RequestOption>,
28 ) -> Result<BaseResponse<T>, LarkAPIError> {
29 let option = option.unwrap_or_default();
30
31 if req.supported_access_token_types.is_empty() {
32 req.supported_access_token_types = vec![AccessTokenType::None];
33 }
34
35 validate_token_type(&req.supported_access_token_types, &option)?;
36 let access_token_type = determine_token_type(
37 &req.supported_access_token_types,
38 &option,
39 config.enable_token_cache,
40 );
41 validate(config, &option, access_token_type)?;
42
43 Self::do_request(req, access_token_type, config, option).await
44 }
45
46 async fn do_request(
47 mut http_req: ApiRequest,
48 access_token_type: AccessTokenType,
49 config: &Config,
50 option: RequestOption,
51 ) -> SDKResult<BaseResponse<T>> {
52 let req =
53 ReqTranslator::translate(&mut http_req, access_token_type, config, &option).await?;
54 debug!("Req:{req:?}");
55 let resp = Self::do_send(req, http_req.body, !http_req.file.is_empty()).await?;
56 debug!("Res:{resp:?}");
57
58 if !resp.success() && resp.raw_response.code == ERR_CODE_APP_TICKET_INVALID {
59 apply_app_ticket(config).await?;
60 }
61
62 Ok(resp)
63 }
64
65 pub async fn do_send(
66 raw_request: RequestBuilder,
67 body: Vec<u8>,
68 multi_part: bool,
69 ) -> SDKResult<BaseResponse<T>> {
70 let future = if multi_part {
71 raw_request.send()
72 } else {
73 raw_request.body(body).send()
74 };
75
76 match future.await {
77 Ok(response) => {
78 ImprovedResponseHandler::handle_response(response).await
80 }
81 Err(err) => {
82 debug!("Request error: {err:?}");
83 Err(LarkAPIError::RequestError(err.to_string()))
84 }
85 }
86 }
87}
88
89fn validate_token_type(
90 access_token_types: &[AccessTokenType],
91 option: &RequestOption,
92) -> Result<(), LarkAPIError> {
93 if !access_token_types.is_empty() {
94 return Ok(());
95 }
96
97 let access_token_type = access_token_types[0];
98
99 if access_token_type == AccessTokenType::Tenant && !option.user_access_token.is_empty() {
100 return Err(LarkAPIError::IllegalParamError(
101 "tenant token type not match user access token".to_string(),
102 ));
103 }
104
105 if access_token_type == AccessTokenType::App && !option.tenant_access_token.is_empty() {
106 return Err(LarkAPIError::IllegalParamError(
107 "user token type not match tenant access token".to_string(),
108 ));
109 }
110
111 Ok(())
112}
113
114fn determine_token_type(
115 access_token_types: &[AccessTokenType],
116 option: &RequestOption,
117 enable_token_cache: bool,
118) -> AccessTokenType {
119 if !enable_token_cache {
120 if !option.user_access_token.is_empty() {
121 return AccessTokenType::User;
122 }
123 if !option.tenant_access_token.is_empty() {
124 return AccessTokenType::Tenant;
125 }
126 if !option.app_access_token.is_empty() {
127 return AccessTokenType::App;
128 }
129
130 return AccessTokenType::None;
131 }
132 let mut accessible_token_type_set: HashSet<AccessTokenType> = HashSet::new();
133 let mut access_token_type = access_token_types[0];
134
135 for t in access_token_types {
136 if *t == AccessTokenType::Tenant {
137 access_token_type = *t; }
139 accessible_token_type_set.insert(*t);
140 }
141
142 if !option.tenant_key.is_empty() && accessible_token_type_set.contains(&AccessTokenType::Tenant)
143 {
144 access_token_type = AccessTokenType::Tenant;
145 }
146
147 if !option.user_access_token.is_empty()
148 && accessible_token_type_set.contains(&AccessTokenType::User)
149 {
150 access_token_type = AccessTokenType::User;
151 }
152
153 access_token_type
154}
155
156fn validate(
157 config: &Config,
158 option: &RequestOption,
159 access_token_type: AccessTokenType,
160) -> Result<(), LarkAPIError> {
161 if config.app_id.is_empty() {
162 return Err(LarkAPIError::IllegalParamError(
163 "AppId is empty".to_string(),
164 ));
165 }
166
167 if config.app_secret.is_empty() {
168 return Err(LarkAPIError::IllegalParamError(
169 "AppSecret is empty".to_string(),
170 ));
171 }
172
173 if !config.enable_token_cache {
174 if access_token_type == AccessTokenType::None {
175 return Ok(());
176 }
177 if option.user_access_token.is_empty()
178 && option.tenant_access_token.is_empty()
179 && option.app_access_token.is_empty()
180 {
181 return Err(LarkAPIError::IllegalParamError(
182 "accessToken is empty".to_string(),
183 ));
184 }
185 }
186
187 if config.app_type == AppType::Marketplace
188 && access_token_type == AccessTokenType::Tenant
189 && option.tenant_key.is_empty()
190 {
191 return Err(LarkAPIError::IllegalParamError(
192 "accessToken is empty".to_string(),
193 ));
194 }
195
196 if access_token_type == AccessTokenType::User && option.user_access_token.is_empty() {
197 return Err(LarkAPIError::IllegalParamError(
198 "user access token is empty".to_string(),
199 ));
200 }
201
202 if option.header.contains_key(HTTP_HEADER_KEY_REQUEST_ID) {
203 return Err(LarkAPIError::IllegalParamError(format!(
204 "use {HTTP_HEADER_KEY_REQUEST_ID} as header key is not allowed"
205 )));
206 }
207 if option.header.contains_key(HTTP_HEADER_REQUEST_ID) {
208 return Err(LarkAPIError::IllegalParamError(format!(
209 "use {HTTP_HEADER_REQUEST_ID} as header key is not allowed"
210 )));
211 }
212
213 Ok(())
214}
215
216#[allow(dead_code)]
218fn decode_file_name(file_name: &str) -> Option<String> {
219 let parts = file_name.split(';');
220
221 for part in parts {
222 if part.trim().starts_with("filename*=") {
223 let filename = part
224 .trim()
225 .strip_prefix("filename*=UTF-8''")
226 .unwrap_or("")
227 .to_string();
228 return Some(filename);
229 }
230 }
231
232 None
233}
234
235#[cfg(test)]
236mod test {
237 use crate::core::http::decode_file_name;
238
239 #[test]
240 fn test_decode_file_name() {
241 let raw = "attachment; filename=\"upload_all.rs\"; filename*=UTF-8''upload_all.rs";
242 let file_name = decode_file_name(raw).unwrap();
243 assert_eq!(file_name, "upload_all.rs");
244 }
245}