1use crate::bucket::Bucket;
2use crate::command::Command;
3use crate::error::S3Error;
4use crate::request::Request;
5use crate::request::RequestImpl;
6use crate::serde_types::{ListBucketResult, ListMultipartUploadsResult};
7use awscreds::Credentials;
8use awsregion::Region;
9use serde::Deserialize;
10
11impl Bucket {
12 pub async fn list_buckets(
36 region: Region,
37 credentials: Credentials,
38 ) -> Result<crate::bucket::ListBucketsResponse, S3Error> {
39 let dummy_bucket = Bucket::new("", region, credentials)?.with_path_style();
40 let request = RequestImpl::new(&dummy_bucket, "", Command::ListBuckets)?;
41 let response = request.response_data(false).await?;
42
43 Ok(quick_xml::de::from_str::<crate::bucket::ListBucketsResponse>(response.as_str()?)?)
44 }
45
46 pub async fn exists(&self) -> Result<bool, S3Error> {
68 let credentials = self
69 .credentials
70 .read()
71 .expect("Read lock to be acquired on Credentials")
72 .clone();
73
74 let response = Self::list_buckets(self.region.clone(), credentials).await?;
75
76 Ok(response
77 .bucket_names()
78 .collect::<std::collections::HashSet<String>>()
79 .contains(&self.name))
80 }
81
82 pub async fn list_page(
83 &self,
84 prefix: String,
85 delimiter: Option<String>,
86 continuation_token: Option<String>,
87 start_after: Option<String>,
88 max_keys: Option<usize>,
89 ) -> Result<(ListBucketResult, u16), S3Error> {
90 let command = if self.listobjects_v2 {
91 Command::ListObjectsV2 {
92 prefix,
93 delimiter,
94 continuation_token,
95 start_after,
96 max_keys,
97 }
98 } else {
99 Command::ListObjects {
103 prefix,
104 delimiter,
105 marker: std::cmp::max(continuation_token, start_after),
106 max_keys,
107 }
108 };
109 let request = RequestImpl::new(self, "/", command)?;
110 let response_data = request.response_data(false).await?;
111 let list_bucket_result = quick_xml::de::from_reader(response_data.as_slice())?;
112
113 Ok((list_bucket_result, response_data.status_code()))
114 }
115
116 pub async fn list(
139 &self,
140 prefix: String,
141 delimiter: Option<String>,
142 ) -> Result<Vec<ListBucketResult>, S3Error> {
143 let the_bucket = self.to_owned();
144 let mut results = Vec::new();
145 let mut continuation_token = None;
146
147 loop {
148 let (list_bucket_result, _) = the_bucket
149 .list_page(
150 prefix.clone(),
151 delimiter.clone(),
152 continuation_token,
153 None,
154 None,
155 )
156 .await?;
157 continuation_token = list_bucket_result.next_continuation_token.clone();
158 results.push(list_bucket_result);
159 if continuation_token.is_none() {
160 break;
161 }
162 }
163
164 Ok(results)
165 }
166
167 pub async fn list_multiparts_uploads_page(
168 &self,
169 prefix: Option<&str>,
170 delimiter: Option<&str>,
171 key_marker: Option<String>,
172 max_uploads: Option<usize>,
173 ) -> Result<(ListMultipartUploadsResult, u16), S3Error> {
174 let command = Command::ListMultipartUploads {
175 prefix,
176 delimiter,
177 key_marker,
178 max_uploads,
179 };
180 let request = RequestImpl::new(self, "/", command)?;
181 let response_data = request.response_data(false).await?;
182 let list_bucket_result = quick_xml::de::from_reader(response_data.as_slice())?;
183
184 Ok((list_bucket_result, response_data.status_code()))
185 }
186
187 pub async fn list_multiparts_uploads(
211 &self,
212 prefix: Option<&str>,
213 delimiter: Option<&str>,
214 ) -> Result<Vec<ListMultipartUploadsResult>, S3Error> {
215 let the_bucket = self.to_owned();
216 let mut results = Vec::new();
217 let mut next_marker: Option<String> = None;
218
219 loop {
220 let (list_multiparts_uploads_result, _) = the_bucket
221 .list_multiparts_uploads_page(prefix, delimiter, next_marker, None)
222 .await?;
223
224 let is_truncated = list_multiparts_uploads_result.is_truncated;
225 next_marker = list_multiparts_uploads_result.next_marker.clone();
226 results.push(list_multiparts_uploads_result);
227
228 if !is_truncated {
229 break;
230 }
231 }
232
233 Ok(results)
234 }
235}
236
237#[derive(Clone, Default, Deserialize, Debug)]
238#[serde(rename_all = "PascalCase", rename = "ListAllMyBucketsResult")]
239pub struct ListBucketsResponse {
240 pub owner: BucketOwner,
241 pub buckets: BucketContainer,
242}
243
244impl ListBucketsResponse {
245 pub fn bucket_names(&self) -> impl Iterator<Item = String> + '_ {
246 self.buckets.bucket.iter().map(|bucket| bucket.name.clone())
247 }
248}
249
250#[derive(Deserialize, Default, Clone, Debug, PartialEq, Eq)]
251pub struct BucketOwner {
252 #[serde(rename = "ID")]
253 pub id: String,
254 #[serde(rename = "DisplayName")]
255 pub display_name: String,
256}
257
258#[derive(Deserialize, Default, Clone, Debug)]
259#[serde(rename_all = "PascalCase")]
260pub struct BucketInfo {
261 pub name: String,
262 pub creation_date: crate::serde_types::DateTime,
263}
264
265#[derive(Deserialize, Default, Clone, Debug)]
266#[serde(rename_all = "PascalCase")]
267pub struct BucketContainer {
268 #[serde(default)]
269 pub bucket: Vec<BucketInfo>,
270}
271
272#[cfg(test)]
273mod tests {
274 #[test]
275 pub fn parse_list_buckets_response() {
276 let response = r#"
277 <?xml version="1.0" encoding="UTF-8"?>
278 <ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
279 <Owner>
280 <ID>02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4</ID>
281 <DisplayName>minio</DisplayName>
282 </Owner>
283 <Buckets>
284 <Bucket>
285 <Name>test-rust-s3</Name>
286 <CreationDate>2023-06-04T20:13:37.837Z</CreationDate>
287 </Bucket>
288 <Bucket>
289 <Name>test-rust-s3-2</Name>
290 <CreationDate>2023-06-04T20:17:47.152Z</CreationDate>
291 </Bucket>
292 </Buckets>
293 </ListAllMyBucketsResult>
294 "#;
295
296 let parsed = quick_xml::de::from_str::<super::ListBucketsResponse>(response).unwrap();
297
298 assert_eq!(parsed.owner.display_name, "minio");
299 assert_eq!(
300 parsed.owner.id,
301 "02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4"
302 );
303 assert_eq!(parsed.buckets.bucket.len(), 2);
304
305 assert_eq!(parsed.buckets.bucket.first().unwrap().name, "test-rust-s3");
306 assert_eq!(
307 parsed.buckets.bucket.first().unwrap().creation_date,
308 "2023-06-04T20:13:37.837Z"
309 .parse::<crate::serde_types::DateTime>()
310 .unwrap()
311 );
312
313 assert_eq!(parsed.buckets.bucket.last().unwrap().name, "test-rust-s3-2");
314 assert_eq!(
315 parsed.buckets.bucket.last().unwrap().creation_date,
316 "2023-06-04T20:17:47.152Z"
317 .parse::<crate::serde_types::DateTime>()
318 .unwrap()
319 );
320 }
321
322 #[test]
323 pub fn parse_list_buckets_response_when_no_buckets_exist() {
324 let response = r#"
325 <?xml version="1.0" encoding="UTF-8"?>
326 <ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
327 <Owner>
328 <ID>02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4</ID>
329 <DisplayName>minio</DisplayName>
330 </Owner>
331 <Buckets>
332 </Buckets>
333 </ListAllMyBucketsResult>
334 "#;
335
336 let parsed = quick_xml::de::from_str::<super::ListBucketsResponse>(response).unwrap();
337
338 assert_eq!(parsed.owner.display_name, "minio");
339 assert_eq!(
340 parsed.owner.id,
341 "02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4"
342 );
343 assert_eq!(parsed.buckets.bucket.len(), 0);
344 }
345}