Skip to main content

ali_oss_rs/
bucket.rs

1//! Basic bucket operations
2use async_trait::async_trait;
3
4use crate::{
5    bucket_common::{
6        build_list_buckets_request, build_list_objects_request, build_put_bucket_request, extract_bucket_location, BucketDetail, BucketStat,
7        ListBucketsOptions, ListBucketsResult, ListObjectsOptions, ListObjectsResult, PutBucketConfiguration, PutBucketOptions,
8    },
9    error::Error,
10    request::{OssRequest, RequestMethod},
11    util::validate_bucket_name,
12    Result,
13};
14
15#[async_trait]
16pub trait BucketOperations {
17    /// Create a new bucket
18    ///
19    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putbucket>
20    async fn put_bucket<S>(&self, bucket_name: S, config: PutBucketConfiguration, options: Option<PutBucketOptions>) -> Result<()>
21    where
22        S: AsRef<str> + Send;
23
24    /// List buckets
25    ///
26    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/listbuckets>
27    async fn list_buckets(&self, options: Option<ListBucketsOptions>) -> Result<ListBucketsResult>;
28
29    /// Get bucket information
30    ///
31    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/getbucketinfo>
32    async fn get_bucket_info<S: AsRef<str> + Send>(&self, bucket_name: S) -> Result<BucketDetail>;
33
34    /// Get bucket location
35    ///
36    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/getbucketlocation>
37    async fn get_bucket_location<S>(&self, bucket_name: S) -> Result<String>
38    where
39        S: AsRef<str> + Send;
40
41    /// Get bucket statistics data
42    ///
43    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/getbucketstat>
44    async fn get_bucket_stat<S>(&self, bucket_name: S) -> Result<BucketStat>
45    where
46        S: AsRef<str> + Send;
47
48    /// List objects in a bucket (V2)
49    ///
50    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/listobjectsv2>
51    async fn list_objects<S>(&self, bucket_name: S, options: Option<ListObjectsOptions>) -> Result<ListObjectsResult>
52    where
53        S: AsRef<str> + Send;
54
55    /// Delete a bucket
56    ///
57    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/deletebucket>
58    async fn delete_bucket<S>(&self, bucket_name: S) -> Result<()>
59    where
60        S: AsRef<str> + Send;
61}
62
63#[async_trait]
64impl BucketOperations for crate::Client {
65    /// Create a bucket.
66    ///
67    /// `bucket_name` constraint:
68    ///
69    /// - 3 to 63 characters length
70    /// - only lower case ascii alphabets, numbers and hyphen (`-`) are allowed
71    /// - not starts or ends with hyphen character
72    ///
73    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putbucket>
74    async fn put_bucket<S: AsRef<str> + Send>(&self, bucket_name: S, config: PutBucketConfiguration, options: Option<PutBucketOptions>) -> Result<()> {
75        if !validate_bucket_name(bucket_name.as_ref()) {
76            return Err(Error::Other(format!(
77                "invalid bucket name: {}. please see the official document for more details",
78                bucket_name.as_ref()
79            )));
80        }
81
82        let request_builder = build_put_bucket_request(bucket_name.as_ref(), &config, &options)?;
83
84        self.do_request::<()>(request_builder).await?;
85
86        Ok(())
87    }
88
89    /// See official document for more details: <https://help.aliyun.com/zh/oss/developer-reference/listbuckets>
90    async fn list_buckets(&self, options: Option<ListBucketsOptions>) -> Result<ListBucketsResult> {
91        let request_builder = build_list_buckets_request(&options);
92
93        let (_, content) = self.do_request::<String>(request_builder).await?;
94
95        ListBucketsResult::from_xml(&content)
96    }
97
98    /// Delete a bucket. Only non-empty bucket can be deleted
99    ///
100    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/deletebucket>
101    async fn delete_bucket<S: AsRef<str> + Send>(&self, bucket_name: S) -> Result<()> {
102        let bucket_name = bucket_name.as_ref();
103
104        if !validate_bucket_name(bucket_name) {
105            return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
106        }
107
108        let request_builder = OssRequest::new().method(RequestMethod::Delete).bucket(bucket_name);
109
110        self.do_request::<()>(request_builder).await?;
111
112        Ok(())
113    }
114
115    /// Get bucket info
116    ///
117    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/getbucketinfo>
118    async fn get_bucket_info<S: AsRef<str> + Send>(&self, bucket_name: S) -> Result<BucketDetail> {
119        let bucket_name = bucket_name.as_ref();
120
121        if !validate_bucket_name(bucket_name) {
122            return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
123        }
124        let request_builder = OssRequest::new().method(RequestMethod::Get).bucket(bucket_name).add_query("bucketInfo", "");
125
126        let (_, content) = self.do_request::<String>(request_builder).await?;
127
128        BucketDetail::from_xml(&content)
129    }
130
131    /// Get bucket location
132    ///
133    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/getbucketlocation>
134    async fn get_bucket_location<S: AsRef<str> + Send>(&self, bucket_name: S) -> Result<String> {
135        let bucket_name = bucket_name.as_ref();
136
137        if !validate_bucket_name(bucket_name) {
138            return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
139        }
140
141        let request_builder = OssRequest::new().method(RequestMethod::Get).bucket(bucket_name).add_query("location", "");
142
143        let (_, content) = self.do_request::<String>(request_builder).await?;
144
145        extract_bucket_location(content.as_str())
146    }
147
148    /// Get bucket statistics data
149    ///
150    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/getbucketstat>
151    async fn get_bucket_stat<S: AsRef<str> + Send>(&self, bucket_name: S) -> Result<BucketStat> {
152        let bucket_name = bucket_name.as_ref();
153
154        if !validate_bucket_name(bucket_name) {
155            return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
156        }
157
158        let request_builder = OssRequest::new().method(RequestMethod::Get).bucket(bucket_name).add_query("stat", "");
159
160        let (_, content) = self.do_request::<String>(request_builder).await?;
161
162        BucketStat::from_xml(&content)
163    }
164
165    /// List objects in a bucket (V2)
166    ///
167    /// Official document: <https://help.aliyun.com/zh/oss/developer-reference/listobjectsv2>
168    async fn list_objects<S: AsRef<str> + Send>(&self, bucket_name: S, options: Option<ListObjectsOptions>) -> Result<ListObjectsResult> {
169        let bucket_name = bucket_name.as_ref();
170
171        if !validate_bucket_name(bucket_name) {
172            return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
173        }
174
175        let request = build_list_objects_request(bucket_name, &options)?;
176
177        let (_, content) = self.do_request::<String>(request).await?;
178
179        ListObjectsResult::from_xml(&content)
180    }
181}
182
183#[cfg(test)]
184pub mod test_bucket_async {
185    use std::sync::Once;
186
187    use crate::{
188        bucket::BucketOperations,
189        bucket_common::{BucketAcl, ListBucketsOptions, ListObjectsOptionsBuilder},
190    };
191
192    static INIT: Once = Once::new();
193
194    fn setup() {
195        INIT.call_once(|| {
196            simple_logger::init_with_level(log::Level::Debug).unwrap();
197            dotenvy::dotenv().unwrap();
198        });
199    }
200
201    fn setup_comp() {
202        INIT.call_once(|| {
203            simple_logger::init_with_level(log::Level::Debug).unwrap();
204            dotenvy::from_filename(".env.comp").unwrap();
205        });
206    }
207
208    #[tokio::test]
209    async fn test_list_buckets_async() {
210        setup();
211        let client = crate::Client::from_env();
212
213        let response = client.list_buckets(None).await;
214
215        assert!(response.is_ok());
216
217        let result = response.unwrap();
218        assert!(!result.buckets.is_empty());
219
220        let bucket = &result.buckets[0];
221        assert!(!bucket.name.is_empty());
222
223        log::debug!("{:?}", result);
224    }
225
226    #[tokio::test]
227    async fn test_list_buckets_with_options_async() {
228        setup_comp();
229        let client = crate::Client::from_env();
230
231        let options = ListBucketsOptions {
232            max_keys: Some(10),
233            ..Default::default()
234        };
235
236        let response = client.list_buckets(Some(options)).await;
237        log::debug!("list buckets, page1: {:#?}", response);
238
239        assert!(response.is_ok());
240
241        let ret = response.unwrap();
242        assert_eq!(10, ret.buckets.len());
243
244        assert!(ret.next_marker.is_some());
245        assert!(ret.is_truncated);
246
247        let options = ListBucketsOptions {
248            max_keys: Some(10),
249            marker: ret.next_marker,
250            ..Default::default()
251        };
252
253        let response = client.list_buckets(Some(options)).await;
254        log::debug!("list buckets, page2: {:#?}", response);
255        assert!(response.is_ok());
256
257        let ret = response.unwrap();
258        assert_eq!(9, ret.buckets.len());
259    }
260
261    #[tokio::test]
262    async fn test_list_objects_async_1() {
263        setup_comp();
264        let client = crate::Client::from_env();
265
266        let options = ListObjectsOptionsBuilder::new().prefix("").delimiter('/').build();
267
268        let response = client.list_objects("mi-dev-public", Some(options)).await;
269        assert!(response.is_ok());
270
271        let result = response.unwrap();
272        assert!(result.key_count > 0);
273        assert_eq!(result.key_count, (result.common_prefixes.len() + result.contents.len()) as u64);
274    }
275
276    #[tokio::test]
277    async fn test_get_bucket_detail_async() {
278        setup_comp();
279        let client = crate::Client::from_env();
280
281        let response = client.get_bucket_info("mi-dev-public").await;
282        assert!(response.is_ok());
283
284        let ret = response.unwrap();
285        assert_eq!(BucketAcl::PublicRead, ret.access_control_list[0]);
286
287        let response = client.get_bucket_info("mi-dev-private").await;
288        assert!(response.is_ok());
289
290        let ret = response.unwrap();
291        assert_eq!(BucketAcl::Private, ret.access_control_list[0]);
292    }
293}