1use reqwest::Method;
2use serde::{Deserialize, Serialize};
3
4use crate::{
5 core::{
6 api_req::ApiRequest,
7 api_resp::{ApiResponseTrait, BaseResponse, BinaryResponse, ResponseFormat},
8 config::Config,
9 constants::AccessTokenType,
10 endpoints::cloud_docs::*,
11 http::Transport,
12 req_option::RequestOption,
13 validation::{validate_file_name, validate_upload_file, ValidateBuilder, ValidationResult},
14 SDKResult,
15 },
16 impl_executable_builder_owned,
17};
18use log;
19
20pub struct MediaService {
22 config: Config,
23}
24
25impl MediaService {
26 pub fn new(config: Config) -> Self {
27 Self { config }
28 }
29
30 pub fn upload_all_builder(&self) -> UploadMediaRequestBuilder {
32 UploadMediaRequestBuilder::default()
33 }
34
35 pub async fn upload_all_with_builder(
37 &self,
38 builder_result: SDKResult<UploadMediaRequest>,
39 option: Option<RequestOption>,
40 ) -> SDKResult<BaseResponse<UploadMediaRespData>> {
41 let request = builder_result?;
42 self.upload_all(request, option).await
43 }
44
45 pub async fn upload_all(
51 &self,
52 request: UploadMediaRequest,
53 option: Option<RequestOption>,
54 ) -> SDKResult<BaseResponse<UploadMediaRespData>> {
55 let mut api_req = request.api_req;
56 api_req.http_method = Method::POST;
57 api_req.api_path = DRIVE_V1_MEDIAS_UPLOAD_ALL.to_string();
58 api_req.supported_access_token_types = vec![AccessTokenType::User, AccessTokenType::Tenant];
59
60 let api_resp = Transport::request(api_req, &self.config, option).await?;
61 Ok(api_resp)
62 }
63
64 pub async fn upload_prepare(
70 &self,
71 request: UploadPrepareRequest,
72 option: Option<RequestOption>,
73 ) -> SDKResult<BaseResponse<UploadPrepareRespData>> {
74 let api_req = ApiRequest {
75 http_method: Method::POST,
76 api_path: DRIVE_V1_MEDIAS_UPLOAD_PREPARE.to_string(),
77 supported_access_token_types: vec![AccessTokenType::User, AccessTokenType::Tenant],
78 body: serde_json::to_vec(&request)?,
79 ..Default::default()
80 };
81
82 let api_resp = Transport::request(api_req, &self.config, option).await?;
83 Ok(api_resp)
84 }
85
86 pub async fn upload_part(
92 &self,
93 request: UploadPartRequest,
94 option: Option<RequestOption>,
95 ) -> SDKResult<BaseResponse<UploadPartRespData>> {
96 let mut api_req = request.api_req;
97 api_req.http_method = Method::POST;
98 api_req.api_path = DRIVE_V1_MEDIAS_UPLOAD_PART.to_string();
99 api_req.supported_access_token_types = vec![AccessTokenType::User, AccessTokenType::Tenant];
100
101 let api_resp = Transport::request(api_req, &self.config, option).await?;
102 Ok(api_resp)
103 }
104
105 pub async fn upload_finish(
111 &self,
112 request: UploadFinishRequest,
113 option: Option<RequestOption>,
114 ) -> SDKResult<BaseResponse<UploadFinishRespData>> {
115 let api_req = ApiRequest {
116 http_method: Method::POST,
117 api_path: DRIVE_V1_MEDIAS_UPLOAD_FINISH.to_string(),
118 supported_access_token_types: vec![AccessTokenType::User, AccessTokenType::Tenant],
119 body: serde_json::to_vec(&request)?,
120 ..Default::default()
121 };
122
123 let api_resp = Transport::request(api_req, &self.config, option).await?;
124 Ok(api_resp)
125 }
126
127 pub async fn download(
133 &self,
134 request: DownloadMediaRequest,
135 option: Option<RequestOption>,
136 ) -> SDKResult<BaseResponse<BinaryResponse>> {
137 let api_req = ApiRequest {
138 http_method: Method::GET,
139 api_path: DRIVE_V1_MEDIAS_DOWNLOAD.replace("{}", &request.file_token),
140 supported_access_token_types: vec![AccessTokenType::User, AccessTokenType::Tenant],
141 ..Default::default()
142 };
143
144 let api_resp = Transport::request(api_req, &self.config, option).await?;
145 Ok(api_resp)
146 }
147
148 pub async fn batch_get_tmp_download_url(
154 &self,
155 request: BatchGetTmpDownloadUrlRequest,
156 option: Option<RequestOption>,
157 ) -> SDKResult<BaseResponse<BatchGetTmpDownloadUrlRespData>> {
158 let mut api_req = ApiRequest {
159 http_method: Method::GET,
160 api_path: DRIVE_V1_MEDIAS_BATCH_GET_TMP_DOWNLOAD_URL.to_string(),
161 supported_access_token_types: vec![AccessTokenType::User, AccessTokenType::Tenant],
162 ..Default::default()
163 };
164
165 let file_tokens = request.file_tokens.join(",");
167 api_req.query_params.insert("file_tokens", file_tokens);
168
169 let api_resp = Transport::request(api_req, &self.config, option).await?;
170 Ok(api_resp)
171 }
172}
173
174#[derive(Debug, Clone, Default, Serialize, Deserialize)]
178pub struct UploadMediaRequest {
179 #[serde(skip)]
181 pub api_req: ApiRequest,
182 file_name: String,
184 parent_token: String,
186 size: i32,
188 checksum: Option<String>,
190}
191
192impl UploadMediaRequest {
193 pub fn builder() -> UploadMediaRequestBuilder {
194 UploadMediaRequestBuilder::default()
195 }
196}
197
198#[derive(Default)]
200pub struct UploadMediaRequestBuilder {
201 request: UploadMediaRequest,
202}
203
204impl UploadMediaRequestBuilder {
205 pub fn file_name(mut self, file_name: impl ToString) -> Self {
206 self.request.file_name = file_name.to_string();
207 self
208 }
209
210 pub fn parent_token(mut self, parent_token: impl ToString) -> Self {
211 self.request.parent_token = parent_token.to_string();
212 self
213 }
214
215 pub fn size(mut self, size: i32) -> Self {
216 self.request.size = size;
217 self
218 }
219
220 pub fn checksum(mut self, checksum: impl ToString) -> Self {
221 self.request.checksum = Some(checksum.to_string());
222 self
223 }
224
225 pub fn file(mut self, file: Vec<u8>) -> Self {
226 self.request.api_req.file = file;
227 self
228 }
229
230 pub fn build(mut self) -> UploadMediaRequest {
231 if self.request.file_name.is_empty() {
233 log::error!("file_name is required for media upload");
234 return UploadMediaRequest {
235 api_req: ApiRequest {
236 body: Vec::new(),
237 ..Default::default()
238 },
239 ..self.request
240 };
241 }
242
243 if self.request.parent_token.is_empty() {
244 log::error!("parent_token is required for media upload");
245 return UploadMediaRequest {
246 api_req: ApiRequest {
247 body: Vec::new(),
248 ..Default::default()
249 },
250 ..self.request
251 };
252 }
253
254 if self.request.size <= 0 {
255 log::error!("file size must be greater than 0");
256 return UploadMediaRequest {
257 api_req: ApiRequest {
258 body: Vec::new(),
259 ..Default::default()
260 },
261 ..self.request
262 };
263 }
264
265 let (_, name_result) = validate_file_name(&self.request.file_name);
267 if !name_result.is_valid() {
268 log::error!(
269 "Invalid file_name: {}",
270 name_result.error().unwrap_or("unknown error")
271 );
272 return UploadMediaRequest {
273 api_req: ApiRequest {
274 body: Vec::new(),
275 ..Default::default()
276 },
277 ..self.request
278 };
279 }
280
281 if !self.request.api_req.file.is_empty() {
283 let upload_result =
284 validate_upload_file(&self.request.api_req.file, &self.request.file_name, false);
285 if !upload_result.is_valid() {
286 log::error!(
287 "File validation failed: {}",
288 upload_result.error().unwrap_or("unknown error")
289 );
290 return UploadMediaRequest {
291 api_req: ApiRequest {
292 body: Vec::new(),
293 ..Default::default()
294 },
295 ..self.request
296 };
297 }
298 }
299
300 self.request.api_req.body = match serde_json::to_vec(&self.request) {
301 Ok(body) => body,
302 Err(e) => {
303 log::error!("Failed to serialize upload media request: {}", e);
304 return UploadMediaRequest {
305 api_req: ApiRequest {
306 body: Vec::new(),
307 ..Default::default()
308 },
309 ..self.request
310 };
311 }
312 };
313 self.request
314 }
315}
316
317impl ValidateBuilder for UploadMediaRequestBuilder {
318 fn validate(&self) -> ValidationResult {
319 if self.request.file_name.is_empty() {
321 return ValidationResult::Invalid("file_name is required".to_string());
322 }
323
324 if self.request.parent_token.is_empty() {
325 return ValidationResult::Invalid("parent_token is required".to_string());
326 }
327
328 if self.request.size <= 0 {
329 return ValidationResult::Invalid("file size must be greater than 0".to_string());
330 }
331
332 let (_, name_result) = validate_file_name(&self.request.file_name);
334 if !name_result.is_valid() {
335 return name_result;
336 }
337
338 if !self.request.api_req.file.is_empty() {
340 validate_upload_file(&self.request.api_req.file, &self.request.file_name, false)
341 } else {
342 ValidationResult::Valid
343 }
344 }
345}
346
347#[derive(Debug, Clone, Serialize, Deserialize)]
349pub struct UploadMediaRespData {
350 pub file_token: String,
352}
353
354impl ApiResponseTrait for UploadMediaRespData {
355 fn data_format() -> ResponseFormat {
356 ResponseFormat::Data
357 }
358}
359
360#[derive(Debug, Clone, Serialize, Deserialize)]
362pub struct UploadPrepareRequest {
363 pub file_name: String,
365 pub parent_token: String,
367 pub size: i64,
369 pub block_size: Option<i32>,
371 pub checksum: Option<String>,
373}
374
375impl UploadPrepareRequest {
376 pub fn new(file_name: impl Into<String>, parent_token: impl Into<String>, size: i64) -> Self {
377 Self {
378 file_name: file_name.into(),
379 parent_token: parent_token.into(),
380 size,
381 block_size: None,
382 checksum: None,
383 }
384 }
385}
386
387#[derive(Debug, Clone, Serialize, Deserialize)]
389pub struct UploadPrepareRespData {
390 pub upload_id: String,
392 pub block_size: i32,
394 pub block_num: i32,
396}
397
398impl ApiResponseTrait for UploadPrepareRespData {
399 fn data_format() -> ResponseFormat {
400 ResponseFormat::Data
401 }
402}
403
404#[derive(Debug, Clone, Default, Serialize, Deserialize)]
406pub struct UploadPartRequest {
407 #[serde(skip)]
409 pub api_req: ApiRequest,
410 upload_id: String,
412 seq: i32,
414 size: i32,
416 checksum: Option<String>,
418}
419
420impl UploadPartRequest {
421 pub fn builder() -> UploadPartRequestBuilder {
422 UploadPartRequestBuilder::default()
423 }
424}
425
426#[derive(Default)]
428pub struct UploadPartRequestBuilder {
429 request: UploadPartRequest,
430}
431
432impl UploadPartRequestBuilder {
433 pub fn upload_id(mut self, upload_id: impl ToString) -> Self {
434 self.request.upload_id = upload_id.to_string();
435 self
436 }
437
438 pub fn seq(mut self, seq: i32) -> Self {
439 self.request.seq = seq;
440 self
441 }
442
443 pub fn size(mut self, size: i32) -> Self {
444 self.request.size = size;
445 self
446 }
447
448 pub fn checksum(mut self, checksum: impl ToString) -> Self {
449 self.request.checksum = Some(checksum.to_string());
450 self
451 }
452
453 pub fn file_chunk(mut self, chunk: Vec<u8>) -> Self {
454 self.request.api_req.file = chunk;
455 self
456 }
457
458 pub fn build(mut self) -> UploadPartRequest {
459 self.request.api_req.body = serde_json::to_vec(&self.request).unwrap();
460 self.request
461 }
462}
463
464#[derive(Debug, Clone, Serialize, Deserialize)]
466pub struct UploadPartRespData {
467 pub etag: String,
469}
470
471impl ApiResponseTrait for UploadPartRespData {
472 fn data_format() -> ResponseFormat {
473 ResponseFormat::Data
474 }
475}
476
477#[derive(Debug, Clone, Serialize, Deserialize)]
479pub struct UploadFinishRequest {
480 pub upload_id: String,
482 pub block_infos: Vec<BlockInfo>,
484}
485
486#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
487pub struct BlockInfo {
488 pub etag: String,
490 pub seq: i32,
492}
493
494impl UploadFinishRequest {
495 pub fn new(upload_id: impl Into<String>, block_infos: Vec<BlockInfo>) -> Self {
496 Self {
497 upload_id: upload_id.into(),
498 block_infos,
499 }
500 }
501}
502
503#[derive(Debug, Clone, Serialize, Deserialize)]
505pub struct UploadFinishRespData {
506 pub file_token: String,
508}
509
510impl ApiResponseTrait for UploadFinishRespData {
511 fn data_format() -> ResponseFormat {
512 ResponseFormat::Data
513 }
514}
515
516#[derive(Debug, Clone, Serialize, Deserialize)]
518pub struct DownloadMediaRequest {
519 pub file_token: String,
521}
522
523impl DownloadMediaRequest {
524 pub fn new(file_token: impl Into<String>) -> Self {
525 Self {
526 file_token: file_token.into(),
527 }
528 }
529}
530
531#[derive(Debug, Clone, Serialize, Deserialize)]
533pub struct BatchGetTmpDownloadUrlRequest {
534 pub file_tokens: Vec<String>,
536}
537
538impl BatchGetTmpDownloadUrlRequest {
539 pub fn new(file_tokens: Vec<String>) -> Self {
540 Self { file_tokens }
541 }
542}
543
544#[derive(Debug, Clone, Serialize, Deserialize)]
546pub struct BatchGetTmpDownloadUrlRespData {
547 pub tmp_download_urls: Vec<TmpDownloadUrl>,
549}
550
551#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
552pub struct TmpDownloadUrl {
553 pub file_token: String,
555 pub tmp_download_url: String,
557}
558
559impl ApiResponseTrait for BatchGetTmpDownloadUrlRespData {
560 fn data_format() -> ResponseFormat {
561 ResponseFormat::Data
562 }
563}
564
565impl_executable_builder_owned!(
568 UploadMediaRequestBuilder,
569 MediaService,
570 UploadMediaRequest,
571 BaseResponse<UploadMediaRespData>,
572 upload_all
573);
574
575impl_executable_builder_owned!(
576 UploadPartRequestBuilder,
577 MediaService,
578 UploadPartRequest,
579 BaseResponse<UploadPartRespData>,
580 upload_part
581);
582
583#[cfg(test)]
584mod tests {
585 use super::*;
586 use rstest::*;
587
588 fn mock_config() -> Config {
589 Config::builder()
590 .app_id("test_app_id")
591 .app_secret("test_app_secret")
592 .build()
593 }
594
595 #[test]
598 fn test_media_service_new() {
599 let config = mock_config();
600 let service = MediaService::new(config.clone());
601 assert_eq!(service.config.app_id, config.app_id);
602 }
603
604 #[test]
605 fn test_upload_all_builder() {
606 let service = MediaService::new(mock_config());
607 let builder = service.upload_all_builder();
608 assert_eq!(builder.request.file_name, "");
609 assert_eq!(builder.request.parent_token, "");
610 assert_eq!(builder.request.size, 0);
611 }
612
613 #[test]
616 fn test_upload_media_request_builder() {
617 let request = UploadMediaRequest::builder()
618 .file_name("test.pdf")
619 .parent_token("parent123")
620 .size(1024)
621 .build();
622 assert_eq!(request.file_name, "test.pdf");
623 assert_eq!(request.parent_token, "parent123");
624 assert_eq!(request.size, 1024);
625 assert!(request.checksum.is_none());
626 }
627
628 #[test]
631 fn test_upload_media_builder_basic() {
632 let builder = UploadMediaRequestBuilder::default()
633 .file_name("test_file.txt")
634 .parent_token("parent_token_123")
635 .size(1024);
636
637 assert_eq!(builder.request.file_name, "test_file.txt");
638 assert_eq!(builder.request.parent_token, "parent_token_123");
639 assert_eq!(builder.request.size, 1024);
640 }
641
642 #[test]
643 fn test_upload_media_builder_with_checksum() {
644 let builder = UploadMediaRequestBuilder::default()
645 .file_name("image.png")
646 .parent_token("images_folder")
647 .size(512000)
648 .checksum("md5:hash123");
649
650 assert_eq!(builder.request.file_name, "image.png");
651 assert_eq!(builder.request.checksum, Some("md5:hash123".to_string()));
652 }
653
654 #[test]
655 fn test_upload_media_builder_with_file_data() {
656 let file_data = vec![0x89, 0x50, 0x4E, 0x47]; let builder = UploadMediaRequestBuilder::default()
658 .file_name("test.png")
659 .parent_token("parent")
660 .size(4)
661 .file(file_data.clone());
662
663 assert_eq!(builder.request.api_req.file, file_data);
664 }
665
666 #[test]
667 fn test_upload_media_builder_build_valid() {
668 let file_data = b"Hello World".to_vec();
669 let request = UploadMediaRequestBuilder::default()
670 .file_name("hello.txt")
671 .parent_token("parent123")
672 .size(11)
673 .file(file_data)
674 .build();
675
676 assert_eq!(request.file_name, "hello.txt");
677 assert_eq!(request.parent_token, "parent123");
678 assert_eq!(request.size, 11);
679 assert!(!request.api_req.body.is_empty());
680 }
681
682 #[test]
683 fn test_upload_media_builder_build_missing_filename() {
684 let request = UploadMediaRequestBuilder::default()
685 .parent_token("parent123")
686 .size(100)
687 .build();
688
689 assert!(request.api_req.body.is_empty());
691 }
692
693 #[test]
694 fn test_upload_media_builder_build_missing_parent_token() {
695 let request = UploadMediaRequestBuilder::default()
696 .file_name("test.txt")
697 .size(100)
698 .build();
699
700 assert!(request.api_req.body.is_empty());
702 }
703
704 #[test]
705 fn test_upload_media_builder_build_invalid_size() {
706 let request = UploadMediaRequestBuilder::default()
707 .file_name("test.txt")
708 .parent_token("parent123")
709 .size(0) .build();
711
712 assert!(request.api_req.body.is_empty());
714 }
715
716 #[test]
717 fn test_upload_media_builder_build_negative_size() {
718 let request = UploadMediaRequestBuilder::default()
719 .file_name("test.txt")
720 .parent_token("parent123")
721 .size(-1) .build();
723
724 assert!(request.api_req.body.is_empty());
726 }
727
728 #[test]
731 fn test_upload_prepare_request_new() {
732 let request = UploadPrepareRequest::new("large_file.zip", "uploads", 10485760);
733 assert_eq!(request.file_name, "large_file.zip");
734 assert_eq!(request.parent_token, "uploads");
735 assert_eq!(request.size, 10485760);
736 assert!(request.block_size.is_none());
737 }
738
739 #[test]
740 fn test_upload_prepare_request_with_block_size() {
741 let mut request = UploadPrepareRequest::new("video.mp4", "videos", 104857600);
742 request.block_size = Some(1048576); assert_eq!(request.file_name, "video.mp4");
744 assert_eq!(request.block_size, Some(1048576));
745 }
746
747 #[test]
750 fn test_upload_part_request_builder() {
751 let request = UploadPartRequest::builder()
752 .upload_id("upload123")
753 .seq(1)
754 .build();
755 assert_eq!(request.upload_id, "upload123");
756 assert_eq!(request.seq, 1);
757 assert_eq!(request.api_req.file.len(), 0);
758 }
759
760 #[test]
761 fn test_upload_part_request_builder_with_file() {
762 let _file_chunk = [0x01, 0x02, 0x03, 0x04];
763 let request = UploadPartRequest::builder()
764 .upload_id("upload456")
765 .seq(2)
766 .size(4)
767 .checksum("crc32:abc123")
768 .build();
769
770 assert_eq!(request.upload_id, "upload456");
771 assert_eq!(request.seq, 2);
772 assert_eq!(request.size, 4);
773 assert_eq!(request.checksum, Some("crc32:abc123".to_string()));
774 }
775
776 #[test]
777 fn test_upload_part_request_builder_minimal() {
778 let request = UploadPartRequestBuilder::default()
779 .upload_id("minimal")
780 .seq(1)
781 .build();
782
783 assert_eq!(request.upload_id, "minimal");
784 assert_eq!(request.seq, 1);
785 assert_eq!(request.size, 0);
786 assert!(request.checksum.is_none());
787 }
788
789 #[test]
792 fn test_upload_finish_request_new() {
793 let block_infos = vec![
794 BlockInfo {
795 etag: "hash1".to_string(),
796 seq: 1,
797 },
798 BlockInfo {
799 etag: "hash2".to_string(),
800 seq: 2,
801 },
802 ];
803 let request = UploadFinishRequest::new("upload789", block_infos.clone());
804 assert_eq!(request.upload_id, "upload789");
805 assert_eq!(request.block_infos, block_infos);
806 }
807
808 #[test]
811 fn test_download_media_request_new() {
812 let request = DownloadMediaRequest::new("media_token_abc");
813 assert_eq!(request.file_token, "media_token_abc");
814 }
815
816 #[test]
819 fn test_batch_get_tmp_download_url_request_new() {
820 let request = BatchGetTmpDownloadUrlRequest::new(vec!["temp_token_xyz".to_string()]);
821 assert_eq!(request.file_tokens, vec!["temp_token_xyz".to_string()]);
822 }
823
824 #[test]
827 fn test_upload_media_resp_data() {
828 let data = UploadMediaRespData {
829 file_token: "uploaded_file_token".to_string(),
830 };
831 assert_eq!(data.file_token, "uploaded_file_token");
832 }
833
834 #[test]
835 fn test_upload_prepare_resp_data() {
836 let data = UploadPrepareRespData {
837 upload_id: "prepared_upload_123".to_string(),
838 block_size: 1048576,
839 block_num: 10,
840 };
841 assert_eq!(data.upload_id, "prepared_upload_123");
842 assert_eq!(data.block_size, 1048576);
843 assert_eq!(data.block_num, 10);
844 }
845
846 #[test]
847 fn test_upload_part_resp_data() {
848 let data = UploadPartRespData {
849 etag: "part_etag_456".to_string(),
850 };
851 assert_eq!(data.etag, "part_etag_456");
852 }
853
854 #[test]
855 fn test_upload_finish_resp_data() {
856 let data = UploadFinishRespData {
857 file_token: "finished_file_token".to_string(),
858 };
859 assert_eq!(data.file_token, "finished_file_token");
860 }
861
862 #[test]
863 fn test_batch_get_tmp_download_url_resp_data() {
864 let data = BatchGetTmpDownloadUrlRespData {
865 tmp_download_urls: vec![TmpDownloadUrl {
866 file_token: "token123".to_string(),
867 tmp_download_url: "https://temp.example.com/download/abc123".to_string(),
868 }],
869 };
870 assert_eq!(
871 data.tmp_download_urls[0].tmp_download_url,
872 "https://temp.example.com/download/abc123"
873 );
874 }
875
876 #[rstest]
879 #[case("upload_media_request")]
880 #[case("upload_prepare_request")]
881 #[case("upload_part_request")]
882 #[case("upload_finish_request")]
883 #[case("download_media_request")]
884 #[case("batch_get_tmp_download_url_request")]
885 fn test_request_serialization_roundtrip(#[case] request_type: &str) {
886 match request_type {
887 "upload_media_request" => {
888 let original = UploadMediaRequest::builder()
889 .file_name("test.txt")
890 .parent_token("parent123")
891 .size(100)
892 .checksum("sha256:test")
893 .build();
894 let json = serde_json::to_string(&original).unwrap();
895 let deserialized: UploadMediaRequest = serde_json::from_str(&json).unwrap();
896 assert_eq!(original.file_name, deserialized.file_name);
897 assert_eq!(original.parent_token, deserialized.parent_token);
898 assert_eq!(original.size, deserialized.size);
899 assert_eq!(original.checksum, deserialized.checksum);
900 }
901 "upload_prepare_request" => {
902 let mut original = UploadPrepareRequest::new("large.zip", "uploads", 1000000);
903 original.block_size = Some(4096);
904 let json = serde_json::to_string(&original).unwrap();
905 let deserialized: UploadPrepareRequest = serde_json::from_str(&json).unwrap();
906 assert_eq!(original.file_name, deserialized.file_name);
907 assert_eq!(original.parent_token, deserialized.parent_token);
908 assert_eq!(original.size, deserialized.size);
909 assert_eq!(original.block_size, deserialized.block_size);
910 }
911 "upload_part_request" => {
912 let original = UploadPartRequest::builder()
913 .upload_id("test_upload")
914 .seq(1)
915 .size(1024)
916 .checksum("test_checksum")
917 .build();
918 let json = serde_json::to_string(&original).unwrap();
919 let deserialized: UploadPartRequest = serde_json::from_str(&json).unwrap();
920 assert_eq!(original.upload_id, deserialized.upload_id);
921 assert_eq!(original.seq, deserialized.seq);
922 assert_eq!(original.size, deserialized.size);
923 assert_eq!(original.checksum, deserialized.checksum);
924 }
925 "upload_finish_request" => {
926 let checksums = vec!["hash1".to_string(), "hash2".to_string()];
927 let block_infos: Vec<BlockInfo> = checksums
928 .into_iter()
929 .enumerate()
930 .map(|(i, etag)| BlockInfo {
931 etag,
932 seq: i as i32,
933 })
934 .collect();
935 let original = UploadFinishRequest::new("upload123", block_infos.clone());
936 let json = serde_json::to_string(&original).unwrap();
937 let deserialized: UploadFinishRequest = serde_json::from_str(&json).unwrap();
938 assert_eq!(original.upload_id, deserialized.upload_id);
939 assert_eq!(original.block_infos, deserialized.block_infos);
940 }
941 "download_media_request" => {
942 let original = DownloadMediaRequest::new("download_token");
943 let json = serde_json::to_string(&original).unwrap();
944 let deserialized: DownloadMediaRequest = serde_json::from_str(&json).unwrap();
945 assert_eq!(original.file_token, deserialized.file_token);
946 }
947 "batch_get_tmp_download_url_request" => {
948 let original = BatchGetTmpDownloadUrlRequest::new(vec!["temp_token".to_string()]);
949 let json = serde_json::to_string(&original).unwrap();
950 let deserialized: BatchGetTmpDownloadUrlRequest =
951 serde_json::from_str(&json).unwrap();
952 assert_eq!(original.file_tokens, deserialized.file_tokens);
953 }
954 _ => panic!("Unknown request type: {}", request_type),
955 }
956 }
957
958 #[rstest]
959 #[case("upload_media_resp")]
960 #[case("upload_prepare_resp")]
961 #[case("upload_part_resp")]
962 #[case("upload_finish_resp")]
963 #[case("batch_get_tmp_download_url_resp")]
964 fn test_response_serialization_roundtrip(#[case] response_type: &str) {
965 match response_type {
966 "upload_media_resp" => {
967 let original = UploadMediaRespData {
968 file_token: "response_token".to_string(),
969 };
970 let json = serde_json::to_string(&original).unwrap();
971 let deserialized: UploadMediaRespData = serde_json::from_str(&json).unwrap();
972 assert_eq!(original.file_token, deserialized.file_token);
973 }
974 "upload_prepare_resp" => {
975 let original = UploadPrepareRespData {
976 upload_id: "prepared_id".to_string(),
977 block_size: 4096,
978 block_num: 5,
979 };
980 let json = serde_json::to_string(&original).unwrap();
981 let deserialized: UploadPrepareRespData = serde_json::from_str(&json).unwrap();
982 assert_eq!(original.upload_id, deserialized.upload_id);
983 assert_eq!(original.block_size, deserialized.block_size);
984 assert_eq!(original.block_num, deserialized.block_num);
985 }
986 "upload_part_resp" => {
987 let original = UploadPartRespData {
988 etag: "part_etag".to_string(),
989 };
990 let json = serde_json::to_string(&original).unwrap();
991 let deserialized: UploadPartRespData = serde_json::from_str(&json).unwrap();
992 assert_eq!(original.etag, deserialized.etag);
993 }
994 "upload_finish_resp" => {
995 let original = UploadFinishRespData {
996 file_token: "final_token".to_string(),
997 };
998 let json = serde_json::to_string(&original).unwrap();
999 let deserialized: UploadFinishRespData = serde_json::from_str(&json).unwrap();
1000 assert_eq!(original.file_token, deserialized.file_token);
1001 }
1002 "batch_get_tmp_download_url_resp" => {
1003 let original = BatchGetTmpDownloadUrlRespData {
1004 tmp_download_urls: vec![TmpDownloadUrl {
1005 file_token: "token123".to_string(),
1006 tmp_download_url: "https://example.com/temp".to_string(),
1007 }],
1008 };
1009 let json = serde_json::to_string(&original).unwrap();
1010 let deserialized: BatchGetTmpDownloadUrlRespData =
1011 serde_json::from_str(&json).unwrap();
1012 assert_eq!(original.tmp_download_urls, deserialized.tmp_download_urls);
1013 }
1014 _ => panic!("Unknown response type: {}", response_type),
1015 }
1016 }
1017
1018 #[rstest]
1021 #[case("UploadMediaRespData")]
1022 #[case("UploadPrepareRespData")]
1023 #[case("UploadPartRespData")]
1024 #[case("UploadFinishRespData")]
1025 #[case("BatchGetTmpDownloadUrlRespData")]
1026 fn test_api_response_trait(#[case] response_type: &str) {
1027 let format = match response_type {
1028 "UploadMediaRespData" => UploadMediaRespData::data_format(),
1029 "UploadPrepareRespData" => UploadPrepareRespData::data_format(),
1030 "UploadPartRespData" => UploadPartRespData::data_format(),
1031 "UploadFinishRespData" => UploadFinishRespData::data_format(),
1032 "BatchGetTmpDownloadUrlRespData" => BatchGetTmpDownloadUrlRespData::data_format(),
1033 _ => panic!("Unknown response type: {}", response_type),
1034 };
1035 assert_eq!(format, ResponseFormat::Data);
1036 }
1037
1038 #[test]
1041 fn test_upload_media_builder_validate_success() {
1042 let builder = UploadMediaRequestBuilder::default()
1043 .file_name("valid.txt")
1044 .parent_token("valid_parent")
1045 .size(100);
1046
1047 let result = builder.validate();
1048 assert!(result.is_valid());
1049 }
1050
1051 #[test]
1052 fn test_upload_media_builder_validate_missing_filename() {
1053 let builder = UploadMediaRequestBuilder::default()
1054 .parent_token("valid_parent")
1055 .size(100);
1056
1057 let result = builder.validate();
1058 assert!(!result.is_valid());
1059 assert!(result.error().unwrap().contains("file_name is required"));
1060 }
1061
1062 #[test]
1063 fn test_upload_media_builder_validate_missing_parent() {
1064 let builder = UploadMediaRequestBuilder::default()
1065 .file_name("valid.txt")
1066 .size(100);
1067
1068 let result = builder.validate();
1069 assert!(!result.is_valid());
1070 assert!(result.error().unwrap().contains("parent_token is required"));
1071 }
1072
1073 #[test]
1074 fn test_upload_media_builder_validate_invalid_size() {
1075 let builder = UploadMediaRequestBuilder::default()
1076 .file_name("valid.txt")
1077 .parent_token("valid_parent")
1078 .size(0);
1079
1080 let result = builder.validate();
1081 assert!(!result.is_valid());
1082 assert!(result
1083 .error()
1084 .unwrap()
1085 .contains("file size must be greater than 0"));
1086 }
1087
1088 #[test]
1091 fn test_upload_large_file_size() {
1092 let request = UploadPrepareRequest::new("huge_file.bin", "storage", i64::MAX);
1093 assert_eq!(request.size, i64::MAX);
1094 }
1095
1096 #[test]
1097 fn test_upload_zero_block_size() {
1098 let mut request = UploadPrepareRequest::new("test.txt", "parent", 1000);
1099 request.block_size = Some(0);
1100 assert_eq!(request.block_size, Some(0));
1101 }
1102
1103 #[test]
1104 fn test_upload_part_maximum_sequence() {
1105 let request = UploadPartRequest::builder()
1106 .upload_id("upload123")
1107 .seq(i32::MAX)
1108 .size(0)
1109 .build();
1110 assert_eq!(request.seq, i32::MAX);
1111 }
1112
1113 #[test]
1114 fn test_unicode_filename() {
1115 let unicode_name = "测试文件🎉.docx";
1116 let request = UploadMediaRequest::builder()
1117 .file_name(unicode_name)
1118 .parent_token("folder")
1119 .size(1024)
1120 .build();
1121 assert_eq!(request.file_name, unicode_name);
1122 }
1123
1124 #[test]
1125 fn test_special_characters_in_filename() {
1126 let special_name = "file with spaces & symbols @#$.txt";
1127 let request = UploadMediaRequest::builder()
1128 .file_name(special_name)
1129 .parent_token("parent")
1130 .size(512)
1131 .build();
1132 assert_eq!(request.file_name, special_name);
1133 }
1134
1135 #[test]
1136 fn test_empty_block_checksums() {
1137 let empty_block_infos: Vec<BlockInfo> = vec![];
1138 let request = UploadFinishRequest::new("upload456", empty_block_infos);
1139 assert_eq!(request.block_infos.len(), 0);
1140 }
1141
1142 #[test]
1143 fn test_many_block_checksums() {
1144 let many_checksums: Vec<String> = (0..1000).map(|i| format!("hash_{}", i)).collect();
1145 let many_block_infos: Vec<BlockInfo> = many_checksums
1146 .into_iter()
1147 .enumerate()
1148 .map(|(i, etag)| BlockInfo {
1149 etag,
1150 seq: i as i32,
1151 })
1152 .collect();
1153 let request = UploadFinishRequest::new("upload789", many_block_infos.clone());
1154 assert_eq!(request.block_infos.len(), 1000);
1155 assert_eq!(request.block_infos[0].etag, "hash_0");
1156 assert_eq!(request.block_infos[999].etag, "hash_999");
1157 }
1158
1159 #[test]
1160 fn test_very_long_token() {
1161 let long_token = "a".repeat(1000);
1162 let request = DownloadMediaRequest::new(&long_token);
1163 assert_eq!(request.file_token, long_token);
1164 }
1165
1166 #[test]
1167 fn test_very_long_url() {
1168 let long_url = format!("https://example.com/{}", "a".repeat(1000));
1169 let data = BatchGetTmpDownloadUrlRespData {
1170 tmp_download_urls: vec![TmpDownloadUrl {
1171 file_token: "token123".to_string(),
1172 tmp_download_url: long_url.clone(),
1173 }],
1174 };
1175 assert_eq!(data.tmp_download_urls[0].tmp_download_url, long_url);
1176 }
1177
1178 #[test]
1181 fn test_request_cloning() {
1182 let original = UploadMediaRequest::builder()
1183 .file_name("clone_test.txt")
1184 .parent_token("parent")
1185 .size(256)
1186 .checksum("test_hash")
1187 .build();
1188 let cloned = original.clone();
1189
1190 assert_eq!(original.file_name, cloned.file_name);
1191 assert_eq!(original.parent_token, cloned.parent_token);
1192 assert_eq!(original.size, cloned.size);
1193 assert_eq!(original.checksum, cloned.checksum);
1194 }
1195
1196 #[test]
1197 fn test_response_debug() {
1198 let data = UploadPrepareRespData {
1199 upload_id: "debug_test".to_string(),
1200 block_size: 4096,
1201 block_num: 2,
1202 };
1203 let debug_str = format!("{:?}", data);
1204 assert!(debug_str.contains("debug_test"));
1205 assert!(debug_str.contains("4096"));
1206 assert!(debug_str.contains("2"));
1207 }
1208
1209 #[test]
1212 fn test_upload_part_builder_method_chaining() {
1213 let chunk_data = vec![0xFF; 1024];
1214 let request = UploadPartRequest::builder()
1215 .upload_id("chain_test")
1216 .seq(3)
1217 .size(1024)
1218 .checksum("md5:abc123")
1219 .file_chunk(chunk_data.clone())
1220 .build();
1221
1222 assert_eq!(request.upload_id, "chain_test");
1223 assert_eq!(request.seq, 3);
1224 assert_eq!(request.size, 1024);
1225 assert_eq!(request.checksum, Some("md5:abc123".to_string()));
1226 assert_eq!(request.api_req.file, chunk_data);
1227 }
1228
1229 #[test]
1230 fn test_upload_part_builder_overwrite_values() {
1231 let request = UploadPartRequest::builder()
1232 .upload_id("first")
1233 .upload_id("second") .seq(1)
1235 .seq(5) .build();
1237
1238 assert_eq!(request.upload_id, "second");
1239 assert_eq!(request.seq, 5);
1240 }
1241
1242 #[test]
1243 fn test_default_values() {
1244 let request = UploadPartRequestBuilder::default().build();
1245 assert_eq!(request.upload_id, "");
1246 assert_eq!(request.seq, 0);
1247 assert_eq!(request.size, 0);
1248 assert!(request.checksum.is_none());
1249 assert!(request.api_req.file.is_empty());
1250 }
1251}