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