Skip to main content

ali_oss_rs/
object.rs

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    /// Uploads a file to a specified bucket and object key.
25    ///
26    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
27    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    /// Create an object from buffer. If you are going to upload a large file, it is recommended to use `upload_file` instead.
40    /// And, it is recommended to set `mime_type` in `options`
41    ///
42    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
43    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    /// Create an object from base64 string.
50    /// And, it is recommended to set `mime_type` in `options`
51    ///
52    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
53    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    /// Append object.
66    ///
67    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/appendobject>
68    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    /// Append object from buffer. suitable for small size content
82    /// And, it is recommended to set `mime_type` in `options`
83    ///
84    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
85    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    /// Append object from base64 string. suitable for small size content
99    /// And, it is recommended to set `mime_type` in `options`
100    ///
101    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
102    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    /// Download object to local file
116    ///
117    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/getobject>
118    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    /// Get object content into memory (bytes array).
125    ///
126    /// Large files can consume significant memory, exercise caution when using this function.
127    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    /// Create a "folder"
133    ///
134    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
135    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    /// Delete a "folder". if the folder contains any object, it will not be deleted
141    ///
142    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/deleteobject>
143    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    /// Get object metadata
149    ///
150    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/getobjectmeta>
151    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    /// Check if the object exists or not using get object metadata
157    ///
158    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/getobjectmeta>
159    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    /// Head object
165    ///
166    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/headobject>
167    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    /// Copy files (Objects) between the same or different Buckets within the same region.
173    ///
174    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/copyobject>
175    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    /// Delete an object
190    ///
191    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/deleteobject>
192    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    /// Delete multiple objects
198    ///
199    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/deletemultipleobjects>
200    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    /// Restore object
206    ///
207    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/restoreobject>
208    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    /// Clean retored object
214    ///
215    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/cleanrestoredobject>
216    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    /// The `object_key` constraints:
225    ///
226    /// - length between [1, 1023]
227    /// - must NOT starts or ends with `/` or `\`. e.g. `path/to/subfolder/some-file.txt`
228    /// - the `file_path` specify full path to the file to be uploaded
229    /// - the file must exist and must be readable
230    /// - file length less than 5GB
231    ///
232    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
233    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    /// Create an object from buffer. If you are going to upload a large file, it is recommended to use `upload_file` instead.
261    /// And, it is recommended to set `mime_type` in `options`
262    ///
263    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
264    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    /// Create an object from base64 string.
290    /// And, it is recommended to set `mime_type` in `options`
291    ///
292    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
293    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    /// Append object.
315    ///
316    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/appendobject>
317    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        // alter the request method and add append object query parameters
341        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    /// Append object from buffer. suitable for small size content
352    /// And, it is recommended to set `mime_type` in `options`
353    ///
354    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
355    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        // alter the request method and add append object query parameters
377        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    /// Append object from base64 string. suitable for small size content
388    /// And, it is recommended to set `mime_type` in `options`
389    ///
390    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
391    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    /// Download oss object to local file.
414    /// `file_path` is the full file path to save.
415    /// If the `file_path` parent path does not exist, it will be created
416    ///
417    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/getobject>
418    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        // check parent path
439        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    /// Get object content into memory (bytes array).
461    ///
462    /// Large files can consume significant memory, exercise caution when using this function.
463    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    /// Create a "folder".
487    /// The `object_key` must ends with `/`
488    ///
489    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
490    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    /// Delete a "folder". if the folder contains any object, it will not be deleted
521    ///
522    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/deleteobject>
523    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    /// Get object metadata.
549    ///
550    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/getobjectmeta>
551    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    /// Check if the object exists or not using get object metadata
584    ///
585    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/getobjectmeta>
586    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    /// Get more detail object metadata
601    ///
602    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/headobject>
603    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    /// Copy files (Objects) between the same or different Buckets within the same region.
618    ///
619    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/copyobject>
620    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    /// Delete an object
648    ///
649    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/deleteobject>
650    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    /// Delete multiple objects
680    ///
681    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/deletemultipleobjects>
682    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    /// Restore object
697    ///
698    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/restoreobject>
699    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    /// Clean retored object
710    ///
711    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/cleanrestoredobject>
712    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    /// Test upload file with non-default storage class
856    #[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    /// Download full file content to local file
929    /// with no options
930    #[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    /// Download range of file
950    #[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    /// Test invalid output file name
974    #[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        // 9b98f68671ec239958e58a6813960ab5
1003        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    /// Copy object in same bucket
1049    #[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    /// Copy object across buckets
1074    #[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}