1use crate::error::S3Error;
40use crate::{Bucket, Region};
41
42#[allow(dead_code)]
44#[derive(Clone, Debug)]
45pub enum CannedBucketAcl {
46 Private,
47 PublicRead,
48 PublicReadWrite,
49 AuthenticatedRead,
50 Custom(String),
51}
52
53use http::HeaderMap;
54use http::header::HeaderName;
55use std::fmt;
56
57impl fmt::Display for CannedBucketAcl {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 match self {
60 CannedBucketAcl::Private => write!(f, "private"),
61 CannedBucketAcl::PublicRead => write!(f, "public-read"),
62 CannedBucketAcl::PublicReadWrite => write!(f, "public-read-write"),
63 CannedBucketAcl::AuthenticatedRead => write!(f, "authenticated-read"),
64 CannedBucketAcl::Custom(policy) => write!(f, "{policy}"),
65 }
66 }
67}
68
69#[allow(dead_code)]
71#[derive(Clone, Debug)]
72pub enum BucketAcl {
73 Id { id: String },
74 Uri { uri: String },
75 Email { email: String },
76}
77
78impl fmt::Display for BucketAcl {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 match self {
81 BucketAcl::Id { id } => write!(f, "id=\"{}\"", id),
82 BucketAcl::Uri { uri } => write!(f, "uri=\"{}\"", uri),
83 BucketAcl::Email { email } => write!(f, "email=\"{}\"", email),
84 }
85 }
86}
87
88#[derive(Clone, Debug)]
89pub struct BucketConfiguration {
90 acl: Option<CannedBucketAcl>,
91 object_lock_enabled: bool,
92 grant_full_control: Option<Vec<BucketAcl>>,
93 grant_read: Option<Vec<BucketAcl>>,
94 grant_read_acp: Option<Vec<BucketAcl>>,
95 grant_write: Option<Vec<BucketAcl>>,
96 grant_write_acp: Option<Vec<BucketAcl>>,
97 location_constraint: Option<Region>,
98}
99
100impl Default for BucketConfiguration {
101 fn default() -> Self {
102 BucketConfiguration::private()
103 }
104}
105
106fn acl_list(acl: &[BucketAcl]) -> String {
107 acl.iter()
108 .map(|x| x.to_string())
109 .collect::<Vec<String>>()
110 .join(",")
111}
112
113impl BucketConfiguration {
114 #[allow(clippy::too_many_arguments)]
115 pub fn new(
116 acl: Option<CannedBucketAcl>,
117 object_lock_enabled: bool,
118 grant_full_control: Option<Vec<BucketAcl>>,
119 grant_read: Option<Vec<BucketAcl>>,
120 grant_read_acp: Option<Vec<BucketAcl>>,
121 grant_write: Option<Vec<BucketAcl>>,
122 grant_write_acp: Option<Vec<BucketAcl>>,
123 location_constraint: Option<Region>,
124 ) -> Self {
125 Self {
126 acl,
127 object_lock_enabled,
128 grant_full_control,
129 grant_read,
130 grant_read_acp,
131 grant_write,
132 grant_write_acp,
133 location_constraint,
134 }
135 }
136
137 pub fn public() -> Self {
138 BucketConfiguration {
139 acl: None,
140 object_lock_enabled: false,
141 grant_full_control: None,
142 grant_read: None,
143 grant_read_acp: None,
144 grant_write: None,
145 grant_write_acp: None,
146 location_constraint: None,
147 }
148 }
149
150 pub fn private() -> Self {
151 BucketConfiguration {
152 acl: Some(CannedBucketAcl::Private),
153 object_lock_enabled: false,
154 grant_full_control: None,
155 grant_read: None,
156 grant_read_acp: None,
157 grant_write: None,
158 grant_write_acp: None,
159 location_constraint: None,
160 }
161 }
162
163 pub fn set_region(&mut self, region: Region) {
164 self.set_location_constraint(region)
165 }
166
167 pub fn set_location_constraint(&mut self, region: Region) {
168 self.location_constraint = Some(region)
169 }
170
171 pub fn location_constraint_payload(&self) -> Option<String> {
172 if let Some(ref location_constraint) = self.location_constraint {
173 if location_constraint == &Region::UsEast1 {
174 return None;
175 }
176 Some(format!(
177 "<CreateBucketConfiguration><LocationConstraint>{}</LocationConstraint></CreateBucketConfiguration>",
178 location_constraint
179 ))
180 } else {
181 None
182 }
183 }
184
185 pub fn add_headers(&self, headers: &mut HeaderMap) -> Result<(), S3Error> {
186 if let Some(ref acl) = self.acl {
187 headers.insert(
188 HeaderName::from_static("x-amz-acl"),
189 acl.to_string().parse()?,
190 );
191 }
192
193 if self.object_lock_enabled {
194 headers.insert(
195 HeaderName::from_static("x-amz-bucket-object-lock-enabled"),
196 "Enabled".to_string().parse()?,
197 );
198 }
199 if let Some(ref value) = self.grant_full_control {
200 headers.insert(
201 HeaderName::from_static("x-amz-grant-full-control"),
202 acl_list(value).parse()?,
203 );
204 }
205 if let Some(ref value) = self.grant_read {
206 headers.insert(
207 HeaderName::from_static("x-amz-grant-read"),
208 acl_list(value).parse()?,
209 );
210 }
211 if let Some(ref value) = self.grant_read_acp {
212 headers.insert(
213 HeaderName::from_static("x-amz-grant-read-acp"),
214 acl_list(value).parse()?,
215 );
216 }
217 if let Some(ref value) = self.grant_write {
218 headers.insert(
219 HeaderName::from_static("x-amz-grant-write"),
220 acl_list(value).parse()?,
221 );
222 }
223 if let Some(ref value) = self.grant_write_acp {
224 headers.insert(
225 HeaderName::from_static("x-amz-grant-write-acp"),
226 acl_list(value).parse()?,
227 );
228 }
229 Ok(())
230 }
231}
232
233#[allow(dead_code)]
234pub struct CreateBucketResponse {
235 pub bucket: Box<Bucket>,
236 pub response_text: String,
237 pub response_code: u16,
238}
239
240impl CreateBucketResponse {
241 pub fn success(&self) -> bool {
242 self.response_code == 200
243 }
244}
245
246pub use list_buckets::*;
247
248mod list_buckets {
249
250 #[derive(Clone, Default, Deserialize, Debug)]
251 #[serde(rename_all = "PascalCase", rename = "ListAllMyBucketsResult")]
252 pub struct ListBucketsResponse {
253 pub owner: BucketOwner,
254 pub buckets: BucketContainer,
255 }
256
257 impl ListBucketsResponse {
258 pub fn bucket_names(&self) -> impl Iterator<Item = String> + '_ {
259 self.buckets.bucket.iter().map(|bucket| bucket.name.clone())
260 }
261 }
262
263 #[derive(Deserialize, Default, Clone, Debug, PartialEq, Eq)]
264 pub struct BucketOwner {
265 #[serde(rename = "ID")]
266 pub id: String,
267 #[serde(rename = "DisplayName")]
268 pub display_name: Option<String>,
269 }
270
271 #[derive(Deserialize, Default, Clone, Debug)]
272 #[serde(rename_all = "PascalCase")]
273 pub struct BucketInfo {
274 pub name: String,
275 pub creation_date: String,
276 }
277
278 #[derive(Deserialize, Default, Clone, Debug)]
279 #[serde(rename_all = "PascalCase")]
280 pub struct BucketContainer {
281 #[serde(default)]
282 pub bucket: Vec<BucketInfo>,
283 }
284
285 #[cfg(test)]
286 mod tests {
287 #[test]
288 pub fn parse_list_buckets_response() {
289 let response = r#"
290 <?xml version="1.0" encoding="UTF-8"?>
291 <ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
292 <Owner>
293 <ID>02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4</ID>
294 <DisplayName>minio</DisplayName>
295 </Owner>
296 <Buckets>
297 <Bucket>
298 <Name>test-rust-s3</Name>
299 <CreationDate>2023-06-04T20:13:37.837Z</CreationDate>
300 </Bucket>
301 <Bucket>
302 <Name>test-rust-s3-2</Name>
303 <CreationDate>2023-06-04T20:17:47.152Z</CreationDate>
304 </Bucket>
305 </Buckets>
306 </ListAllMyBucketsResult>
307 "#;
308
309 let parsed = quick_xml::de::from_str::<super::ListBucketsResponse>(response).unwrap();
310
311 assert_eq!(parsed.owner.display_name, Some("minio".to_string()));
312 assert_eq!(
313 parsed.owner.id,
314 "02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4"
315 );
316 assert_eq!(parsed.buckets.bucket.len(), 2);
317
318 assert_eq!(parsed.buckets.bucket.first().unwrap().name, "test-rust-s3");
319 assert_eq!(
320 parsed.buckets.bucket.first().unwrap().creation_date,
321 "2023-06-04T20:13:37.837Z"
322 );
323
324 assert_eq!(parsed.buckets.bucket.last().unwrap().name, "test-rust-s3-2");
325 assert_eq!(
326 parsed.buckets.bucket.last().unwrap().creation_date,
327 "2023-06-04T20:17:47.152Z"
328 );
329 }
330
331 #[test]
332 pub fn parse_list_buckets_response_when_no_buckets_exist() {
333 let response = r#"
334 <?xml version="1.0" encoding="UTF-8"?>
335 <ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
336 <Owner>
337 <ID>02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4</ID>
338 <DisplayName>minio</DisplayName>
339 </Owner>
340 <Buckets>
341 </Buckets>
342 </ListAllMyBucketsResult>
343 "#;
344
345 let parsed = quick_xml::de::from_str::<super::ListBucketsResponse>(response).unwrap();
346
347 assert_eq!(parsed.owner.display_name, Some("minio".to_string()));
348 assert_eq!(
349 parsed.owner.id,
350 "02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4"
351 );
352 assert_eq!(parsed.buckets.bucket.len(), 0);
353 }
354 }
355}