Skip to content

Commit e61a410

Browse files
durchRWDai
andauthored
Feat: bucket lifecycle configuration impl (#394)
* feat: bucketLifecycleConfiguration impl * get bucket lifecycle * public some struct * add LifecycleRuleBuilder * add DeleteBucketLifecycle * fix update * Remove returns * Bump to alpha.3 --------- Co-authored-by: RWDai <[email protected]>
1 parent bcdd67d commit e61a410

7 files changed

+461
-21
lines changed

s3/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rust-s3"
3-
version = "0.35.0-alpha.2"
3+
version = "0.35.0-alpha.3"
44
authors = ["Drazen Urch"]
55
description = "Rust library for working with AWS S3 and compatible object storage APIs"
66
repository = "https://github.com/durch/rust-s3"

s3/src/bucket.rs

+30-2
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,9 @@ use crate::error::S3Error;
5252
use crate::post_policy::PresignedPost;
5353
use crate::request::Request;
5454
use crate::serde_types::{
55-
BucketLocationResult, CompleteMultipartUploadData, CorsConfiguration, HeadObjectResult,
56-
InitiateMultipartUploadResponse, ListBucketResult, ListMultipartUploadsResult, Part,
55+
BucketLifecycleConfiguration, BucketLocationResult, CompleteMultipartUploadData,
56+
CorsConfiguration, HeadObjectResult, InitiateMultipartUploadResponse, ListBucketResult,
57+
ListMultipartUploadsResult, Part,
5758
};
5859
#[allow(unused_imports)]
5960
use crate::utils::{error_from_response_data, PutStreamResponse};
@@ -780,6 +781,33 @@ impl Bucket {
780781
request.response_data(false).await
781782
}
782783

784+
#[maybe_async::maybe_async]
785+
pub async fn get_bucket_lifecycle(&self) -> Result<BucketLifecycleConfiguration, S3Error> {
786+
let request = RequestImpl::new(self, "", Command::GetBucketLifecycle).await?;
787+
let response = request.response_data(false).await?;
788+
Ok(quick_xml::de::from_str::<BucketLifecycleConfiguration>(
789+
response.as_str()?,
790+
)?)
791+
}
792+
793+
#[maybe_async::maybe_async]
794+
pub async fn put_bucket_lifecycle(
795+
&self,
796+
lifecycle_config: BucketLifecycleConfiguration,
797+
) -> Result<ResponseData, S3Error> {
798+
let command = Command::PutBucketLifecycle {
799+
configuration: lifecycle_config,
800+
};
801+
let request = RequestImpl::new(self, "", command).await?;
802+
request.response_data(false).await
803+
}
804+
805+
#[maybe_async::maybe_async]
806+
pub async fn delete_bucket_lifecycle(&self) -> Result<ResponseData, S3Error> {
807+
let request = RequestImpl::new(self, "", Command::DeleteBucket).await?;
808+
request.response_data(false).await
809+
}
810+
783811
/// Gets torrent from an S3 path.
784812
///
785813
/// # Example:

s3/src/command.rs

+28-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use std::collections::HashMap;
22

3-
use crate::serde_types::{CompleteMultipartUploadData, CorsConfiguration};
3+
use crate::error::S3Error;
4+
use crate::serde_types::{
5+
BucketLifecycleConfiguration, CompleteMultipartUploadData, CorsConfiguration,
6+
};
47

58
use crate::EMPTY_PAYLOAD_SHA;
69
use sha2::{Digest, Sha256};
@@ -129,6 +132,11 @@ pub enum Command<'a> {
129132
PutBucketCors {
130133
configuration: CorsConfiguration,
131134
},
135+
GetBucketLifecycle,
136+
PutBucketLifecycle {
137+
configuration: BucketLifecycleConfiguration,
138+
},
139+
DeleteBucketLifecycle,
132140
}
133141

134142
impl<'a> Command<'a> {
@@ -142,6 +150,7 @@ impl<'a> Command<'a> {
142150
| Command::ListObjectsV2 { .. }
143151
| Command::GetBucketLocation
144152
| Command::GetObjectTagging
153+
| Command::GetBucketLifecycle
145154
| Command::ListMultipartUploads { .. }
146155
| Command::PresignGet { .. } => HttpMethod::Get,
147156
Command::PutObject { .. }
@@ -150,21 +159,23 @@ impl<'a> Command<'a> {
150159
| Command::PresignPut { .. }
151160
| Command::UploadPart { .. }
152161
| Command::PutBucketCors { .. }
153-
| Command::CreateBucket { .. } => HttpMethod::Put,
162+
| Command::CreateBucket { .. }
163+
| Command::PutBucketLifecycle { .. } => HttpMethod::Put,
154164
Command::DeleteObject
155165
| Command::DeleteObjectTagging
156166
| Command::AbortMultipartUpload { .. }
157167
| Command::PresignDelete { .. }
158-
| Command::DeleteBucket => HttpMethod::Delete,
168+
| Command::DeleteBucket
169+
| Command::DeleteBucketLifecycle => HttpMethod::Delete,
159170
Command::InitiateMultipartUpload { .. } | Command::CompleteMultipartUpload { .. } => {
160171
HttpMethod::Post
161172
}
162173
Command::HeadObject => HttpMethod::Head,
163174
}
164175
}
165176

166-
pub fn content_length(&self) -> usize {
167-
match &self {
177+
pub fn content_length(&self) -> Result<usize, S3Error> {
178+
let result = match &self {
168179
Command::CopyObject { from: _ } => 0,
169180
Command::PutObject { content, .. } => content.len(),
170181
Command::PutObjectTagging { tags } => tags.len(),
@@ -177,21 +188,27 @@ impl<'a> Command<'a> {
177188
0
178189
}
179190
}
191+
Command::PutBucketLifecycle { configuration } => {
192+
quick_xml::se::to_string(configuration)?.as_bytes().len()
193+
}
180194
_ => 0,
181-
}
195+
};
196+
Ok(result)
182197
}
183198

184199
pub fn content_type(&self) -> String {
185200
match self {
186201
Command::InitiateMultipartUpload { content_type } => content_type.to_string(),
187202
Command::PutObject { content_type, .. } => content_type.to_string(),
188-
Command::CompleteMultipartUpload { .. } => "application/xml".into(),
203+
Command::CompleteMultipartUpload { .. } | Command::PutBucketLifecycle { .. } => {
204+
"application/xml".into()
205+
}
189206
_ => "text/plain".into(),
190207
}
191208
}
192209

193-
pub fn sha256(&self) -> String {
194-
match &self {
210+
pub fn sha256(&self) -> Result<String, S3Error> {
211+
let result = match &self {
195212
Command::PutObject { content, .. } => {
196213
let mut sha = Sha256::default();
197214
sha.update(content);
@@ -217,6 +234,7 @@ impl<'a> Command<'a> {
217234
}
218235
}
219236
_ => EMPTY_PAYLOAD_SHA.into(),
220-
}
237+
};
238+
Ok(result)
221239
}
222240
}

s3/src/request/async_std_backend.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ impl<'a> Request for SurfRequest<'a> {
6161
HttpMethod::Head => surf::Request::builder(Method::Head, self.url()?),
6262
};
6363

64-
let mut request = request.body(self.request_body());
64+
let mut request = request.body(self.request_body()?);
6565

6666
for (name, value) in headers.iter() {
6767
request = request.header(

s3/src/request/request_trait.rs

+20-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use base64::engine::general_purpose;
22
use base64::Engine;
33
use hmac::Mac;
4+
use quick_xml::se::to_string;
45
use std::collections::HashMap;
56
#[cfg(any(feature = "with-tokio", feature = "with-async-std"))]
67
use std::pin::Pin;
@@ -153,8 +154,8 @@ pub trait Request {
153154
)
154155
}
155156

156-
fn request_body(&self) -> Vec<u8> {
157-
if let Command::PutObject { content, .. } = self.command() {
157+
fn request_body(&self) -> Result<Vec<u8>, S3Error> {
158+
let result = if let Command::PutObject { content, .. } = self.command() {
158159
Vec::from(content)
159160
} else if let Command::PutObjectTagging { tags } = self.command() {
160161
Vec::from(tags)
@@ -169,9 +170,12 @@ pub trait Request {
169170
} else {
170171
Vec::new()
171172
}
173+
} else if let Command::PutBucketLifecycle { configuration } = &self.command() {
174+
quick_xml::se::to_string(configuration)?.as_bytes().to_vec()
172175
} else {
173176
Vec::new()
174-
}
177+
};
178+
Ok(result)
175179
}
176180

177181
fn long_date(&self) -> Result<String, S3Error> {
@@ -377,6 +381,11 @@ pub trait Request {
377381
url_str.push_str(&multipart.query_string())
378382
}
379383
}
384+
Command::GetBucketLifecycle
385+
| Command::PutBucketLifecycle { .. }
386+
| Command::DeleteBucketLifecycle => {
387+
url_str.push_str("?lifecycle");
388+
}
380389
_ => {}
381390
}
382391

@@ -464,7 +473,7 @@ pub trait Request {
464473
&self.command().http_verb().to_string(),
465474
&self.url()?,
466475
headers,
467-
&self.command().sha256(),
476+
&self.command().sha256()?,
468477
)
469478
}
470479

@@ -492,7 +501,7 @@ pub trait Request {
492501
#[maybe_async::maybe_async]
493502
async fn headers(&self) -> Result<HeaderMap, S3Error> {
494503
// Generate this once, but it's used in more than one place.
495-
let sha256 = self.command().sha256();
504+
let sha256 = self.command().sha256()?;
496505

497506
// Start with extra_headers, that way our headers replace anything with
498507
// the same name.
@@ -531,7 +540,7 @@ pub trait Request {
531540
_ => {
532541
headers.insert(
533542
CONTENT_LENGTH,
534-
self.command().content_length().to_string().parse()?,
543+
self.command().content_length()?.to_string().parse()?,
535544
);
536545
headers.insert(CONTENT_TYPE, self.command().content_type().parse()?);
537546
}
@@ -584,6 +593,11 @@ pub trait Request {
584593
headers.insert(RANGE, range.parse()?);
585594
} else if let Command::CreateBucket { ref config } = self.command() {
586595
config.add_headers(&mut headers)?;
596+
} else if let Command::PutBucketLifecycle { ref configuration } = self.command() {
597+
let digest = md5::compute(to_string(configuration)?.as_bytes());
598+
let hash = general_purpose::STANDARD.encode(digest.as_ref());
599+
headers.insert(HeaderName::from_static("content-md5"), hash.parse()?);
600+
headers.remove("x-amz-content-sha256");
587601
}
588602

589603
// This must be last, as it signs the other headers, omitted if no secret key is provided

s3/src/request/tokio_backend.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ impl<'a> Request for HyperRequest<'a> {
133133
request = request.header(header, value);
134134
}
135135

136-
request.body(Body::from(self.request_body()))?
136+
request.body(Body::from(self.request_body()?))?
137137
};
138138
let response = client.request(request).await?;
139139

0 commit comments

Comments
 (0)