1use std::path::Path;
2
3use async_trait::async_trait;
4use base64::{prelude::BASE64_STANDARD, Engine};
5use futures::TryStreamExt;
6use reqwest::StatusCode;
7use tokio::io::AsyncWriteExt;
8
9use crate::{
10 error::Error,
11 object_common::{
12 build_copy_object_request, build_delete_multiple_objects_request, build_get_object_request, build_head_object_request, build_put_object_request,
13 build_restore_object_request, AppendObjectOptions, AppendObjectResult, CopyObjectOptions, CopyObjectResult, DeleteMultipleObjectsConfig,
14 DeleteMultipleObjectsResult, DeleteObjectOptions, DeleteObjectResult, GetObjectMetadataOptions, GetObjectOptions, GetObjectResult, HeadObjectOptions,
15 ObjectMetadata, PutObjectOptions, PutObjectResult, RestoreObjectRequest, RestoreObjectResult,
16 },
17 request::{OssRequest, RequestMethod},
18 util::{validate_bucket_name, validate_object_key, validate_path},
19 ByteStream, Client, RequestBody, Result,
20};
21
22#[async_trait]
23pub trait ObjectOperations {
24 async fn put_object_from_file<S1, S2, P>(
28 &self,
29 bucket_name: S1,
30 object_key: S2,
31 file_path: P,
32 options: Option<PutObjectOptions>,
33 ) -> Result<PutObjectResult>
34 where
35 S1: AsRef<str> + Send,
36 S2: AsRef<str> + Send,
37 P: AsRef<Path> + Send;
38
39 async fn put_object_from_buffer<S1, S2, B>(&self, bucket_name: S1, object_key: S2, buffer: B, options: Option<PutObjectOptions>) -> Result<PutObjectResult>
44 where
45 S1: AsRef<str> + Send,
46 S2: AsRef<str> + Send,
47 B: Into<Vec<u8>> + Send;
48
49 async fn put_object_from_base64<S1, S2, S3>(
54 &self,
55 bucket_name: S1,
56 object_key: S2,
57 base64_string: S3,
58 options: Option<PutObjectOptions>,
59 ) -> Result<PutObjectResult>
60 where
61 S1: AsRef<str> + Send,
62 S2: AsRef<str> + Send,
63 S3: AsRef<str> + Send;
64
65 async fn append_object_from_file<S1, S2, P>(
69 &self,
70 bucket_name: S1,
71 object_key: S2,
72 file_path: P,
73 position: u64,
74 options: Option<AppendObjectOptions>,
75 ) -> Result<AppendObjectResult>
76 where
77 S1: AsRef<str> + Send,
78 S2: AsRef<str> + Send,
79 P: AsRef<Path> + Send;
80
81 async fn append_object_from_buffer<S1, S2, B>(
86 &self,
87 bucket_name: S1,
88 object_key: S2,
89 buffer: B,
90 position: u64,
91 options: Option<AppendObjectOptions>,
92 ) -> Result<AppendObjectResult>
93 where
94 S1: AsRef<str> + Send,
95 S2: AsRef<str> + Send,
96 B: Into<Vec<u8>> + Send;
97
98 async fn append_object_from_base64<S1, S2, S3>(
103 &self,
104 bucket_name: S1,
105 object_key: S2,
106 base64_string: S3,
107 position: u64,
108 options: Option<AppendObjectOptions>,
109 ) -> Result<AppendObjectResult>
110 where
111 S1: AsRef<str> + Send,
112 S2: AsRef<str> + Send,
113 S3: AsRef<str> + Send;
114
115 async fn get_object_to_file<S1, S2, P>(&self, bucket_name: S1, object_key: S2, file_path: P, options: Option<GetObjectOptions>) -> Result<GetObjectResult>
119 where
120 S1: AsRef<str> + Send,
121 S2: AsRef<str> + Send,
122 P: AsRef<Path> + Send;
123
124 async fn get_object_to_buffer<S1, S2>(&self, bucket_name: S1, object_key: S2, options: Option<GetObjectOptions>) -> Result<Vec<u8>>
128 where
129 S1: AsRef<str> + Send,
130 S2: AsRef<str> + Send;
131
132 async fn create_folder<S1, S2>(&self, bucket_name: S1, object_key: S2) -> Result<()>
136 where
137 S1: AsRef<str> + Send,
138 S2: AsRef<str> + Send;
139
140 async fn delete_folder<S1, S2>(&self, bucket_name: S1, object_key: S2) -> Result<()>
144 where
145 S1: AsRef<str> + Send,
146 S2: AsRef<str> + Send;
147
148 async fn get_object_metadata<S1, S2>(&self, bucket_name: S1, object_key: S2, options: Option<GetObjectMetadataOptions>) -> Result<ObjectMetadata>
152 where
153 S1: AsRef<str> + Send,
154 S2: AsRef<str> + Send;
155
156 async fn exists<S1, S2>(&self, bucket_name: S1, object_key: S2, options: Option<GetObjectMetadataOptions>) -> Result<bool>
160 where
161 S1: AsRef<str> + Send,
162 S2: AsRef<str> + Send;
163
164 async fn head_object<S1, S2>(&self, bucket_name: S1, object_key: S2, options: Option<HeadObjectOptions>) -> Result<ObjectMetadata>
168 where
169 S1: AsRef<str> + Send,
170 S2: AsRef<str> + Send;
171
172 async fn copy_object<S1, S2, S3, S4>(
176 &self,
177 source_bucket_name: S1,
178 source_object_key: S2,
179 dest_bucket_name: S3,
180 dest_object_key: S4,
181 options: Option<CopyObjectOptions>,
182 ) -> Result<CopyObjectResult>
183 where
184 S1: AsRef<str> + Send,
185 S2: AsRef<str> + Send,
186 S3: AsRef<str> + Send,
187 S4: AsRef<str> + Send;
188
189 async fn delete_object<S1, S2>(&self, bucket_name: S1, object_key: S2, options: Option<DeleteObjectOptions>) -> Result<DeleteObjectResult>
193 where
194 S1: AsRef<str> + Send,
195 S2: AsRef<str> + Send;
196
197 async fn delete_multiple_objects<'c, S1, S2>(&self, bucket_name: S1, config: DeleteMultipleObjectsConfig<'c, S2>) -> Result<DeleteMultipleObjectsResult>
201 where
202 S1: AsRef<str> + Send,
203 S2: AsRef<str> + Send + Sync;
204
205 async fn restore_object<S1, S2>(&self, bucket_name: S1, object_key: S2, config: RestoreObjectRequest) -> Result<RestoreObjectResult>
209 where
210 S1: AsRef<str> + Send,
211 S2: AsRef<str> + Send;
212
213 async fn clean_restored_object<S1, S2>(&self, bucket_name: S1, object_key: S2) -> Result<()>
217 where
218 S1: AsRef<str> + Send,
219 S2: AsRef<str> + Send;
220}
221
222#[async_trait]
223impl ObjectOperations for Client {
224 async fn put_object_from_file<S1, S2, P>(&self, bucket_name: S1, object_key: S2, file_path: P, options: Option<PutObjectOptions>) -> Result<PutObjectResult>
234 where
235 S1: AsRef<str> + Send,
236 S2: AsRef<str> + Send,
237 P: AsRef<Path> + Send,
238 {
239 let bucket_name = bucket_name.as_ref();
240 let object_key = object_key.as_ref();
241
242 let object_key = object_key.strip_prefix("/").unwrap_or(object_key);
243 let object_key = object_key.strip_suffix("/").unwrap_or(object_key);
244
245 let file_path = file_path.as_ref();
246
247 let with_callback = if let Some(opt) = &options { opt.callback.is_some() } else { false };
248
249 let request = build_put_object_request(bucket_name, object_key, RequestBody::File(file_path.to_path_buf(), None), &options)?;
250
251 let (headers, content) = self.do_request::<String>(request).await?;
252
253 if with_callback {
254 Ok(PutObjectResult::CallbackResponse(content))
255 } else {
256 Ok(PutObjectResult::ApiResponse(headers.into()))
257 }
258 }
259
260 async fn put_object_from_buffer<S1, S2, B>(&self, bucket_name: S1, object_key: S2, buffer: B, options: Option<PutObjectOptions>) -> Result<PutObjectResult>
265 where
266 S1: AsRef<str> + Send,
267 S2: AsRef<str> + Send,
268 B: Into<Vec<u8>> + Send,
269 {
270 let bucket_name = bucket_name.as_ref();
271 let object_key = object_key.as_ref();
272
273 let object_key = object_key.strip_prefix("/").unwrap_or(object_key);
274 let object_key = object_key.strip_suffix("/").unwrap_or(object_key);
275
276 let with_callback = if let Some(opt) = &options { opt.callback.is_some() } else { false };
277
278 let request = build_put_object_request(bucket_name, object_key, RequestBody::Bytes(buffer.into()), &options)?;
279
280 let (headers, content) = self.do_request::<String>(request).await?;
281
282 if with_callback {
283 Ok(PutObjectResult::CallbackResponse(content))
284 } else {
285 Ok(PutObjectResult::ApiResponse(headers.into()))
286 }
287 }
288
289 async fn put_object_from_base64<S1, S2, S3>(
294 &self,
295 bucket_name: S1,
296 object_key: S2,
297 base64_string: S3,
298 options: Option<PutObjectOptions>,
299 ) -> Result<PutObjectResult>
300 where
301 S1: AsRef<str> + Send,
302 S2: AsRef<str> + Send,
303 S3: AsRef<str> + Send,
304 {
305 let data = if let Ok(d) = BASE64_STANDARD.decode(base64_string.as_ref()) {
306 d
307 } else {
308 return Err(Error::Other("Decoding base64 string failed".to_string()));
309 };
310
311 self.put_object_from_buffer(bucket_name, object_key, data, options).await
312 }
313
314 async fn append_object_from_file<S1, S2, P>(
318 &self,
319 bucket_name: S1,
320 object_key: S2,
321 file_path: P,
322 position: u64,
323 options: Option<AppendObjectOptions>,
324 ) -> Result<AppendObjectResult>
325 where
326 S1: AsRef<str> + Send,
327 S2: AsRef<str> + Send,
328 P: AsRef<Path> + Send,
329 {
330 let bucket_name = bucket_name.as_ref();
331 let object_key = object_key.as_ref();
332
333 let object_key = object_key.strip_prefix("/").unwrap_or(object_key);
334 let object_key = object_key.strip_suffix("/").unwrap_or(object_key);
335
336 let file_path = file_path.as_ref();
337
338 let mut request = build_put_object_request(bucket_name, object_key, RequestBody::File(file_path.to_path_buf(), None), &options)?;
339
340 request = request
342 .method(RequestMethod::Post)
343 .add_query("append", "")
344 .add_query("position", position.to_string());
345
346 let (headers, _) = self.do_request::<()>(request).await?;
347
348 Ok(headers.into())
349 }
350
351 async fn append_object_from_buffer<S1, S2, B>(
356 &self,
357 bucket_name: S1,
358 object_key: S2,
359 buffer: B,
360 position: u64,
361 options: Option<AppendObjectOptions>,
362 ) -> Result<AppendObjectResult>
363 where
364 S1: AsRef<str> + Send,
365 S2: AsRef<str> + Send,
366 B: Into<Vec<u8>> + Send,
367 {
368 let bucket_name = bucket_name.as_ref();
369 let object_key = object_key.as_ref();
370
371 let object_key = object_key.strip_prefix("/").unwrap_or(object_key);
372 let object_key = object_key.strip_suffix("/").unwrap_or(object_key);
373
374 let mut request = build_put_object_request(bucket_name, object_key, RequestBody::Bytes(buffer.into()), &options)?;
375
376 request = request
378 .method(RequestMethod::Post)
379 .add_query("append", "")
380 .add_query("position", position.to_string());
381
382 let (headers, _) = self.do_request::<()>(request).await?;
383
384 Ok(headers.into())
385 }
386
387 async fn append_object_from_base64<S1, S2, S3>(
392 &self,
393 bucket_name: S1,
394 object_key: S2,
395 base64_string: S3,
396 position: u64,
397 options: Option<AppendObjectOptions>,
398 ) -> Result<AppendObjectResult>
399 where
400 S1: AsRef<str> + Send,
401 S2: AsRef<str> + Send,
402 S3: AsRef<str> + Send,
403 {
404 let data = if let Ok(d) = BASE64_STANDARD.decode(base64_string.as_ref()) {
405 d
406 } else {
407 return Err(Error::Other("Decoding base64 string failed".to_string()));
408 };
409
410 self.append_object_from_buffer(bucket_name, object_key, data, position, options).await
411 }
412
413 async fn get_object_to_file<S1, S2, P>(&self, bucket_name: S1, object_key: S2, file_path: P, options: Option<GetObjectOptions>) -> Result<GetObjectResult>
419 where
420 S1: AsRef<str> + Send,
421 S2: AsRef<str> + Send,
422 P: AsRef<Path> + Send,
423 {
424 let bucket_name = bucket_name.as_ref();
425 let object_key = object_key.as_ref();
426 let file_path = file_path.as_ref();
427
428 let file_path = if file_path.is_relative() {
429 file_path.canonicalize()?
430 } else {
431 file_path.to_path_buf()
432 };
433
434 if !validate_path(&file_path) {
435 return Err(Error::Other(format!("invalid file path: {:?}", file_path.as_os_str().to_str())));
436 }
437
438 if let Some(parent_path) = file_path.parent() {
440 if !parent_path.exists() {
441 std::fs::create_dir_all(parent_path)?;
442 }
443 }
444
445 let request = build_get_object_request(bucket_name, object_key, &options)?;
446
447 let (_, mut stream) = self.do_request::<ByteStream>(request).await?;
448
449 let mut file = tokio::fs::File::create(&file_path).await?;
450
451 while let Some(chunk) = stream.try_next().await? {
452 file.write_all(&chunk).await?;
453 }
454
455 file.flush().await?;
456
457 Ok(GetObjectResult)
458 }
459
460 async fn get_object_to_buffer<S1, S2>(&self, bucket_name: S1, object_key: S2, options: Option<GetObjectOptions>) -> Result<Vec<u8>>
464 where
465 S1: AsRef<str> + Send,
466 S2: AsRef<str> + Send
467 {
468 let bucket_name = bucket_name.as_ref();
469 let object_key = object_key.as_ref();
470
471 let request = build_get_object_request(bucket_name, object_key, &options)?;
472
473 let (_, mut stream) = self.do_request::<ByteStream>(request).await?;
474
475 let mut buf = Vec::new();
476
477 while let Some(chunk) = stream.try_next().await? {
478 buf.write_all(&chunk).await?;
479 }
480
481 buf.flush().await?;
482
483 Ok(buf)
484 }
485
486 async fn create_folder<S1, S2>(&self, bucket_name: S1, object_key: S2) -> Result<()>
491 where
492 S1: AsRef<str> + Send,
493 S2: AsRef<str> + Send,
494 {
495 let bucket_name = bucket_name.as_ref();
496 let object_key = object_key.as_ref();
497 let object_key = object_key.strip_prefix("/").unwrap_or(object_key);
498 let object_key = if object_key.ends_with("/") {
499 object_key.to_string()
500 } else {
501 format!("{}/", object_key)
502 };
503
504 if !validate_bucket_name(bucket_name) {
505 return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
506 }
507
508 let request = OssRequest::new()
509 .method(RequestMethod::Put)
510 .bucket(bucket_name)
511 .object(object_key)
512 .body(RequestBody::Empty)
513 .content_length(0);
514
515 let _ = self.do_request::<()>(request).await?;
516
517 Ok(())
518 }
519
520 async fn delete_folder<S1, S2>(&self, bucket_name: S1, object_key: S2) -> Result<()>
524 where
525 S1: AsRef<str> + Send,
526 S2: AsRef<str> + Send,
527 {
528 let bucket_name = bucket_name.as_ref();
529 let object_key = object_key.as_ref();
530 let object_key = object_key.strip_prefix("/").unwrap_or(object_key);
531 let object_key = if object_key.ends_with("/") {
532 object_key.to_string()
533 } else {
534 format!("{}/", object_key)
535 };
536
537 if !validate_bucket_name(bucket_name) {
538 return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
539 }
540
541 let request = OssRequest::new().method(RequestMethod::Delete).bucket(bucket_name).object(object_key);
542
543 let _ = self.do_request::<()>(request).await?;
544
545 Ok(())
546 }
547
548 async fn get_object_metadata<S1, S2>(&self, bucket_name: S1, object_key: S2, options: Option<GetObjectMetadataOptions>) -> Result<ObjectMetadata>
552 where
553 S1: AsRef<str> + Send,
554 S2: AsRef<str> + Send,
555 {
556 let bucket_name = bucket_name.as_ref();
557 let object_key = object_key.as_ref();
558
559 if !validate_bucket_name(bucket_name) {
560 return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
561 }
562
563 if !validate_object_key(object_key) {
564 return Err(Error::Other(format!("invalid object key: {}", object_key)));
565 }
566
567 let mut request = OssRequest::new()
568 .method(RequestMethod::Head)
569 .bucket(bucket_name)
570 .object(object_key)
571 .add_query("objectMeta", "");
572
573 if let Some(options) = &options {
574 if let Some(s) = &options.version_id {
575 request = request.add_query("versionId", s);
576 }
577 }
578
579 let (headers, _) = self.do_request::<()>(request).await?;
580 Ok(ObjectMetadata::from(headers))
581 }
582
583 async fn exists<S1, S2>(&self, bucket_name: S1, object_key: S2, options: Option<GetObjectMetadataOptions>) -> Result<bool>
587 where
588 S1: AsRef<str> + Send,
589 S2: AsRef<str> + Send,
590 {
591 match self.get_object_metadata(bucket_name, object_key, options).await {
592 Ok(_) => Ok(true),
593 Err(e) => match e {
594 Error::StatusError(status) if status == StatusCode::NOT_FOUND => Ok(false),
595 _ => Err(e),
596 },
597 }
598 }
599
600 async fn head_object<S1, S2>(&self, bucket_name: S1, object_key: S2, options: Option<HeadObjectOptions>) -> Result<ObjectMetadata>
604 where
605 S1: AsRef<str> + Send,
606 S2: AsRef<str> + Send,
607 {
608 let bucket_name = bucket_name.as_ref();
609 let object_key = object_key.as_ref();
610
611 let request = build_head_object_request(bucket_name, object_key, &options)?;
612
613 let (headers, _) = self.do_request::<()>(request).await?;
614 Ok(ObjectMetadata::from(headers))
615 }
616
617 async fn copy_object<S1, S2, S3, S4>(
621 &self,
622 source_bucket_name: S1,
623 source_object_key: S2,
624 dest_bucket_name: S3,
625 dest_object_key: S4,
626 options: Option<CopyObjectOptions>,
627 ) -> Result<CopyObjectResult>
628 where
629 S1: AsRef<str> + Send,
630 S2: AsRef<str> + Send,
631 S3: AsRef<str> + Send,
632 S4: AsRef<str> + Send,
633 {
634 let request = build_copy_object_request(
635 source_bucket_name.as_ref(),
636 source_object_key.as_ref(),
637 dest_bucket_name.as_ref(),
638 dest_object_key.as_ref(),
639 &options,
640 )?;
641
642 let (_, _) = self.do_request::<()>(request).await?;
643
644 Ok(CopyObjectResult)
645 }
646
647 async fn delete_object<S1, S2>(&self, bucket_name: S1, object_key: S2, options: Option<DeleteObjectOptions>) -> Result<DeleteObjectResult>
651 where
652 S1: AsRef<str> + Send,
653 S2: AsRef<str> + Send,
654 {
655 let bucket_name = bucket_name.as_ref();
656 let object_key = object_key.as_ref();
657
658 if !validate_bucket_name(bucket_name) {
659 return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
660 }
661
662 if !validate_object_key(object_key) {
663 return Err(Error::Other(format!("invalid object key: {}", object_key)));
664 }
665
666 let mut request = OssRequest::new().method(RequestMethod::Delete).bucket(bucket_name).object(object_key);
667
668 if let Some(options) = options {
669 if let Some(s) = options.version_id {
670 request = request.add_query("versionId", s);
671 }
672 }
673
674 let _ = self.do_request::<()>(request).await?;
675
676 Ok(DeleteObjectResult)
677 }
678
679 async fn delete_multiple_objects<'c, S1, S2>(&self, bucket_name: S1, config: DeleteMultipleObjectsConfig<'c, S2>) -> Result<DeleteMultipleObjectsResult>
683 where
684 S1: AsRef<str> + Send,
685 S2: AsRef<str> + Send + Sync,
686 {
687 let bucket_name = bucket_name.as_ref();
688
689 let request = build_delete_multiple_objects_request(bucket_name, config)?;
690
691 let (_, content) = self.do_request::<String>(request).await?;
692
693 DeleteMultipleObjectsResult::from_xml(&content)
694 }
695
696 async fn restore_object<S1, S2>(&self, bucket_name: S1, object_key: S2, config: RestoreObjectRequest) -> Result<RestoreObjectResult>
700 where
701 S1: AsRef<str> + Send,
702 S2: AsRef<str> + Send,
703 {
704 let request = build_restore_object_request(bucket_name.as_ref(), object_key.as_ref(), config)?;
705 let (headers, _) = self.do_request::<()>(request).await?;
706 Ok(headers.into())
707 }
708
709 async fn clean_restored_object<S1, S2>(&self, bucket_name: S1, object_key: S2) -> Result<()>
713 where
714 S1: AsRef<str> + Send,
715 S2: AsRef<str> + Send,
716 {
717 let bucket_name = bucket_name.as_ref();
718 let object_key = object_key.as_ref();
719
720 if !validate_bucket_name(bucket_name) {
721 return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
722 }
723
724 if !validate_object_key(object_key) {
725 return Err(Error::Other(format!("invalid object key: {}", object_key)));
726 }
727
728 let request = OssRequest::new()
729 .method(RequestMethod::Post)
730 .bucket(bucket_name)
731 .object(object_key)
732 .add_query("cleanRestoredObject", "");
733
734 let _ = self.do_request::<()>(request).await?;
735
736 Ok(())
737 }
738}
739
740#[cfg(test)]
741mod test_object_async {
742 use std::{collections::HashMap, sync::Once};
743
744 use base64::{prelude::BASE64_STANDARD, Engine};
745 use uuid::Uuid;
746
747 use crate::{
748 common::{ObjectType, StorageClass},
749 object::ObjectOperations,
750 object_common::{
751 CallbackBodyParameter, CallbackBuilder, DeleteMultipleObjectsConfig, GetObjectOptionsBuilder, PutObjectApiResponse, PutObjectOptions,
752 PutObjectOptionsBuilder, PutObjectResult, RestoreObjectRequest,
753 },
754 util, Client,
755 };
756
757 static INIT: Once = Once::new();
758
759 fn setup() {
760 INIT.call_once(|| {
761 simple_logger::init_with_level(log::Level::Debug).unwrap();
762 dotenvy::dotenv().unwrap();
763 });
764 }
765
766 #[tokio::test]
767 async fn test_upload_file_1_async() {
768 log::debug!("test upload file with no options");
769 setup();
770
771 let client = Client::from_env();
772
773 let object = format!("rust-sdk-test/{}.pdf", uuid::Uuid::new_v4());
774
775 let result = client
776 .put_object_from_file("yuanyq", &object, "/home/yuanyq/Downloads/test-pdf-output.pdf", None)
777 .await;
778
779 log::debug!("{:?}", result);
780
781 assert!(result.is_ok());
782
783 let ret = result.unwrap();
784 if let PutObjectResult::ApiResponse(PutObjectApiResponse {
785 request_id: _,
786 etag: _,
787 content_md5,
788 hash_crc64ecma: _,
789 version_id: _,
790 }) = ret
791 {
792 assert_eq!("u3j3ZJAf4d4uOHz4BNcXiw==", content_md5);
793 } else {
794 panic!("md5 match failed");
795 }
796
797 assert!(client.exists("yuanyq", &object, None).await.unwrap());
798
799 let _ = client.delete_object("yuanyq", &object, None).await;
800
801 let response = client.exists("yuanyq", &object, None).await;
802 log::debug!("{:#?}", response);
803
804 assert!(!response.unwrap());
805 }
806
807 #[tokio::test]
808 async fn test_upload_file_2_async() {
809 log::debug!("test upload file with meta data set");
810 setup();
811
812 let client = Client::from_env();
813
814 let options = PutObjectOptions {
815 tags: HashMap::from([("purpose".to_string(), "test".to_string()), ("where".to_string(), "beijing".to_string())]),
816
817 metadata: HashMap::from([
818 ("x-oss-meta-who".to_string(), "yuanyu".to_string()),
819 ("x-oss-meta-when".to_string(), "now or later".to_string()),
820 ]),
821
822 ..Default::default()
823 };
824
825 let object = format!("rust-sdk-test/{}.pdf", uuid::Uuid::new_v4());
826
827 let result = client
828 .put_object_from_file("yuanyq", &object, "/home/yuanyq/Downloads/云教材发布与管理系统-用户手册.pdf", Some(options))
829 .await;
830
831 log::debug!("{:?}", result);
832
833 assert!(result.is_ok());
834
835 let ret = result.unwrap();
836 if let PutObjectResult::ApiResponse(PutObjectApiResponse {
837 request_id: _,
838 etag: _,
839 content_md5,
840 hash_crc64ecma: _,
841 version_id: _,
842 }) = ret
843 {
844 assert_eq!("m6YFnp+xXeBIXkiWnqFi9w==", content_md5);
845 }
846
847 let _ = client.delete_object("yuanyq", &object, None).await;
848
849 let response = client.exists("yuanyq", &object, None).await;
850 log::debug!("{:#?}", response);
851
852 assert!(!response.unwrap());
853 }
854
855 #[tokio::test]
857 async fn test_upload_file_3_async() {
858 log::debug!("test upload file and set to Archive storage class");
859 setup();
860
861 let client = Client::from_env();
862
863 let options = PutObjectOptions {
864 storage_class: Some(StorageClass::Archive),
865 ..Default::default()
866 };
867
868 let object = format!("rust-sdk-test/{}.mp4", uuid::Uuid::new_v4());
869
870 let result = client
871 .put_object_from_file("yuanyq", &object, "/home/yuanyq/Pictures/demo.mp4", Some(options))
872 .await;
873
874 log::debug!("{:?}", result);
875
876 assert!(result.is_ok());
877
878 let ret = result.unwrap();
879 if let PutObjectResult::ApiResponse(PutObjectApiResponse {
880 request_id: _,
881 etag: _,
882 content_md5,
883 hash_crc64ecma: _,
884 version_id: _,
885 }) = ret
886 {
887 assert_eq!("8TAE7tQlHGArvhVzfooeyw==", content_md5);
888 }
889
890 let _ = client.delete_object("yuanyq", &object, None).await;
891
892 let response = client.exists("yuanyq", &object, None).await;
893 log::debug!("{:#?}", response);
894
895 assert!(!response.unwrap());
896 }
897
898 #[tokio::test]
899 async fn test_create_folder_1_async() {
900 log::debug!("test create folder");
901 setup();
902
903 let client = Client::from_env();
904
905 let result = client.create_folder("yuanyq", "rust-sdk-test/test-folder/").await;
906
907 log::debug!("{:?}", result);
908
909 assert!(result.is_ok())
910 }
911
912 #[tokio::test]
913 async fn test_delete_folder_async() {
914 log::debug!("test delete folder");
915 setup();
916
917 let client = Client::from_env();
918
919 let bucket = "yuanyq";
920 let object = format!("rust-sdk-test/{}/", Uuid::new_v4());
921
922 client.create_folder(bucket, &object).await.unwrap();
923
924 let response = client.delete_folder(bucket, &object).await;
925 assert!(response.is_ok());
926 }
927
928 #[tokio::test]
931 async fn test_download_file_1_async() {
932 log::debug!("test download object to local file");
933 setup();
934 let client = Client::from_env();
935
936 let output_file = format!("/home/yuanyq/Downloads/ali-oss-rs-test/{}.zip", Uuid::new_v4());
937
938 let result = client.get_object_to_file("yuanyq", "rust-sdk-test/katex.zip", &output_file, None).await;
939
940 assert!(result.is_ok());
941
942 let md5_hash = util::file_md5(&output_file);
943 assert_eq!("pIPky6/KtraaoNqF76ia8Q==", md5_hash);
944
945 std::fs::remove_file(&output_file).unwrap();
946 log::debug!("local file {} is deleted", output_file);
947 }
948
949 #[tokio::test]
951 async fn test_download_file_2_async() {
952 log::debug!("test download file with range header");
953 setup();
954 let client = Client::from_env();
955
956 let output_file = format!("/home/yuanyq/Downloads/ali-oss-rs-test/{}.zip.1", Uuid::new_v4());
957
958 let options = GetObjectOptionsBuilder::new().range("bytes=0-499").build();
959
960 let result = client
961 .get_object_to_file("yuanyq", "rust-sdk-test/katex.zip", &output_file, Some(options))
962 .await;
963
964 assert!(result.is_ok());
965
966 let file_meta = std::fs::metadata(&output_file).unwrap();
967
968 assert_eq!(500, file_meta.len());
969
970 std::fs::remove_file(&output_file).unwrap();
971 }
972
973 #[tokio::test]
975 async fn test_download_file_3_async() {
976 setup();
977 let client = Client::from_env();
978
979 let invalid_files = [
980 "/home/yuanyq/Downloads/ali-oss-rs-test>/katex.zip.1",
981 "/home/yuanyq/Downloads/ali-oss-rs-test|/katex;.zip.1",
982 "/home/yuanyq/Downloads/ali-oss-rs-test\0/katex.zip.1",
983 ];
984
985 for output_file in invalid_files {
986 let options = GetObjectOptionsBuilder::new().range("bytes=0-499").build();
987
988 let result = client.get_object_to_file("yuanyq", "rust-sdk-test/katex.zip", output_file, Some(options)).await;
989
990 assert!(result.is_err());
991
992 log::debug!("{}", result.unwrap_err());
993 }
994 }
995
996 #[tokio::test]
997 async fn test_get_object_to_buf_async() {
998 setup();
999 let client = Client::from_env();
1000 let bucket_name = "mi-dev-public";
1001 let object_key = "quiz_template.xls";
1002 let buf = client.get_object_to_buffer(bucket_name, object_key, None).await.unwrap();
1004 let d = md5::compute(&buf).to_vec();
1005 assert_eq!(hex::encode(&d), "9b98f68671ec239958e58a6813960ab5");
1006 }
1007
1008 #[tokio::test]
1009 async fn test_get_object_metadata_async() {
1010 setup();
1011 let client = Client::from_env();
1012
1013 let result = client
1014 .get_object_metadata("yuanyq", "rust-sdk-test/Oracle_VirtualBox_Extension_Pack-7.1.4.vbox-extpack", None)
1015 .await;
1016
1017 assert!(result.is_ok());
1018
1019 let meta = result.unwrap();
1020 log::debug!("{:?}", meta);
1021 assert_eq!(22966826, meta.content_length);
1022 assert_eq!("B752E1A13502E231AC4AA0E1D91F887C", meta.etag);
1023 assert_eq!(Some(7873641174252289613u64), meta.hash_crc64ecma);
1024 assert_eq!(Some("Tue, 18 Feb 2025 15:03:23 GMT".to_string()), meta.last_modified);
1025 }
1026
1027 #[tokio::test]
1028 async fn test_head_object_async() {
1029 setup();
1030 let client = Client::from_env();
1031
1032 let result = client
1033 .head_object("yuanyq", "rust-sdk-test/Oracle_VirtualBox_Extension_Pack-7.1.4.vbox-extpack", None)
1034 .await;
1035
1036 assert!(result.is_ok());
1037
1038 let meta = result.unwrap();
1039 log::debug!("{:#?}", meta);
1040 assert_eq!(22966826, meta.content_length);
1041 assert_eq!("B752E1A13502E231AC4AA0E1D91F887C", meta.etag);
1042 assert_eq!(Some(7873641174252289613), meta.hash_crc64ecma);
1043 assert_eq!(Some("Tue, 18 Feb 2025 15:03:23 GMT".to_string()), meta.last_modified);
1044 assert_eq!(Some(ObjectType::Normal), meta.object_type);
1045 assert_eq!(Some(StorageClass::Standard), meta.storage_class);
1046 }
1047
1048 #[tokio::test]
1050 async fn test_copy_object_1_async() {
1051 log::debug!("test copy object within the same bucket");
1052 setup();
1053 let client = Client::from_env();
1054
1055 let source_bucket = "yuanyq";
1056 let source_object = "rust-sdk-test/katex.zip";
1057
1058 let dest_bucket = "yuanyq";
1059 let dest_object = format!("rust-sdk-test/katex-{}.zip", Uuid::new_v4());
1060
1061 let ret = client.copy_object(source_bucket, source_object, dest_bucket, &dest_object, None).await;
1062
1063 assert!(ret.is_ok());
1064
1065 let source_meta = client.get_object_metadata(source_bucket, source_object, None).await.unwrap();
1066 let dest_meta = client.get_object_metadata(dest_bucket, &dest_object, None).await.unwrap();
1067
1068 assert_eq!(source_meta.etag, dest_meta.etag);
1069
1070 client.delete_object("yuanyq", &dest_object, None).await.unwrap();
1071 }
1072
1073 #[tokio::test]
1075 async fn test_copy_object_2_async() {
1076 log::debug!("test copy object accross buckets");
1077 setup();
1078 let client = Client::from_env();
1079
1080 let source_bucket = "yuanyq";
1081 let source_object = "rust-sdk-test/katex.zip";
1082
1083 let dest_bucket = "yuanyq-2";
1084 let dest_object = format!("rust-sdk-test/katex-{}.zip", Uuid::new_v4());
1085
1086 let ret = client.copy_object(source_bucket, source_object, dest_bucket, &dest_object, None).await;
1087
1088 assert!(ret.is_ok());
1089
1090 let source_meta = client.get_object_metadata(source_bucket, source_object, None).await.unwrap();
1091 let dest_meta = client.get_object_metadata(dest_bucket, &dest_object, None).await.unwrap();
1092
1093 assert_eq!(source_meta.etag, dest_meta.etag);
1094
1095 client.delete_object(dest_bucket, &dest_object, None).await.unwrap();
1096 }
1097
1098 #[tokio::test]
1099 async fn test_create_object_from_buffer_async() {
1100 log::debug!("test create object from buffer");
1101
1102 setup();
1103 let client = Client::from_env();
1104
1105 let bucket = "yuanyq";
1106 let object = format!("rust-sdk-test/{}.jpg", Uuid::new_v4());
1107
1108 let options = PutObjectOptionsBuilder::new().mime_type("image/jpeg").build();
1109
1110 let buffer = std::fs::read("/home/yuanyq/Pictures/f69e41cb1642c3360bd5bb6e700a0ecb.jpeg").unwrap();
1111
1112 let md5 = "1ziAOyOVKo5/xAIvbUEQJA==";
1113
1114 let ret = client.put_object_from_buffer(bucket, &object, buffer, Some(options)).await;
1115
1116 log::debug!("{:?}", ret);
1117
1118 assert!(ret.is_ok());
1119
1120 let meta = client.head_object(bucket, &object, None).await.unwrap();
1121 assert_eq!(Some(md5.to_string()), meta.content_md5);
1122
1123 client.delete_object(bucket, &object, None).await.unwrap();
1124 }
1125
1126 #[tokio::test]
1127 async fn test_create_object_from_base64_async() {
1128 log::debug!("test create object from base64 string");
1129 setup();
1130 let client = Client::from_env();
1131
1132 let bucket = "yuanyq";
1133 let object = format!("rust-sdk-test/{}.jpg", Uuid::new_v4());
1134
1135 let options = PutObjectOptionsBuilder::new().mime_type("image/jpeg").build();
1136
1137 let buffer = std::fs::read("/home/yuanyq/Pictures/f69e41cb1642c3360bd5bb6e700a0ecb.jpeg").unwrap();
1138 let base64 = BASE64_STANDARD.encode(&buffer);
1139 let md5 = "1ziAOyOVKo5/xAIvbUEQJA==";
1140
1141 let ret = client.put_object_from_base64(bucket, &object, base64, Some(options)).await;
1142
1143 assert!(ret.is_ok());
1144
1145 let meta = client.head_object(bucket, &object, None).await.unwrap();
1146 assert_eq!(Some(md5.to_string()), meta.content_md5);
1147
1148 client.delete_object(bucket, &object, None).await.unwrap();
1149 }
1150
1151 #[tokio::test]
1152 async fn test_append_object_1_async() {
1153 log::debug!("test append object from file");
1154 setup();
1155 let client = Client::from_env();
1156
1157 let bucket = "yuanyq";
1158 let object = format!("rust-sdk-test/{}.jpg", Uuid::new_v4());
1159
1160 let file1 = "/home/yuanyq/Pictures/test-image-part-1.data";
1161 let file2 = "/home/yuanyq/Pictures/test-image-part-2.data";
1162 let file3 = "/home/yuanyq/Pictures/test-image-part-3.data";
1163
1164 let ret1 = client.append_object_from_file(bucket, &object, file1, 0, None).await;
1165
1166 assert!(ret1.is_ok());
1167
1168 let next_pos = ret1.unwrap().next_append_position;
1169 assert_eq!(61929, next_pos);
1170
1171 let ret2 = client.append_object_from_file(bucket, &object, file2, next_pos, None).await;
1172 assert!(ret2.is_ok());
1173
1174 let next_pos = ret2.unwrap().next_append_position;
1175 assert_eq!(61929 * 2, next_pos);
1176
1177 let ret3 = client.append_object_from_file(bucket, &object, file3, next_pos, None).await;
1178 assert!(ret3.is_ok());
1179
1180 let meta = client.head_object(bucket, &object, None).await;
1181
1182 assert_eq!(185786, meta.unwrap().content_length);
1183
1184 client.delete_object(bucket, &object, None).await.unwrap();
1185 }
1186
1187 #[tokio::test]
1188 async fn test_append_object_from_buffer_async() {
1189 log::debug!("format append object from buffer");
1190 setup();
1191 let client = Client::from_env();
1192
1193 let bucket = "yuanyq";
1194 let object = format!("rust-sdk-test/{}.jpg", Uuid::new_v4());
1195
1196 let file1 = "/home/yuanyq/Pictures/test-image-part-1.data";
1197 let file2 = "/home/yuanyq/Pictures/test-image-part-2.data";
1198 let file3 = "/home/yuanyq/Pictures/test-image-part-3.data";
1199
1200 let buffer1 = std::fs::read(file1).unwrap();
1201 let buffer2 = std::fs::read(file2).unwrap();
1202 let buffer3 = std::fs::read(file3).unwrap();
1203
1204 let ret1 = client.append_object_from_buffer(bucket, &object, buffer1, 0, None).await;
1205 assert!(ret1.is_ok());
1206
1207 let next_pos = ret1.unwrap().next_append_position;
1208 assert_eq!(61929, next_pos);
1209
1210 let ret2 = client.append_object_from_buffer(bucket, &object, buffer2, next_pos, None).await;
1211 assert!(ret2.is_ok());
1212
1213 let next_pos = ret2.unwrap().next_append_position;
1214 assert_eq!(61929 * 2, next_pos);
1215
1216 let ret3 = client.append_object_from_buffer(bucket, &object, buffer3, next_pos, None).await;
1217 assert!(ret3.is_ok());
1218
1219 let meta = client.head_object(bucket, &object, None).await;
1220 assert_eq!(185786, meta.unwrap().content_length);
1221
1222 client.delete_object(bucket, &object, None).await.unwrap();
1223 }
1224
1225 #[tokio::test]
1226 async fn test_append_object_from_base64_async() {
1227 log::debug!("test append object from base64 string");
1228 setup();
1229 let client = Client::from_env();
1230
1231 let bucket = "yuanyq";
1232 let object = format!("rust-sdk-test/{}.jpg", Uuid::new_v4());
1233
1234 let file1 = "/home/yuanyq/Pictures/test-image-part-1.data";
1235 let file2 = "/home/yuanyq/Pictures/test-image-part-2.data";
1236 let file3 = "/home/yuanyq/Pictures/test-image-part-3.data";
1237
1238 let buffer1 = std::fs::read(file1).unwrap();
1239 let buffer2 = std::fs::read(file2).unwrap();
1240 let buffer3 = std::fs::read(file3).unwrap();
1241
1242 let s1 = BASE64_STANDARD.encode(buffer1);
1243 let s2 = BASE64_STANDARD.encode(buffer2);
1244 let s3 = BASE64_STANDARD.encode(buffer3);
1245
1246 let ret1 = client.append_object_from_base64(bucket, &object, s1, 0, None).await;
1247 assert!(ret1.is_ok());
1248
1249 let next_pos = ret1.unwrap().next_append_position;
1250 assert_eq!(61929, next_pos);
1251
1252 let ret2 = client.append_object_from_base64(bucket, &object, s2, next_pos, None).await;
1253 assert!(ret2.is_ok());
1254
1255 let next_pos = ret2.unwrap().next_append_position;
1256 assert_eq!(61929 * 2, next_pos);
1257
1258 let ret3 = client.append_object_from_base64(bucket, &object, s3, next_pos, None).await;
1259 assert!(ret3.is_ok());
1260
1261 let meta = client.head_object(bucket, &object, None).await;
1262 assert_eq!(185786, meta.unwrap().content_length);
1263
1264 client.delete_object(bucket, &object, None).await.unwrap();
1265 }
1266
1267 #[tokio::test]
1268 async fn test_delete_multiple_objects_async() {
1269 setup();
1270 let client = Client::from_env();
1271
1272 let local_files = [
1273 "/home/yuanyq/Pictures/01-01.jpg",
1274 "/home/yuanyq/Pictures/01-02.jpg",
1275 "/home/yuanyq/Pictures/01-03.png",
1276 "/home/yuanyq/Pictures/01-04.jpg",
1277 "/home/yuanyq/Pictures/01-05.png",
1278 ];
1279
1280 let keys = [
1281 format!("rust-sdk-test/{}.jpg", Uuid::new_v4()),
1282 format!("rust-sdk-test/{}.jpg", Uuid::new_v4()),
1283 format!("rust-sdk-test/{}.png", Uuid::new_v4()),
1284 format!("rust-sdk-test/{}.jpg", Uuid::new_v4()),
1285 format!("rust-sdk-test/{}.png", Uuid::new_v4()),
1286 ];
1287
1288 for i in 0..5 {
1289 let file = local_files[i];
1290 let object = &keys[i];
1291 client.put_object_from_file("yuanyq", object, file, None).await.unwrap();
1292 }
1293
1294 let response = client.delete_multiple_objects("yuanyq", DeleteMultipleObjectsConfig::FromKeys(&keys)).await;
1295
1296 log::debug!("{:#?}", response);
1297
1298 assert!(response.is_ok());
1299
1300 let result = response.unwrap();
1301 assert_eq!(5, result.items.len());
1302
1303 for s in keys {
1304 assert!(result.items.iter().any(|item| item.key == s));
1305 }
1306 }
1307
1308 #[tokio::test]
1309 async fn test_put_object_from_file_with_callback_async() {
1310 setup();
1311 let client = Client::from_env();
1312
1313 let bucket = "yuanyq".to_string();
1314 let object = format!("rust-sdk-test/{}.webp", Uuid::new_v4());
1315 let file = "/home/yuanyq/Pictures/test-1.webp".to_string();
1316
1317 let cb = CallbackBuilder::new("https://your-callback.domain.com/oss-callback-test.php")
1318 .body_parameter(CallbackBodyParameter::OssBucket("the_bucket"))
1319 .body_parameter(CallbackBodyParameter::OssObject("the_object_key"))
1320 .body_parameter(CallbackBodyParameter::OssETag("the_etag"))
1321 .body_parameter(CallbackBodyParameter::OssSize("the_size"))
1322 .body_parameter(CallbackBodyParameter::OssCrc64("the_crc"))
1323 .body_parameter(CallbackBodyParameter::OssClientIp("the_client_ip"))
1324 .body_parameter(CallbackBodyParameter::OssContentMd5("the_content_md5"))
1325 .body_parameter(CallbackBodyParameter::OssMimeType("the_mime_type"))
1326 .body_parameter(CallbackBodyParameter::OssImageWidth("the_image_width"))
1327 .body_parameter(CallbackBodyParameter::OssImageHeight("the_image_height"))
1328 .body_parameter(CallbackBodyParameter::OssImageFormat("the_image_format"))
1329 .body_parameter(CallbackBodyParameter::Custom("my-key", "my-prop", "hello world".to_string()))
1330 .body_parameter(CallbackBodyParameter::Constant("my-key-constant", "the-value"))
1331 .body_parameter(CallbackBodyParameter::Literal("k1".to_string(), "${x:v1}".to_string()))
1332 .custom_variable("v1", "this is value of v1")
1333 .build();
1334
1335 let options = PutObjectOptionsBuilder::new().callback(cb).build();
1336
1337 let response = client.put_object_from_file(bucket, &object, &file, Some(options)).await;
1338 assert!(response.is_ok());
1339
1340 let ret = response.unwrap();
1341
1342 log::debug!("{:#?}", ret);
1343
1344 client.delete_object("yuanyq", &object, None).await.unwrap();
1345 }
1346
1347 #[tokio::test]
1348 async fn test_put_object_from_buffer_with_callback_async() {
1349 log::debug!("test put object from buffer with callback");
1350 setup();
1351 let client = Client::from_env();
1352
1353 let bucket = "yuanyq".to_string();
1354 let object = format!("rust-sdk-test/{}.webp", Uuid::new_v4());
1355 let file = "/home/yuanyq/Pictures/test-1.webp".to_string();
1356
1357 let data = std::fs::read(&file).unwrap();
1358
1359 let cb = CallbackBuilder::new("https://your-callback.domain.com/oss-callback-test.php")
1360 .body_parameter(CallbackBodyParameter::OssBucket("the_bucket"))
1361 .body_parameter(CallbackBodyParameter::OssObject("the_object_key"))
1362 .body_parameter(CallbackBodyParameter::OssETag("the_etag"))
1363 .body_parameter(CallbackBodyParameter::OssSize("the_size"))
1364 .body_parameter(CallbackBodyParameter::OssCrc64("the_crc"))
1365 .body_parameter(CallbackBodyParameter::OssClientIp("the_client_ip"))
1366 .body_parameter(CallbackBodyParameter::OssContentMd5("the_content_md5"))
1367 .body_parameter(CallbackBodyParameter::OssMimeType("the_mime_type"))
1368 .body_parameter(CallbackBodyParameter::OssImageWidth("the_image_width"))
1369 .body_parameter(CallbackBodyParameter::OssImageHeight("the_image_height"))
1370 .body_parameter(CallbackBodyParameter::OssImageFormat("the_image_format"))
1371 .body_parameter(CallbackBodyParameter::Custom("my-key", "my-prop", "hello world".to_string()))
1372 .body_parameter(CallbackBodyParameter::Constant("my-key-constant", "the-value"))
1373 .body_parameter(CallbackBodyParameter::Literal("k1".to_string(), "${x:v1}".to_string()))
1374 .custom_variable("v1", "this is value of v1")
1375 .build();
1376
1377 let options = PutObjectOptionsBuilder::new().callback(cb).build();
1378
1379 let response = client.put_object_from_buffer(bucket, &object, data, Some(options)).await;
1380 assert!(response.is_ok());
1381
1382 let ret = response.unwrap();
1383
1384 log::debug!("{:#?}", ret);
1385
1386 client.delete_object("yuanyq", &object, None).await.unwrap();
1387 }
1388
1389 #[tokio::test]
1390 async fn test_restore_object_async() {
1391 log::debug!("test restore archived object");
1392 setup();
1393 let client = Client::from_env();
1394
1395 let bucket = "yuanyq".to_string();
1396 let file = "/home/yuanyq/Pictures/test-1.webp";
1397 let object = format!("rust-sdk-test/{}.webp", Uuid::new_v4());
1398
1399 let options = PutObjectOptionsBuilder::new().storage_class(StorageClass::Archive).build();
1400 client.put_object_from_file(&bucket, &object, file, Some(options)).await.unwrap();
1401
1402 let response = client
1403 .restore_object(&bucket, object, RestoreObjectRequest { days: 2, ..Default::default() })
1404 .await;
1405
1406 assert!(response.is_ok());
1407
1408 let ret = response.unwrap();
1409 log::debug!("{:#?}", ret);
1410 }
1411
1412 #[tokio::test]
1413 async fn test_put_object_from_file_with_options_async() {
1414 setup();
1415 let client = Client::from_env();
1416
1417 let bucket = "yuanyq".to_string();
1418 let object = format!("rust-sdk-test/{}.webp", Uuid::new_v4());
1419 let file = "/home/yuanyq/Pictures/test-5.webp";
1420
1421 let options = PutObjectOptionsBuilder::new()
1422 .metadata("x-oss-meta-who", "me")
1423 .metadata("x-oss-meta-user-id", "123456")
1424 .tag("source", "ubuntu")
1425 .tag("purpose", "test rust sdk")
1426 .build();
1427
1428 let response = client.put_object_from_file(&bucket, &object, file, Some(options)).await;
1429 assert!(response.is_ok());
1430
1431 let result = response.unwrap();
1432 if let PutObjectResult::ApiResponse(PutObjectApiResponse {
1433 request_id: _,
1434 etag: _,
1435 content_md5,
1436 hash_crc64ecma: _,
1437 version_id: _,
1438 }) = result
1439 {
1440 assert_eq!("gbqycESJX3i9b8aB/3Y7ZQ==", content_md5);
1441 } else {
1442 panic!("file md5 validation failed");
1443 }
1444
1445 client.delete_object(&bucket, &object, None).await.unwrap();
1446 }
1447}