open_lark/core/
http.rs

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                // 使用改进的响应处理器,单次解析而非双重解析
79                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; // 默认值
138        }
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/// 解析文件名
217#[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}