Skip to main content

ali_oss_rs/blocking/
object.rs

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    /// Uploads a file to a specified bucket and object key.
23    ///
24    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
25    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    /// Create an object from buffer. If you are going to upload a large file, it is recommended to use `upload_file` instead.
32    /// And, it is recommended to set `mime_type` in `options`
33    ///
34    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
35    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    /// Create an object from base64 string.
42    /// And, it is recommended to set `mime_type` in `options`
43    ///
44    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
45    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    /// Append object.
58    ///
59    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/appendobject>
60    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    /// Append object from buffer. suitable for small size content
74    /// And, it is recommended to set `mime_type` in `options`
75    ///
76    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
77    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    /// Append object from base64 string. suitable for small size content
91    /// And, it is recommended to set `mime_type` in `options`
92    ///
93    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
94    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    /// Uploads a file to a specified bucket and object key.
108    ///
109    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/getobject>
110    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    /// Get object content into memory (bytes array).
117    ///
118    /// Large files can consume significant memory, exercise caution when using this function.
119    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    /// Create a "folder"
125    ///
126    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
127    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    /// Delete a "folder". if the folder contains any object, it will not be deleted
133    ///
134    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/deleteobject>
135    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    /// Get object metadata
141    ///
142    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/getobjectmeta>
143    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    /// Check if the object exists or not using get object metadata
149    ///
150    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/getobjectmeta>
151    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    /// Head object
157    ///
158    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/headobject>
159    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    /// Copy files (Objects) between the same or different Buckets within the same region.
165    ///
166    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/copyobject>
167    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    /// Delete an object
182    ///
183    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/deleteobject>
184    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    /// Delete multiple objects
190    ///
191    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/deletemultipleobjects>
192    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    /// Restore object
198    ///
199    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/restoreobject>
200    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    /// Clean retored object
206    ///
207    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/cleanrestoredobject>
208    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    /// Uploads a file to a specified bucket and object key.
216    ///
217    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
218    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    /// Create an object from buffer. If you are going to upload a large file, it is recommended to use `upload_file` instead.
246    /// And, it is recommended to set `mime_type` in `options`
247    ///
248    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
249    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    /// Create an object from base64 string.
275    /// And, it is recommended to set `mime_type` in `options`
276    ///
277    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
278    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    /// Append object.
300    ///
301    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/appendobject>
302    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        // alter the request method and add append object query parameters
326        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    /// Append object from buffer. suitable for small size content
337    /// And, it is recommended to set `mime_type` in `options`
338    ///
339    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
340    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        // alter the request method and add append object query parameters
362        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    /// Append object from base64 string. suitable for small size content
373    /// And, it is recommended to set `mime_type` in `options`
374    ///
375    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
376    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    /// Uploads a file to a specified bucket and object key.
399    ///
400    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/getobject>
401    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        // check parent path
422        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    /// Get object content into memory (bytes array).
438    ///
439    /// Large files can consume significant memory, exercise caution when using this function.
440    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    /// Create a "folder"
456    ///
457    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
458    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    /// Delete a "folder". if the folder contains any object, it will not be deleted
489    ///
490    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/deleteobject>
491    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    /// Get object metadata
517    ///
518    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/getobjectmeta>
519    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    /// Check if the object exists or not using get object metadata
552    ///
553    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/getobjectmeta>
554    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    /// Head object
569    ///
570    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/headobject>
571    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    /// Copy files (Objects) between the same or different Buckets within the same region.
586    ///
587    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/copyobject>
588    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    /// Delete an object
616    ///
617    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/deleteobject>
618    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    /// Delete multiple objects
648    ///
649    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/deletemultipleobjects>
650    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    /// Restore object
663    ///
664    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/restoreobject>
665    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    /// Clean retored object
676    ///
677    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/cleanrestoredobject>
678    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        // 9b98f68671ec239958e58a6813960ab5
955        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    /// Copy object in same bucket
997    #[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    /// Copy object across buckets
1022    #[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}