1use log::debug;
2use serde_json::Value;
3use tracing::{info_span, Instrument};
4
5use crate::core::{
6 api_resp::{ApiResponseTrait, BaseResponse, RawResponse, ResponseFormat},
7 error::LarkAPIError,
8 observability::ResponseTracker,
9 SDKResult,
10};
11
12pub struct ImprovedResponseHandler;
15
16impl ImprovedResponseHandler {
17 pub async fn handle_response<T: ApiResponseTrait>(
24 response: reqwest::Response,
25 ) -> SDKResult<BaseResponse<T>> {
26 let format = match T::data_format() {
27 ResponseFormat::Data => "data",
28 ResponseFormat::Flatten => "flatten",
29 ResponseFormat::Binary => "binary",
30 };
31
32 let span = info_span!(
33 "response_handling",
34 format = format,
35 status_code = response.status().as_u16(),
36 content_length = tracing::field::Empty,
37 processing_duration_ms = tracing::field::Empty,
38 );
39
40 async move {
41 let start_time = std::time::Instant::now();
42
43 let content_length = response.content_length();
45 if let Some(length) = content_length {
46 tracing::Span::current().record("content_length", length);
47 }
48
49 let result = match T::data_format() {
50 ResponseFormat::Data => Self::handle_data_response(response).await,
51 ResponseFormat::Flatten => Self::handle_flatten_response(response).await,
52 ResponseFormat::Binary => Self::handle_binary_response(response).await,
53 };
54
55 let duration_ms = start_time.elapsed().as_millis() as u64;
57 tracing::Span::current().record("processing_duration_ms", duration_ms);
58
59 result
60 }
61 .instrument(span)
62 .await
63 }
64
65 async fn handle_data_response<T: ApiResponseTrait>(
68 response: reqwest::Response,
69 ) -> SDKResult<BaseResponse<T>> {
70 let tracker = ResponseTracker::start("json_data", response.content_length());
71
72 let response_text = response.text().await?;
73 debug!("Raw response: {response_text}");
74
75 tracker.parsing_complete();
77
78 match serde_json::from_str::<BaseResponse<T>>(&response_text) {
80 Ok(base_response) => {
81 tracker.success();
82 Ok(base_response)
83 }
84 Err(direct_parse_err) => {
85 tracing::debug!("Direct parsing failed, attempting structured data extraction");
86
87 match serde_json::from_str::<Value>(&response_text) {
89 Ok(raw_value) => {
90 let code = raw_value["code"].as_i64().unwrap_or(-1) as i32;
91 let msg = raw_value["msg"]
92 .as_str()
93 .unwrap_or("Unknown error")
94 .to_string();
95
96 let data = if code == 0 {
98 if let Some(data_value) = raw_value.get("data") {
99 match serde_json::from_value::<T>(data_value.clone()) {
101 Ok(parsed_data) => {
102 tracing::debug!("Successfully parsed data field as type T");
103 Some(parsed_data)
104 }
105 Err(data_parse_err) => {
106 tracing::debug!("Failed to parse data field as type T: {data_parse_err:?}");
107
108 if std::any::type_name::<T>().contains("CreateMessageResp")
110 {
111 let wrapped_value = serde_json::json!({
113 "data": data_value
114 });
115 match serde_json::from_value::<T>(wrapped_value) {
116 Ok(wrapped_data) => {
117 tracing::debug!("Successfully parsed data by wrapping Message in CreateMessageResp");
118 Some(wrapped_data)
119 }
120 Err(_) => {
121 tracing::warn!("Failed to parse even after wrapping, but response contains valid message data");
122 None
124 }
125 }
126 } else {
127 None
128 }
129 }
130 }
131 } else {
132 tracing::debug!("No data field found in successful response");
133 None
134 }
135 } else {
136 None
137 };
138
139 tracker.validation_complete();
140 tracker.success();
141
142 Ok(BaseResponse {
143 raw_response: RawResponse {
144 code,
145 msg,
146 err: None,
147 },
148 data,
149 })
150 }
151 Err(fallback_err) => {
152 let error_msg = format!(
153 "Failed to parse response. Direct parse error: {}. Fallback parse error: {}",
154 direct_parse_err, fallback_err
155 );
156 tracker.error(&error_msg);
157 Err(LarkAPIError::IllegalParamError(error_msg))
158 }
159 }
160 }
161 }
162 }
163
164 async fn handle_flatten_response<T: ApiResponseTrait>(
167 response: reqwest::Response,
168 ) -> SDKResult<BaseResponse<T>> {
169 let tracker = ResponseTracker::start("json_flatten", response.content_length());
170
171 let response_text = response.text().await?;
172 debug!("Raw response: {response_text}");
173
174 let raw_value: Value = match serde_json::from_str(&response_text) {
176 Ok(value) => {
177 tracker.parsing_complete();
178 value
179 }
180 Err(e) => {
181 let error_msg = format!("Failed to parse JSON: {}", e);
182 tracker.error(&error_msg);
183 return Err(LarkAPIError::IllegalParamError(error_msg));
184 }
185 };
186
187 let raw_response: RawResponse = match serde_json::from_value(raw_value.clone()) {
189 Ok(response) => response,
190 Err(e) => {
191 let error_msg = format!("Failed to parse raw response: {}", e);
192 tracker.error(&error_msg);
193 return Err(LarkAPIError::IllegalParamError(error_msg));
194 }
195 };
196
197 let data = if raw_response.code == 0 {
199 match serde_json::from_value::<T>(raw_value) {
200 Ok(parsed_data) => {
201 tracker.validation_complete();
202 Some(parsed_data)
203 }
204 Err(e) => {
205 debug!("Failed to parse data for flatten response: {e}");
206 tracker.validation_complete();
207 None
208 }
209 }
210 } else {
211 tracker.validation_complete();
212 None
213 };
214
215 tracker.success();
216 Ok(BaseResponse { raw_response, data })
217 }
218
219 async fn handle_binary_response<T: ApiResponseTrait>(
221 response: reqwest::Response,
222 ) -> SDKResult<BaseResponse<T>> {
223 let tracker = ResponseTracker::start("binary", response.content_length());
224
225 let file_name = response
227 .headers()
228 .get("Content-Disposition")
229 .and_then(|header| header.to_str().ok())
230 .and_then(Self::extract_filename)
231 .unwrap_or_default();
232
233 tracker.parsing_complete();
235
236 let bytes = match response.bytes().await {
238 Ok(bytes) => {
239 let byte_vec = bytes.to_vec();
240 tracing::debug!("Binary response received: {} bytes", byte_vec.len());
241 byte_vec
242 }
243 Err(e) => {
244 let error_msg = format!("Failed to read binary response: {}", e);
245 tracker.error(&error_msg);
246 return Err(LarkAPIError::RequestError(error_msg));
247 }
248 };
249
250 let data = match T::from_binary(file_name.clone(), bytes) {
252 Some(binary_data) => {
253 tracker.validation_complete();
254 Some(binary_data)
255 }
256 None => {
257 tracker.validation_complete();
258 tracing::warn!("Binary data could not be processed for file: {}", file_name);
259 None
260 }
261 };
262
263 tracker.success();
264 Ok(BaseResponse {
265 raw_response: RawResponse {
266 code: 0,
267 msg: "success".to_string(),
268 err: None,
269 },
270 data,
271 })
272 }
273
274 fn extract_filename(content_disposition: &str) -> Option<String> {
276 for part in content_disposition.split(';') {
278 let part = part.trim();
279
280 if let Some(filename) = part.strip_prefix("filename*=UTF-8''") {
282 return Some(filename.to_string());
283 }
284
285 if let Some(filename) = part.strip_prefix("filename=") {
287 let filename = filename.trim_matches('"');
288 return Some(filename.to_string());
289 }
290 }
291 None
292 }
293}
294
295#[derive(Debug, serde::Serialize, serde::Deserialize)]
297pub struct OptimizedBaseResponse<T>
298where
299 T: Default,
300{
301 pub code: i32,
303 pub msg: String,
305 #[serde(rename = "error", default, skip_serializing_if = "Option::is_none")]
307 pub error: Option<ErrorInfo>,
308 #[serde(default, skip_serializing_if = "Option::is_none")]
310 pub data: Option<T>,
311}
312
313impl<T> OptimizedBaseResponse<T>
314where
315 T: Default,
316{
317 pub fn is_success(&self) -> bool {
319 self.code == 0
320 }
321
322 pub fn into_data(self) -> Result<T, LarkAPIError> {
324 if self.is_success() {
325 self.data.ok_or_else(|| {
326 LarkAPIError::illegal_param("Response is successful but data is missing")
327 })
328 } else {
329 Err(LarkAPIError::api_error(
330 self.code, self.msg, None, ))
332 }
333 }
334
335 pub fn data(&self) -> Option<&T> {
337 self.data.as_ref()
338 }
339
340 pub fn has_error(&self) -> bool {
342 self.error.is_some()
343 }
344}
345
346#[derive(Debug, serde::Serialize, serde::Deserialize)]
347pub struct ErrorInfo {
348 #[serde(rename = "key", default, skip_serializing_if = "Option::is_none")]
349 pub log_id: Option<String>,
350 #[serde(default, skip_serializing_if = "Vec::is_empty")]
351 pub details: Vec<ErrorDetail>,
352}
353
354#[derive(Debug, serde::Serialize, serde::Deserialize)]
355pub struct ErrorDetail {
356 #[serde(default, skip_serializing_if = "Option::is_none")]
357 pub key: Option<String>,
358 #[serde(default, skip_serializing_if = "Option::is_none")]
359 pub value: Option<String>,
360 #[serde(default, skip_serializing_if = "Option::is_none")]
361 pub description: Option<String>,
362}
363
364#[macro_export]
366macro_rules! impl_api_response {
367 ($type:ty, $format:expr) => {
368 impl ApiResponseTrait for $type {
369 fn data_format() -> ResponseFormat {
370 $format
371 }
372 }
373 };
374
375 ($type:ty, $format:expr, binary) => {
376 impl ApiResponseTrait for $type {
377 fn data_format() -> ResponseFormat {
378 $format
379 }
380
381 fn from_binary(file_name: String, body: Vec<u8>) -> Option<Self> {
382 Some(<$type>::from_binary_data(file_name, body))
383 }
384 }
385 };
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391 use crate::core::api_resp::ResponseFormat;
392 use serde::{Deserialize, Serialize};
393
394 #[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)]
395 struct TestData {
396 id: i32,
397 name: String,
398 }
399
400 impl ApiResponseTrait for TestData {
401 fn data_format() -> ResponseFormat {
402 ResponseFormat::Data
403 }
404 }
405
406 #[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)]
407 struct TestFlattenData {
408 id: i32,
409 name: String,
410 code: i32,
411 msg: String,
412 }
413
414 impl ApiResponseTrait for TestFlattenData {
415 fn data_format() -> ResponseFormat {
416 ResponseFormat::Flatten
417 }
418 }
419
420 #[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)]
421 struct TestBinaryData {
422 file_name: String,
423 content: Vec<u8>,
424 }
425
426 impl ApiResponseTrait for TestBinaryData {
427 fn data_format() -> ResponseFormat {
428 ResponseFormat::Binary
429 }
430
431 fn from_binary(file_name: String, body: Vec<u8>) -> Option<Self> {
432 Some(TestBinaryData {
433 file_name,
434 content: body,
435 })
436 }
437 }
438
439 #[test]
443 fn test_optimized_base_response_success() {
444 let response = OptimizedBaseResponse {
445 code: 0,
446 msg: "success".to_string(),
447 error: None,
448 data: Some(TestData {
449 id: 1,
450 name: "test".to_string(),
451 }),
452 };
453
454 assert!(response.is_success());
455 assert!(response.data().is_some());
456 assert_eq!(response.data().unwrap().id, 1);
457 assert!(!response.has_error());
458 }
459
460 #[test]
461 fn test_optimized_base_response_error() {
462 let response: OptimizedBaseResponse<TestData> = OptimizedBaseResponse {
463 code: 400,
464 msg: "Bad Request".to_string(),
465 error: Some(ErrorInfo {
466 log_id: Some("log123".to_string()),
467 details: vec![],
468 }),
469 data: None,
470 };
471
472 assert!(!response.is_success());
473 assert!(response.has_error());
474 assert!(response.data().is_none());
475 }
476
477 #[test]
478 fn test_optimized_base_response_into_data_success() {
479 let response = OptimizedBaseResponse {
480 code: 0,
481 msg: "success".to_string(),
482 error: None,
483 data: Some(TestData {
484 id: 1,
485 name: "test".to_string(),
486 }),
487 };
488
489 let data = response.into_data().unwrap();
490 assert_eq!(data.id, 1);
491 assert_eq!(data.name, "test");
492 }
493
494 #[test]
495 fn test_optimized_base_response_into_data_error() {
496 let response: OptimizedBaseResponse<TestData> = OptimizedBaseResponse {
497 code: 400,
498 msg: "Bad Request".to_string(),
499 error: None,
500 data: None,
501 };
502
503 let result = response.into_data();
504 assert!(result.is_err());
505 match result.unwrap_err() {
506 LarkAPIError::ApiError { code, message, .. } => {
507 assert_eq!(code, 400);
508 assert_eq!(message, "Bad Request");
509 }
510 _ => panic!("Expected ApiError"),
511 }
512 }
513
514 #[test]
515 fn test_optimized_base_response_into_data_success_but_no_data() {
516 let response: OptimizedBaseResponse<TestData> = OptimizedBaseResponse {
517 code: 0,
518 msg: "success".to_string(),
519 error: None,
520 data: None,
521 };
522
523 let result = response.into_data();
524 assert!(result.is_err());
525 match result.unwrap_err() {
526 LarkAPIError::IllegalParamError(msg) => {
527 assert!(msg.contains("data is missing"));
528 }
529 _ => panic!("Expected IllegalParamError"),
530 }
531 }
532
533 #[test]
534 fn test_error_info_serialization() {
535 let error_info = ErrorInfo {
536 log_id: Some("test_log_id".to_string()),
537 details: vec![
538 ErrorDetail {
539 key: Some("field1".to_string()),
540 value: Some("invalid_value".to_string()),
541 description: Some("Field is required".to_string()),
542 },
543 ErrorDetail {
544 key: Some("field2".to_string()),
545 value: None,
546 description: Some("Missing field".to_string()),
547 },
548 ],
549 };
550
551 let json = serde_json::to_string(&error_info).unwrap();
552 let deserialized: ErrorInfo = serde_json::from_str(&json).unwrap();
553
554 assert_eq!(deserialized.log_id, error_info.log_id);
555 assert_eq!(deserialized.details.len(), 2);
556 assert_eq!(deserialized.details[0].key, Some("field1".to_string()));
557 assert_eq!(deserialized.details[1].value, None);
558 }
559
560 #[test]
561 fn test_error_detail_optional_fields() {
562 let detail = ErrorDetail {
563 key: None,
564 value: Some("test_value".to_string()),
565 description: None,
566 };
567
568 let json = serde_json::to_string(&detail).unwrap();
569 let deserialized: ErrorDetail = serde_json::from_str(&json).unwrap();
570
571 assert_eq!(deserialized.key, None);
572 assert_eq!(deserialized.value, Some("test_value".to_string()));
573 assert_eq!(deserialized.description, None);
574 }
575
576 #[test]
577 fn test_filename_extraction() {
578 let cases = vec![
579 (
580 "attachment; filename=\"test.txt\"",
581 Some("test.txt".to_string()),
582 ),
583 (
584 "attachment; filename*=UTF-8''test%20file.pdf",
585 Some("test%20file.pdf".to_string()),
586 ),
587 (
588 "attachment; filename=simple.doc",
589 Some("simple.doc".to_string()),
590 ),
591 ("attachment", None),
592 ("", None),
593 ("filename=\"quoted.txt\"", Some("quoted.txt".to_string())),
594 ("filename=unquoted.txt", Some("unquoted.txt".to_string())),
595 (
596 "filename*=UTF-8''unicode%E2%9C%93.txt",
597 Some("unicode%E2%9C%93.txt".to_string()),
598 ),
599 (
600 "attachment; filename=\"spaced file.doc\"; other=value",
601 Some("spaced file.doc".to_string()),
602 ),
603 ];
604
605 for (input, expected) in cases {
606 let result = ImprovedResponseHandler::extract_filename(input);
607 assert_eq!(result, expected, "Failed for input: {input}");
608 }
609 }
610
611 #[test]
612 fn test_filename_extraction_edge_cases() {
613 assert_eq!(ImprovedResponseHandler::extract_filename(""), None);
615 assert_eq!(ImprovedResponseHandler::extract_filename(" "), None);
616 assert_eq!(ImprovedResponseHandler::extract_filename(";;;"), None);
617
618 assert_eq!(
620 ImprovedResponseHandler::extract_filename("filename="),
621 Some("".to_string())
622 );
623 assert_eq!(
624 ImprovedResponseHandler::extract_filename("filename*="),
625 None
626 ); assert_eq!(
628 ImprovedResponseHandler::extract_filename("filename=\""),
629 Some("".to_string())
630 );
631
632 assert_eq!(
634 ImprovedResponseHandler::extract_filename("filename=\"\""),
635 Some("".to_string())
636 );
637
638 let multi_filename = "filename=\"first.txt\"; filename=\"second.txt\"";
640 assert_eq!(
641 ImprovedResponseHandler::extract_filename(multi_filename),
642 Some("first.txt".to_string())
643 );
644 }
645
646 #[test]
647 fn test_json_parsing_performance() {
648 let json_data = r#"{"code": 0, "msg": "success", "data": {"id": 1, "name": "test"}}"#;
649
650 let start = std::time::Instant::now();
652 let _result: Result<OptimizedBaseResponse<TestData>, _> = serde_json::from_str(json_data);
653 let direct_parse_time = start.elapsed();
654
655 let start = std::time::Instant::now();
657 let _value: Value = serde_json::from_str(json_data).unwrap();
658 let _result: Result<OptimizedBaseResponse<TestData>, _> = serde_json::from_value(_value);
659 let double_parse_time = start.elapsed();
660
661 println!("Direct parse time: {direct_parse_time:?}");
662 println!("Double parse time: {double_parse_time:?}");
663
664 }
667
668 #[test]
669 fn test_api_response_trait_data_format() {
670 assert_eq!(TestData::data_format(), ResponseFormat::Data);
671 assert_eq!(TestFlattenData::data_format(), ResponseFormat::Flatten);
672 assert_eq!(TestBinaryData::data_format(), ResponseFormat::Binary);
673 }
674
675 #[test]
676 fn test_api_response_trait_from_binary() {
677 let file_name = "test.txt".to_string();
678 let content = b"Hello, World!".to_vec();
679
680 let binary_data = TestBinaryData::from_binary(file_name.clone(), content.clone()).unwrap();
681 assert_eq!(binary_data.file_name, file_name);
682 assert_eq!(binary_data.content, content);
683
684 let default_result = TestData::from_binary("test.txt".to_string(), vec![1, 2, 3]);
686 assert!(default_result.is_none());
687 }
688
689 #[tokio::test]
693 async fn test_handle_data_response_parsing_logic() {
694 let test_cases = vec![
696 (r#"{"code": 400, "msg": "Bad Request"}"#, true),
698 (r#"{"invalid": json"#, false),
700 ];
701
702 for (json, should_succeed) in test_cases {
703 if json.contains("code") && !json.contains("raw_response") {
705 let fallback_result = serde_json::from_str::<Value>(json);
706 if should_succeed {
707 assert!(
708 fallback_result.is_ok(),
709 "Fallback parsing should succeed for: {}",
710 json
711 );
712 let value = fallback_result.unwrap();
713 assert!(value["code"].is_i64());
714 assert!(value["msg"].is_string());
715 }
716 } else if json.contains("invalid") {
717 let parse_result = serde_json::from_str::<Value>(json);
718 assert!(parse_result.is_err(), "Invalid JSON should fail to parse");
719 }
720 }
721 }
722
723 #[tokio::test]
724 async fn test_handle_flatten_response_parsing_logic() {
725 let test_cases = vec![
726 (
728 r#"{"id": 1, "name": "test", "code": 0, "msg": "success"}"#,
729 0,
730 true,
731 ),
732 (r#"{"code": 400, "msg": "Bad Request"}"#, 400, false),
734 (r#"{"invalid": json"#, -1, false),
736 ];
737
738 for (json, expected_code, should_have_data) in test_cases {
739 if json.contains("invalid") {
740 let parse_result = serde_json::from_str::<Value>(json);
741 assert!(parse_result.is_err(), "Invalid JSON should fail to parse");
742 continue;
743 }
744
745 let value_result = serde_json::from_str::<Value>(json);
746 assert!(value_result.is_ok(), "Valid JSON should parse as Value");
747
748 let value = value_result.unwrap();
749 let raw_response_result = serde_json::from_value::<RawResponse>(value.clone());
750
751 if expected_code >= 0 {
752 assert!(
753 raw_response_result.is_ok(),
754 "Should parse RawResponse for: {}",
755 json
756 );
757 let raw_response = raw_response_result.unwrap();
758 assert_eq!(raw_response.code, expected_code);
759
760 if should_have_data && raw_response.code == 0 {
761 let data_result = serde_json::from_value::<TestFlattenData>(value);
762 assert!(
763 data_result.is_ok(),
764 "Should parse data for success response"
765 );
766 }
767 }
768 }
769 }
770
771 #[test]
772 fn test_response_format_display_logic() {
773 let formats = vec![
774 (ResponseFormat::Data, "data"),
775 (ResponseFormat::Flatten, "flatten"),
776 (ResponseFormat::Binary, "binary"),
777 ];
778
779 for (format, expected_str) in formats {
780 let format_str = match format {
781 ResponseFormat::Data => "data",
782 ResponseFormat::Flatten => "flatten",
783 ResponseFormat::Binary => "binary",
784 };
785 assert_eq!(format_str, expected_str);
786 }
787 }
788
789 #[test]
790 fn test_binary_response_logic() {
791 let test_file_name = "test_document.pdf";
792 let test_content = b"PDF content here".to_vec();
793
794 let binary_data =
796 TestBinaryData::from_binary(test_file_name.to_string(), test_content.clone());
797 assert!(binary_data.is_some());
798
799 let data = binary_data.unwrap();
800 assert_eq!(data.file_name, test_file_name);
801 assert_eq!(data.content, test_content);
802
803 let empty_data = TestBinaryData::from_binary("empty.txt".to_string(), vec![]);
805 assert!(empty_data.is_some());
806 assert_eq!(empty_data.unwrap().content.len(), 0);
807 }
808
809 #[test]
810 fn test_optimized_response_serialization_roundtrip() {
811 let original = OptimizedBaseResponse {
812 code: 0,
813 msg: "success".to_string(),
814 error: Some(ErrorInfo {
815 log_id: Some("test123".to_string()),
816 details: vec![ErrorDetail {
817 key: Some("validation".to_string()),
818 value: Some("failed".to_string()),
819 description: Some("Field validation failed".to_string()),
820 }],
821 }),
822 data: Some(TestData {
823 id: 42,
824 name: "serialization_test".to_string(),
825 }),
826 };
827
828 let json = serde_json::to_string(&original).unwrap();
830
831 let deserialized: OptimizedBaseResponse<TestData> = serde_json::from_str(&json).unwrap();
833
834 assert_eq!(deserialized.code, original.code);
836 assert_eq!(deserialized.msg, original.msg);
837 assert_eq!(deserialized.data, original.data);
838 assert!(deserialized.error.is_some());
839
840 let error = deserialized.error.unwrap();
841 assert_eq!(error.log_id, Some("test123".to_string()));
842 assert_eq!(error.details.len(), 1);
843 assert_eq!(error.details[0].key, Some("validation".to_string()));
844 }
845
846 #[test]
847 fn test_optimized_response_skipped_fields() {
848 let response: OptimizedBaseResponse<TestData> = OptimizedBaseResponse {
850 code: 0,
851 msg: "success".to_string(),
852 error: None,
853 data: None,
854 };
855
856 let json = serde_json::to_string(&response).unwrap();
857
858 assert!(!json.contains("\"error\""));
860 assert!(!json.contains("\"data\""));
861 assert!(json.contains("\"code\":0"));
862 assert!(json.contains("\"msg\":\"success\""));
863 }
864
865 #[test]
866 fn test_macro_api_response_implementation() {
867 #[derive(Debug, Default, Serialize, Deserialize)]
872 struct MacroTestData;
873
874 impl ApiResponseTrait for MacroTestData {
875 fn data_format() -> ResponseFormat {
876 ResponseFormat::Data
877 }
878 }
879
880 assert_eq!(MacroTestData::data_format(), ResponseFormat::Data);
881 assert!(MacroTestData::from_binary("test".to_string(), vec![1, 2, 3]).is_none());
882 }
883
884 #[test]
885 fn test_error_detail_empty_values() {
886 let detail = ErrorDetail {
887 key: Some("".to_string()),
888 value: Some("".to_string()),
889 description: Some("".to_string()),
890 };
891
892 let json = serde_json::to_string(&detail).unwrap();
893 let deserialized: ErrorDetail = serde_json::from_str(&json).unwrap();
894
895 assert_eq!(deserialized.key, Some("".to_string()));
896 assert_eq!(deserialized.value, Some("".to_string()));
897 assert_eq!(deserialized.description, Some("".to_string()));
898 }
899
900 #[test]
901 fn test_content_disposition_header_edge_cases() {
902 let edge_cases = vec![
903 ("FILENAME=\"test.txt\"", None), ("Filename=\"test.txt\"", None), (
908 "attachment; filename=\"test.txt\"",
909 Some("test.txt".to_string()),
910 ),
911 ("attachment; filename = \"test.txt\"", None), (
914 "attachment; filename=\"test-file_v1.2.txt\"",
915 Some("test-file_v1.2.txt".to_string()),
916 ),
917 (
918 "attachment; filename=\"测试文件.txt\"",
919 Some("测试文件.txt".to_string()),
920 ),
921 (
923 "attachment; filename=\"test.txt\"; filename*=UTF-8''better.txt",
924 Some("test.txt".to_string()),
925 ),
926 ];
927
928 for (input, expected) in edge_cases {
929 let result = ImprovedResponseHandler::extract_filename(input);
930 assert_eq!(result, expected, "Failed for input: {}", input);
931 }
932 }
933}
934
935mod usage_examples {}