1use log::debug;
2use serde_json::Value;
3
4use crate::core::{
5 api_resp::{ApiResponseTrait, BaseResponse, RawResponse, ResponseFormat},
6 error::LarkAPIError,
7 SDKResult,
8};
9
10pub struct ImprovedResponseHandler;
13
14impl ImprovedResponseHandler {
15 pub async fn handle_response<T: ApiResponseTrait>(
21 response: reqwest::Response,
22 ) -> SDKResult<BaseResponse<T>> {
23 match T::data_format() {
24 ResponseFormat::Data => Self::handle_data_response(response).await,
25 ResponseFormat::Flatten => Self::handle_flatten_response(response).await,
26 ResponseFormat::Binary => Self::handle_binary_response(response).await,
27 }
28 }
29
30 async fn handle_data_response<T: ApiResponseTrait>(
33 response: reqwest::Response,
34 ) -> SDKResult<BaseResponse<T>> {
35 let response_text = response.text().await?;
36 debug!("Raw response: {response_text}");
37
38 match serde_json::from_str::<BaseResponse<T>>(&response_text) {
40 Ok(base_response) => Ok(base_response),
41 Err(_) => {
42 let raw_value: Value = serde_json::from_str(&response_text)?;
44
45 let code = raw_value["code"].as_i64().unwrap_or(-1) as i32;
46 let msg = raw_value["msg"]
47 .as_str()
48 .unwrap_or("Unknown error")
49 .to_string();
50
51 Ok(BaseResponse {
53 raw_response: RawResponse {
54 code,
55 msg,
56 err: raw_value
57 .get("error")
58 .and_then(|e| serde_json::from_value(e.clone()).ok()),
59 },
60 data: None,
61 })
62 }
63 }
64 }
65
66 async fn handle_flatten_response<T: ApiResponseTrait>(
69 response: reqwest::Response,
70 ) -> SDKResult<BaseResponse<T>> {
71 let response_text = response.text().await?;
72 debug!("Raw response: {response_text}");
73
74 let raw_value: Value = serde_json::from_str(&response_text)?;
75
76 let raw_response: RawResponse = serde_json::from_value(raw_value.clone())?;
78
79 let data = if raw_response.code == 0 {
81 match serde_json::from_value::<T>(raw_value) {
82 Ok(parsed_data) => Some(parsed_data),
83 Err(e) => {
84 debug!("Failed to parse data for flatten response: {e}");
85 None
86 }
87 }
88 } else {
89 None
90 };
91
92 Ok(BaseResponse { raw_response, data })
93 }
94
95 async fn handle_binary_response<T: ApiResponseTrait>(
97 response: reqwest::Response,
98 ) -> SDKResult<BaseResponse<T>> {
99 let file_name = response
101 .headers()
102 .get("Content-Disposition")
103 .and_then(|header| header.to_str().ok())
104 .and_then(Self::extract_filename)
105 .unwrap_or_default();
106
107 let bytes = response.bytes().await?.to_vec();
109
110 let data = T::from_binary(file_name, bytes);
112
113 Ok(BaseResponse {
114 raw_response: RawResponse {
115 code: 0,
116 msg: "success".to_string(),
117 err: None,
118 },
119 data,
120 })
121 }
122
123 fn extract_filename(content_disposition: &str) -> Option<String> {
125 for part in content_disposition.split(';') {
127 let part = part.trim();
128
129 if let Some(filename) = part.strip_prefix("filename*=UTF-8''") {
131 return Some(filename.to_string());
132 }
133
134 if let Some(filename) = part.strip_prefix("filename=") {
136 let filename = filename.trim_matches('"');
137 return Some(filename.to_string());
138 }
139 }
140 None
141 }
142}
143
144#[derive(Debug, serde::Serialize, serde::Deserialize)]
146pub struct OptimizedBaseResponse<T>
147where
148 T: Default,
149{
150 pub code: i32,
152 pub msg: String,
154 #[serde(rename = "error", default, skip_serializing_if = "Option::is_none")]
156 pub error: Option<ErrorInfo>,
157 #[serde(default, skip_serializing_if = "Option::is_none")]
159 pub data: Option<T>,
160}
161
162impl<T> OptimizedBaseResponse<T>
163where
164 T: Default,
165{
166 pub fn is_success(&self) -> bool {
168 self.code == 0
169 }
170
171 pub fn into_data(self) -> Result<T, LarkAPIError> {
173 if self.is_success() {
174 self.data.ok_or_else(|| {
175 LarkAPIError::illegal_param("Response is successful but data is missing")
176 })
177 } else {
178 Err(LarkAPIError::api_error(
179 self.code, self.msg, None, ))
181 }
182 }
183
184 pub fn data(&self) -> Option<&T> {
186 self.data.as_ref()
187 }
188
189 pub fn has_error(&self) -> bool {
191 self.error.is_some()
192 }
193}
194
195#[derive(Debug, serde::Serialize, serde::Deserialize)]
196pub struct ErrorInfo {
197 #[serde(rename = "key", default, skip_serializing_if = "Option::is_none")]
198 pub log_id: Option<String>,
199 #[serde(default, skip_serializing_if = "Vec::is_empty")]
200 pub details: Vec<ErrorDetail>,
201}
202
203#[derive(Debug, serde::Serialize, serde::Deserialize)]
204pub struct ErrorDetail {
205 #[serde(default, skip_serializing_if = "Option::is_none")]
206 pub key: Option<String>,
207 #[serde(default, skip_serializing_if = "Option::is_none")]
208 pub value: Option<String>,
209 #[serde(default, skip_serializing_if = "Option::is_none")]
210 pub description: Option<String>,
211}
212
213#[macro_export]
215macro_rules! impl_api_response {
216 ($type:ty, $format:expr) => {
217 impl ApiResponseTrait for $type {
218 fn data_format() -> ResponseFormat {
219 $format
220 }
221 }
222 };
223
224 ($type:ty, $format:expr, binary) => {
225 impl ApiResponseTrait for $type {
226 fn data_format() -> ResponseFormat {
227 $format
228 }
229
230 fn from_binary(file_name: String, body: Vec<u8>) -> Option<Self> {
231 Some(<$type>::from_binary_data(file_name, body))
232 }
233 }
234 };
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240 use crate::core::api_resp::ResponseFormat;
241 use serde::{Deserialize, Serialize};
242
243 #[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
244 struct TestData {
245 id: i32,
246 name: String,
247 }
248
249 impl ApiResponseTrait for TestData {
250 fn data_format() -> ResponseFormat {
251 ResponseFormat::Data
252 }
253 }
254
255 #[test]
256 fn test_optimized_base_response_success() {
257 let response = OptimizedBaseResponse {
258 code: 0,
259 msg: "success".to_string(),
260 error: None,
261 data: Some(TestData {
262 id: 1,
263 name: "test".to_string(),
264 }),
265 };
266
267 assert!(response.is_success());
268 assert!(response.data().is_some());
269 assert_eq!(response.data().unwrap().id, 1);
270 }
271
272 #[test]
273 fn test_optimized_base_response_error() {
274 let response: OptimizedBaseResponse<TestData> = OptimizedBaseResponse {
275 code: 400,
276 msg: "Bad Request".to_string(),
277 error: Some(ErrorInfo {
278 log_id: Some("log123".to_string()),
279 details: vec![],
280 }),
281 data: None,
282 };
283
284 assert!(!response.is_success());
285 assert!(response.has_error());
286 assert!(response.data().is_none());
287 }
288
289 #[test]
290 fn test_filename_extraction() {
291 let cases = vec![
292 (
293 "attachment; filename=\"test.txt\"",
294 Some("test.txt".to_string()),
295 ),
296 (
297 "attachment; filename*=UTF-8''test%20file.pdf",
298 Some("test%20file.pdf".to_string()),
299 ),
300 (
301 "attachment; filename=simple.doc",
302 Some("simple.doc".to_string()),
303 ),
304 ("attachment", None),
305 ];
306
307 for (input, expected) in cases {
308 let result = ImprovedResponseHandler::extract_filename(input);
309 assert_eq!(result, expected, "Failed for input: {input}");
310 }
311 }
312
313 #[test]
314 fn test_json_parsing_performance() {
315 let json_data = r#"{"code": 0, "msg": "success", "data": {"id": 1, "name": "test"}}"#;
316
317 let start = std::time::Instant::now();
319 let _result: Result<OptimizedBaseResponse<TestData>, _> = serde_json::from_str(json_data);
320 let direct_parse_time = start.elapsed();
321
322 let start = std::time::Instant::now();
324 let _value: Value = serde_json::from_str(json_data).unwrap();
325 let _result: Result<OptimizedBaseResponse<TestData>, _> = serde_json::from_value(_value);
326 let double_parse_time = start.elapsed();
327
328 println!("Direct parse time: {direct_parse_time:?}");
329 println!("Double parse time: {double_parse_time:?}");
330
331 }
334}
335
336mod usage_examples {}