1use 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 async fn put_bucket<S>(&self, bucket_name: S, config: PutBucketConfiguration, options: Option<PutBucketOptions>) -> Result<()>
21 where
22 S: AsRef<str> + Send;
23
24 async fn list_buckets(&self, options: Option<ListBucketsOptions>) -> Result<ListBucketsResult>;
28
29 async fn get_bucket_info<S: AsRef<str> + Send>(&self, bucket_name: S) -> Result<BucketDetail>;
33
34 async fn get_bucket_location<S>(&self, bucket_name: S) -> Result<String>
38 where
39 S: AsRef<str> + Send;
40
41 async fn get_bucket_stat<S>(&self, bucket_name: S) -> Result<BucketStat>
45 where
46 S: AsRef<str> + Send;
47
48 async fn list_objects<S>(&self, bucket_name: S, options: Option<ListObjectsOptions>) -> Result<ListObjectsResult>
52 where
53 S: AsRef<str> + Send;
54
55 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 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 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 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 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 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 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 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}