diff --git a/examples/bucket_encryption.rs b/examples/bucket_encryption.rs index d2968c7..55e7f92 100644 --- a/examples/bucket_encryption.rs +++ b/examples/bucket_encryption.rs @@ -34,7 +34,7 @@ async fn main() -> Result<(), Box> { let _resp: SetBucketEncryptionResponse = client .set_bucket_encryption(bucket_name) - .config(SseConfig::default()) + .sse_config(SseConfig::default()) .send() .await?; diff --git a/examples/bucket_lifecycle.rs b/examples/bucket_lifecycle.rs new file mode 100644 index 0000000..9a48aa5 --- /dev/null +++ b/examples/bucket_lifecycle.rs @@ -0,0 +1,88 @@ +// MinIO Rust Library for Amazon S3 Compatible Cloud Storage +// Copyright 2025 MinIO, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod common; + +use crate::common::{create_bucket_if_not_exists, create_client_on_play}; +use minio::s3::args::DeleteBucketLifecycleArgs; +use minio::s3::response::{ + DeleteBucketLifecycleResponse, GetBucketLifecycleResponse, SetBucketLifecycleResponse, +}; +use minio::s3::types::{Filter, LifecycleConfig, LifecycleRule, S3Api}; +use minio::s3::Client; + +#[tokio::main] +async fn main() -> Result<(), Box> { + env_logger::init(); // Note: set environment variable RUST_LOG="INFO" to log info and higher + let client: Client = create_client_on_play()?; + + let bucket_name: &str = "lifecycle-rust-bucket"; + create_bucket_if_not_exists(bucket_name, &client).await?; + + if false { + // TODO + let resp: GetBucketLifecycleResponse = + client.get_bucket_lifecycle(bucket_name).send().await?; + log::info!("life cycle settings before setting: resp={:?}", resp); + } + + let rules: Vec = vec![LifecycleRule { + abort_incomplete_multipart_upload_days_after_initiation: None, + expiration_date: None, + expiration_days: Some(365), + expiration_expired_object_delete_marker: None, + filter: Filter { + and_operator: None, + prefix: Some(String::from("logs/")), + tag: None, + }, + id: String::from("rule1"), + noncurrent_version_expiration_noncurrent_days: None, + noncurrent_version_transition_noncurrent_days: None, + noncurrent_version_transition_storage_class: None, + status: true, + transition_date: None, + transition_days: None, + transition_storage_class: None, + }]; + + let resp: SetBucketLifecycleResponse = client + .set_bucket_lifecycle(bucket_name) + .life_cycle_config(LifecycleConfig { rules }) + .send() + .await?; + log::info!("response of setting life cycle config: resp={:?}", resp); + + if false { + // TODO + let resp: GetBucketLifecycleResponse = + client.get_bucket_lifecycle(bucket_name).send().await?; + log::info!("life cycle settings after setting: resp={:?}", resp); + } + + if false { + // TODO + let resp: DeleteBucketLifecycleResponse = client + .delete_bucket_lifecycle(&DeleteBucketLifecycleArgs { + extra_headers: None, + extra_query_params: None, + region: None, + bucket: "", + }) + .await?; + log::info!("response of deleting lifecycle config: resp={:?}", resp); + } + Ok(()) +} diff --git a/src/s3/args.rs b/src/s3/args.rs index f665525..28e1842 100644 --- a/src/s3/args.rs +++ b/src/s3/args.rs @@ -19,8 +19,8 @@ use crate::s3::error::Error; use crate::s3::signer::post_presign_v4; use crate::s3::sse::{Sse, SseCustomerKey}; use crate::s3::types::{ - Directive, LifecycleConfig, NotificationConfig, ObjectLockConfig, Part, ReplicationConfig, - Retention, RetentionMode, SelectRequest, + Directive, NotificationConfig, ObjectLockConfig, Part, ReplicationConfig, Retention, + RetentionMode, SelectRequest, }; use crate::s3::utils::{ b64encode, check_bucket_name, merge, to_amz_date, to_http_header_value, to_iso8601utc, @@ -1333,58 +1333,6 @@ pub type DeleteBucketLifecycleArgs<'a> = BucketArgs<'a>; /// Argument for [get_bucket_lifecycle()](crate::s3::client::Client::get_bucket_lifecycle) API pub type GetBucketLifecycleArgs<'a> = BucketArgs<'a>; -/// Argument for [set_bucket_lifecycle()](crate::s3::client::Client::set_bucket_lifecycle) API -pub struct SetBucketLifecycleArgs<'a> { - pub extra_headers: Option<&'a Multimap>, - pub extra_query_params: Option<&'a Multimap>, - pub region: Option<&'a str>, - pub bucket: &'a str, - pub config: &'a LifecycleConfig, -} - -impl<'a> SetBucketLifecycleArgs<'a> { - /// Returns argument for [set_bucket_lifecycle()](crate::s3::client::Client::set_bucket_lifecycle) API with given bucket name and configuration - /// - /// # Examples - /// - /// ``` - /// use minio::s3::args::*; - /// use minio::s3::types::*; - /// let mut rules: Vec = Vec::new(); - /// rules.push(LifecycleRule { - /// abort_incomplete_multipart_upload_days_after_initiation: None, - /// expiration_date: None, - /// expiration_days: Some(365), - /// expiration_expired_object_delete_marker: None, - /// filter: Filter {and_operator: None, prefix: Some(String::from("logs/")), tag: None}, - /// id: String::from("rule1"), - /// noncurrent_version_expiration_noncurrent_days: None, - /// noncurrent_version_transition_noncurrent_days: None, - /// noncurrent_version_transition_storage_class: None, - /// status: true, - /// transition_date: None, - /// transition_days: None, - /// transition_storage_class: None, - /// }); - /// let mut config = LifecycleConfig {rules}; - /// let args = SetBucketLifecycleArgs::new("my-bucket", &config).unwrap(); - /// ``` - pub fn new( - bucket_name: &'a str, - config: &'a LifecycleConfig, - ) -> Result, Error> { - check_bucket_name(bucket_name, true)?; - - Ok(SetBucketLifecycleArgs { - extra_headers: None, - extra_query_params: None, - region: None, - bucket: bucket_name, - config, - }) - } -} - /// Argument for [delete_bucket_notification()](crate::s3::client::Client::delete_bucket_notification) API pub type DeleteBucketNotificationArgs<'a> = BucketArgs<'a>; diff --git a/src/s3/builders.rs b/src/s3/builders.rs index 3532bcc..bd9e1f7 100644 --- a/src/s3/builders.rs +++ b/src/s3/builders.rs @@ -17,6 +17,7 @@ mod bucket_common; mod get_bucket_encryption; +mod get_bucket_lifecycle; mod get_bucket_versioning; mod get_object; mod list_buckets; @@ -27,10 +28,12 @@ mod object_prompt; mod put_object; mod remove_objects; mod set_bucket_encryption; +mod set_bucket_lifecycle; mod set_bucket_versioning; pub use bucket_common::*; pub use get_bucket_encryption::*; +pub use get_bucket_lifecycle::*; pub use get_bucket_versioning::*; pub use get_object::*; pub use list_buckets::*; @@ -41,4 +44,5 @@ pub use object_prompt::*; pub use put_object::*; pub use remove_objects::*; pub use set_bucket_encryption::*; +pub use set_bucket_lifecycle::*; pub use set_bucket_versioning::*; diff --git a/src/s3/builders/get_bucket_encryption.rs b/src/s3/builders/get_bucket_encryption.rs index 5916872..7c9fbef 100644 --- a/src/s3/builders/get_bucket_encryption.rs +++ b/src/s3/builders/get_bucket_encryption.rs @@ -20,7 +20,7 @@ use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::utils::{check_bucket_name, merge, Multimap}; use http::Method; -/// Argument builder for [get_bucket_encryption()](Client::get_bucket_encryption) API +/// Argument builder for [get_bucket_encryption()](crate::s3::client::Client::get_bucket_encryption) API pub type GetBucketEncryption = BucketCommon; #[derive(Default, Debug)] diff --git a/src/s3/builders/get_bucket_lifecycle.rs b/src/s3/builders/get_bucket_lifecycle.rs new file mode 100644 index 0000000..862374f --- /dev/null +++ b/src/s3/builders/get_bucket_lifecycle.rs @@ -0,0 +1,63 @@ +// MinIO Rust Library for Amazon S3 Compatible Cloud Storage +// Copyright 2025 MinIO, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::s3::builders::BucketCommon; +use crate::s3::error::Error; +use crate::s3::response::GetBucketLifecycleResponse; +use crate::s3::types::{S3Api, S3Request, ToS3Request}; +use crate::s3::utils::check_bucket_name; +use crate::s3::Client; +use http::Method; + +/// Argument builder for [get_bucket_lifecycle()](Client::get_bucket_lifecycle) API +pub type GetBucketLifecycle = BucketCommon; + +#[derive(Default, Debug)] +pub struct GetBucketLifecyclePhantomData; + +impl S3Api for GetBucketLifecycle { + type S3Response = GetBucketLifecycleResponse; +} + +impl ToS3Request for GetBucketLifecycle { + fn to_s3request(&self) -> Result { + check_bucket_name(&self.bucket, true)?; + + let headers = self + .extra_headers + .as_ref() + .filter(|v| !v.is_empty()) + .cloned() + .unwrap_or_default(); + let mut query_params = self + .extra_query_params + .as_ref() + .filter(|v| !v.is_empty()) + .cloned() + .unwrap_or_default(); + + query_params.insert("lifecycle".into(), String::new()); + + let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?; + + let req = S3Request::new(client, Method::GET) + .region(self.region.as_deref()) + .bucket(Some(&self.bucket)) + .query_params(query_params) + .headers(headers); + + Ok(req) + } +} diff --git a/src/s3/builders/get_bucket_versioning.rs b/src/s3/builders/get_bucket_versioning.rs index 2742a56..433d38f 100644 --- a/src/s3/builders/get_bucket_versioning.rs +++ b/src/s3/builders/get_bucket_versioning.rs @@ -20,7 +20,7 @@ use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::utils::{check_bucket_name, merge, Multimap}; use http::Method; -/// Argument builder for [get_bucket_versioning()](Client::get_bucket_versioning) API +/// Argument builder for [get_bucket_versioning()](crate::s3::client::Client::get_bucket_versioning) API pub type GetBucketVersioning = BucketCommon; #[derive(Default, Debug)] diff --git a/src/s3/builders/set_bucket_encryption.rs b/src/s3/builders/set_bucket_encryption.rs index bff6b26..4529a58 100644 --- a/src/s3/builders/set_bucket_encryption.rs +++ b/src/s3/builders/set_bucket_encryption.rs @@ -63,7 +63,7 @@ impl SetBucketEncryption { self } - pub fn config(mut self, config: SseConfig) -> Self { + pub fn sse_config(mut self, config: SseConfig) -> Self { self.config = config; self } diff --git a/src/s3/builders/set_bucket_lifecycle.rs b/src/s3/builders/set_bucket_lifecycle.rs new file mode 100644 index 0000000..4932ddc --- /dev/null +++ b/src/s3/builders/set_bucket_lifecycle.rs @@ -0,0 +1,109 @@ +// MinIO Rust Library for Amazon S3 Compatible Cloud Storage +// Copyright 2025 MinIO, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::s3::builders::SegmentedBytes; +use crate::s3::error::Error; +use crate::s3::response::SetBucketLifecycleResponse; +use crate::s3::types::{LifecycleConfig, S3Api, S3Request, ToS3Request}; +use crate::s3::utils::{check_bucket_name, md5sum_hash, Multimap}; +use crate::s3::Client; +use bytes::Bytes; +use http::Method; + +/// Argument builder for [set_bucket_lifecycle()](crate::s3::client::Client::set_bucket_lifecycle) API +#[derive(Clone, Debug, Default)] +pub struct SetBucketLifecycle { + pub(crate) client: Option, + + pub(crate) extra_headers: Option, + pub(crate) extra_query_params: Option, + pub(crate) region: Option, + pub(crate) bucket: String, + + pub(crate) config: LifecycleConfig, +} + +impl SetBucketLifecycle { + pub fn new(bucket: &str) -> Self { + Self { + bucket: bucket.to_owned(), + ..Default::default() + } + } + pub fn client(mut self, client: &Client) -> Self { + self.client = Some(client.clone()); + self + } + + pub fn extra_headers(mut self, extra_headers: Option) -> Self { + self.extra_headers = extra_headers; + self + } + + pub fn extra_query_params(mut self, extra_query_params: Option) -> Self { + self.extra_query_params = extra_query_params; + self + } + + pub fn region(mut self, region: Option) -> Self { + self.region = region; + self + } + + pub fn life_cycle_config(mut self, config: LifecycleConfig) -> Self { + self.config = config; + self + } +} + +impl S3Api for SetBucketLifecycle { + type S3Response = SetBucketLifecycleResponse; +} + +impl ToS3Request for SetBucketLifecycle { + fn to_s3request(&self) -> Result { + check_bucket_name(&self.bucket, true)?; + + let mut headers = self + .extra_headers + .as_ref() + .filter(|v| !v.is_empty()) + .cloned() + .unwrap_or_default(); + let mut query_params = self + .extra_query_params + .as_ref() + .filter(|v| !v.is_empty()) + .cloned() + .unwrap_or_default(); + + query_params.insert("lifecycle".into(), String::new()); + + let bytes: Bytes = self.config.to_xml().into(); + headers.insert(String::from("Content-MD5"), md5sum_hash(&bytes)); + let body: Option = Some(SegmentedBytes::from(bytes)); + + let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?; + + let req = S3Request::new(client, Method::PUT) + .region(self.region.as_deref()) + .bucket(Some(&self.bucket)) + .query_params(query_params) + .headers(headers) + .body(body); + + Ok(req) + } +} diff --git a/src/s3/builders/set_bucket_versioning.rs b/src/s3/builders/set_bucket_versioning.rs index 8df6d87..e701f4b 100644 --- a/src/s3/builders/set_bucket_versioning.rs +++ b/src/s3/builders/set_bucket_versioning.rs @@ -40,7 +40,7 @@ impl fmt::Display for VersioningStatus { } } -/// Argument builder for [set_bucket_encryption()](Client::set_bucket_encryption) API +/// Argument builder for [set_bucket_encryption()](crate::s3::client::Client::set_bucket_encryption) API #[derive(Clone, Debug, Default)] pub struct SetBucketVersioning { pub(crate) client: Option, diff --git a/src/s3/client.rs b/src/s3/client.rs index 64c482c..64b714f 100644 --- a/src/s3/client.rs +++ b/src/s3/client.rs @@ -29,8 +29,7 @@ use crate::s3::response::*; use crate::s3::signer::{presign_v4, sign_v4_s3}; use crate::s3::sse::SseCustomerKey; use crate::s3::types::{ - Directive, LifecycleConfig, NotificationConfig, ObjectLockConfig, Part, ReplicationConfig, - RetentionMode, + Directive, NotificationConfig, ObjectLockConfig, Part, ReplicationConfig, RetentionMode, }; use crate::s3::utils::{ from_iso8601utc, get_default_text, get_option_text, get_text, md5sum_hash, md5sum_hash_sb, @@ -48,6 +47,7 @@ use tokio::fs; use xmltree::Element; mod get_bucket_encryption; +mod get_bucket_lifecycle; mod get_bucket_versioning; mod get_object; mod list_objects; @@ -56,6 +56,7 @@ mod object_prompt; mod put_object; mod remove_objects; mod set_bucket_encryption; +mod set_bucket_lifecycle; mod set_bucket_versioning; use super::builders::{ListBuckets, SegmentedBytes}; @@ -1204,7 +1205,7 @@ impl Client { Ok(resp) => Ok(DeleteBucketEncryptionResponse { headers: resp.headers().clone(), region: region.clone(), - bucket_name: args.bucket.to_string(), + bucket: args.bucket.to_string(), }), Err(e) => match e { Error::S3Error(ref err) => { @@ -1212,7 +1213,7 @@ impl Client { return Ok(DeleteBucketEncryptionResponse { headers: HeaderMap::new(), region: region.clone(), - bucket_name: args.bucket.to_string(), + bucket: args.bucket.to_string(), }); } Err(e) @@ -1297,7 +1298,7 @@ impl Client { Ok(DeleteBucketLifecycleResponse { headers: resp.headers().clone(), region: region.clone(), - bucket_name: args.bucket.to_string(), + bucket: args.bucket.to_string(), }) } @@ -1351,7 +1352,7 @@ impl Client { Ok(resp) => Ok(DeleteBucketPolicyResponse { headers: resp.headers().clone(), region: region.clone(), - bucket_name: args.bucket.to_string(), + bucket: args.bucket.to_string(), }), Err(e) => match e { Error::S3Error(ref err) => { @@ -1359,7 +1360,7 @@ impl Client { return Ok(DeleteBucketPolicyResponse { headers: HeaderMap::new(), region: region.clone(), - bucket_name: args.bucket.to_string(), + bucket: args.bucket.to_string(), }); } Err(e) @@ -1401,7 +1402,7 @@ impl Client { Ok(resp) => Ok(DeleteBucketReplicationResponse { headers: resp.headers().clone(), region: region.clone(), - bucket_name: args.bucket.to_string(), + bucket: args.bucket.to_string(), }), Err(e) => match e { Error::S3Error(ref err) => { @@ -1409,7 +1410,7 @@ impl Client { return Ok(DeleteBucketReplicationResponse { headers: HeaderMap::new(), region: region.clone(), - bucket_name: args.bucket.to_string(), + bucket: args.bucket.to_string(), }); } Err(e) @@ -1451,7 +1452,7 @@ impl Client { Ok(DeleteBucketTagsResponse { headers: resp.headers().clone(), region: region.clone(), - bucket_name: args.bucket.to_string(), + bucket: args.bucket.to_string(), }) } @@ -1606,64 +1607,6 @@ impl Client { }) } - pub async fn get_bucket_lifecycle( - &self, - args: &GetBucketLifecycleArgs<'_>, - ) -> Result { - let region = self.get_region(args.bucket, args.region).await?; - - let mut headers = Multimap::new(); - if let Some(v) = &args.extra_headers { - merge(&mut headers, v); - } - - let mut query_params = Multimap::new(); - if let Some(v) = &args.extra_query_params { - merge(&mut query_params, v); - } - query_params.insert(String::from("lifecycle"), String::new()); - - match self - .execute( - Method::GET, - ®ion, - &mut headers, - &query_params, - Some(args.bucket), - None, - None, - ) - .await - { - Ok(resp) => { - let header_map = resp.headers().clone(); - let body = resp.bytes().await?; - let root = Element::parse(body.reader())?; - - Ok(GetBucketLifecycleResponse { - headers: header_map.clone(), - region: region.clone(), - bucket_name: args.bucket.to_string(), - config: LifecycleConfig::from_xml(&root)?, - }) - } - Err(e) => match e { - Error::S3Error(ref err) => { - if err.code == "NoSuchLifecycleConfiguration" { - return Ok(GetBucketLifecycleResponse { - headers: HeaderMap::new(), - region: region.clone(), - bucket_name: args.bucket.to_string(), - config: LifecycleConfig { rules: Vec::new() }, - }); - } - Err(e) - } - _ => Err(e), - }, - } - } - pub async fn get_bucket_notification( &self, args: &GetBucketNotificationArgs<'_>, @@ -2274,7 +2217,7 @@ impl Client { Ok(MakeBucketResponse { headers: resp.headers().clone(), region: region.to_string(), - bucket_name: args.bucket.to_string(), + bucket: args.bucket.to_string(), }) } @@ -2521,43 +2464,7 @@ impl Client { Ok(RemoveBucketResponse { headers: resp.headers().clone(), region: region.to_string(), - bucket_name: args.bucket.to_string(), - }) - } - - pub async fn set_bucket_lifecycle( - &self, - args: &SetBucketLifecycleArgs<'_>, - ) -> Result { - let region = self.get_region(args.bucket, args.region).await?; - - let mut headers = Multimap::new(); - if let Some(v) = &args.extra_headers { - merge(&mut headers, v); - } - - let mut query_params = Multimap::new(); - if let Some(v) = &args.extra_query_params { - merge(&mut query_params, v); - } - query_params.insert(String::from("lifecycle"), String::new()); - - let resp = self - .execute( - Method::PUT, - ®ion, - &mut headers, - &query_params, - Some(args.bucket), - None, - Some(args.config.to_xml().into()), - ) - .await?; - - Ok(SetBucketLifecycleResponse { - headers: resp.headers().clone(), - region: region.clone(), - bucket_name: args.bucket.to_string(), + bucket: args.bucket.to_string(), }) } @@ -2593,7 +2500,7 @@ impl Client { Ok(SetBucketNotificationResponse { headers: resp.headers().clone(), region: region.clone(), - bucket_name: args.bucket.to_string(), + bucket: args.bucket.to_string(), }) } @@ -2629,7 +2536,7 @@ impl Client { Ok(SetBucketPolicyResponse { headers: resp.headers().clone(), region: region.clone(), - bucket_name: args.bucket.to_string(), + bucket: args.bucket.to_string(), }) } @@ -2665,7 +2572,7 @@ impl Client { Ok(SetBucketReplicationResponse { headers: resp.headers().clone(), region: region.clone(), - bucket_name: args.bucket.to_string(), + bucket: args.bucket.to_string(), }) } @@ -2718,7 +2625,7 @@ impl Client { Ok(SetBucketTagsResponse { headers: resp.headers().clone(), region: region.clone(), - bucket_name: args.bucket.to_string(), + bucket: args.bucket.to_string(), }) } @@ -2754,7 +2661,7 @@ impl Client { Ok(SetObjectLockConfigResponse { headers: resp.headers().clone(), region: region.clone(), - bucket_name: args.bucket.to_string(), + bucket: args.bucket.to_string(), }) } @@ -2797,7 +2704,7 @@ impl Client { } data.push_str(""); - headers.insert(String::from("Content-MD5"), md5sum_hash(data.as_bytes())); + headers.insert(String::from("Content-MD5"), md5sum_hash(data.as_ref())); let resp = self .execute( diff --git a/src/s3/client/get_bucket_lifecycle.rs b/src/s3/client/get_bucket_lifecycle.rs new file mode 100644 index 0000000..2a42bd2 --- /dev/null +++ b/src/s3/client/get_bucket_lifecycle.rs @@ -0,0 +1,26 @@ +// MinIO Rust Library for Amazon S3 Compatible Cloud Storage +// Copyright 2025 MinIO, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! S3 APIs for bucket objects. + +use super::Client; +use crate::s3::builders::GetBucketLifecycle; + +impl Client { + /// Create a GetBucketLifecycle request builder. + pub fn get_bucket_lifecycle(&self, bucket: &str) -> GetBucketLifecycle { + GetBucketLifecycle::new(bucket).client(self) + } +} diff --git a/src/s3/client/set_bucket_lifecycle.rs b/src/s3/client/set_bucket_lifecycle.rs new file mode 100644 index 0000000..a4edb1f --- /dev/null +++ b/src/s3/client/set_bucket_lifecycle.rs @@ -0,0 +1,59 @@ +// MinIO Rust Library for Amazon S3 Compatible Cloud Storage +// Copyright 2025 MinIO, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! S3 APIs for bucket objects. + +use super::Client; +use crate::s3::builders::SetBucketLifecycle; + +impl Client { + /// Create a SetBucketLifecycle request builder. + /// + /// Returns argument for [set_bucket_lifecycle()](crate::s3::client::Client::set_bucket_lifecycle) API with given bucket name and configuration + /// + /// # Examples + /// + /// ```ignore + /// use minio::s3::Client; + /// use minio::s3::types::{Filter, LifecycleConfig, LifecycleRule, S3Api}; + /// + /// #[tokio::main] + /// async fn main() { + /// let rules: Vec = vec![LifecycleRule { + /// abort_incomplete_multipart_upload_days_after_initiation: None, + /// expiration_date: None, + /// expiration_days: Some(365), + /// expiration_expired_object_delete_marker: None, + /// filter: Filter {and_operator: None, prefix: Some(String::from("logs/")), tag: None}, + /// id: String::from("rule1"), + /// noncurrent_version_expiration_noncurrent_days: None, + /// noncurrent_version_transition_noncurrent_days: None, + /// noncurrent_version_transition_storage_class: None, + /// status: true, + /// transition_date: None, + /// transition_days: None, + /// transition_storage_class: None, + /// }]; + /// let client = Client::default(); + /// let _resp = client + /// .set_bucket_lifecycle("my-bucket-name") + /// .life_cycle_config(LifecycleConfig { rules }) + /// .send().await; + /// } + /// ``` + pub fn set_bucket_lifecycle(&self, bucket: &str) -> SetBucketLifecycle { + SetBucketLifecycle::new(bucket).client(self) + } +} diff --git a/src/s3/response.rs b/src/s3/response.rs index 02d01e6..b756600 100644 --- a/src/s3/response.rs +++ b/src/s3/response.rs @@ -24,14 +24,15 @@ use xmltree::Element; use crate::s3::error::Error; use crate::s3::types::{ - parse_legal_hold, LifecycleConfig, NotificationConfig, ObjectLockConfig, ReplicationConfig, - RetentionMode, SelectProgress, + parse_legal_hold, NotificationConfig, ObjectLockConfig, ReplicationConfig, RetentionMode, + SelectProgress, }; use crate::s3::utils::{ copy_slice, crc32, from_http_header_value, from_iso8601utc, get_text, uint32, UtcTime, }; mod get_bucket_encryption; +mod get_bucket_lifecycle; mod get_bucket_versioning; mod get_object; mod list_buckets; @@ -41,9 +42,11 @@ mod object_prompt; mod put_object; mod remove_objects; mod set_bucket_encryption; +mod set_bucket_lifecycle; mod set_bucket_versioning; pub use get_bucket_encryption::GetBucketEncryptionResponse; +pub use get_bucket_lifecycle::GetBucketLifecycleResponse; pub use get_bucket_versioning::GetBucketVersioningResponse; pub use get_object::GetObjectResponse; pub use list_buckets::ListBucketsResponse; @@ -57,6 +60,7 @@ pub use put_object::{ }; pub use remove_objects::{DeleteError, DeletedObject, RemoveObjectResponse, RemoveObjectsResponse}; pub use set_bucket_encryption::SetBucketEncryptionResponse; +pub use set_bucket_lifecycle::SetBucketLifecycleResponse; pub use set_bucket_versioning::SetBucketVersioningResponse; #[derive(Debug)] @@ -64,7 +68,7 @@ pub use set_bucket_versioning::SetBucketVersioningResponse; pub struct BucketResponse { pub headers: HeaderMap, pub region: String, - pub bucket_name: String, + pub bucket: String, } /// Response of [make_bucket()](crate::s3::client::Client::make_bucket) API @@ -577,18 +581,6 @@ pub struct IsObjectLegalHoldEnabledResponse { /// Response of [delete_bucket_lifecycle()](crate::s3::client::Client::delete_bucket_lifecycle) API pub type DeleteBucketLifecycleResponse = BucketResponse; -#[derive(Clone, Debug)] -/// Response of [get_bucket_lifecycle()](crate::s3::client::Client::get_bucket_lifecycle) API -pub struct GetBucketLifecycleResponse { - pub headers: HeaderMap, - pub region: String, - pub bucket_name: String, - pub config: LifecycleConfig, -} - -/// Response of [set_bucket_lifecycle()](crate::s3::client::Client::set_bucket_lifecycle) API -pub type SetBucketLifecycleResponse = BucketResponse; - /// Response of [delete_bucket_notification()](crate::s3::client::Client::delete_bucket_notification) API pub type DeleteBucketNotificationResponse = BucketResponse; diff --git a/src/s3/response/get_bucket_lifecycle.rs b/src/s3/response/get_bucket_lifecycle.rs new file mode 100644 index 0000000..ec0b30c --- /dev/null +++ b/src/s3/response/get_bucket_lifecycle.rs @@ -0,0 +1,56 @@ +// MinIO Rust Library for Amazon S3 Compatible Cloud Storage +// Copyright 2025 MinIO, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::s3::error::Error; +use crate::s3::types::{FromS3Response, LifecycleConfig, S3Request}; +use async_trait::async_trait; +use bytes::Buf; +use http::HeaderMap; +use xmltree::Element; + +/// Response of +/// [get_bucket_lifecycle()](crate::s3::client::Client::get_bucket_lifecycle) +/// API +#[derive(Clone, Debug)] +pub struct GetBucketLifecycleResponse { + pub headers: HeaderMap, + pub region: String, + pub bucket: String, + pub config: LifecycleConfig, +} + +#[async_trait] +impl FromS3Response for GetBucketLifecycleResponse { + async fn from_s3response<'a>( + req: S3Request<'a>, + resp: reqwest::Response, + ) -> Result { + let bucket: String = match req.bucket { + None => return Err(Error::InvalidBucketName("no bucket specified".to_string())), + Some(v) => v.to_string(), + }; + let headers = resp.headers().clone(); + let body = resp.bytes().await?; + let root = Element::parse(body.reader())?; + let config = LifecycleConfig::from_xml(&root)?; + + Ok(GetBucketLifecycleResponse { + headers, + region: req.get_computed_region(), + bucket, + config, + }) + } +} diff --git a/src/s3/response/set_bucket_encryption.rs b/src/s3/response/set_bucket_encryption.rs index 99d2bb9..28ecfd8 100644 --- a/src/s3/response/set_bucket_encryption.rs +++ b/src/s3/response/set_bucket_encryption.rs @@ -24,6 +24,7 @@ use xmltree::Element; /// Response of /// [set_bucket_encryption()](crate::s3::client::Client::set_bucket_encryption) /// API +/// TODO equal to GetBucketEncryptionResponse #[derive(Clone, Debug)] pub struct SetBucketEncryptionResponse { pub headers: HeaderMap, diff --git a/src/s3/response/set_bucket_lifecycle.rs b/src/s3/response/set_bucket_lifecycle.rs new file mode 100644 index 0000000..5553466 --- /dev/null +++ b/src/s3/response/set_bucket_lifecycle.rs @@ -0,0 +1,47 @@ +// MinIO Rust Library for Amazon S3 Compatible Cloud Storage +// Copyright 2025 MinIO, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::s3::error::Error; +use crate::s3::types::{FromS3Response, S3Request}; +use async_trait::async_trait; +use http::HeaderMap; + +/// Response of [set_bucket_lifecycle()](crate::s3::client::Client::set_bucket_lifecycle) API +#[derive(Debug)] +/// TODO is identical ot BucketResponse +pub struct SetBucketLifecycleResponse { + pub headers: HeaderMap, + pub region: String, + pub bucket: String, +} + +#[async_trait] +impl FromS3Response for SetBucketLifecycleResponse { + async fn from_s3response<'a>( + req: S3Request<'a>, + resp: reqwest::Response, + ) -> Result { + let bucket: String = match req.bucket { + None => return Err(Error::InvalidBucketName("no bucket specified".to_string())), + Some(v) => v.to_string(), + }; + + Ok(SetBucketLifecycleResponse { + headers: resp.headers().clone(), + region: req.get_computed_region(), + bucket, + }) + } +} diff --git a/src/s3/response/set_bucket_versioning.rs b/src/s3/response/set_bucket_versioning.rs index bb53a4a..3e265c3 100644 --- a/src/s3/response/set_bucket_versioning.rs +++ b/src/s3/response/set_bucket_versioning.rs @@ -14,12 +14,18 @@ // limitations under the License. use crate::s3::error::Error; -use crate::s3::response::BucketResponse; use crate::s3::types::{FromS3Response, S3Request}; use async_trait::async_trait; +use http::HeaderMap; -/// Response of [set_bucket_versioning()](Client::set_bucket_versioning) API -pub type SetBucketVersioningResponse = BucketResponse; +/// Response of [set_bucket_versioning()](crate::s3::client::Client::set_bucket_versioning) API +#[derive(Debug)] +/// TODO is identical ot BucketResponse +pub struct SetBucketVersioningResponse { + pub headers: HeaderMap, + pub region: String, + pub bucket: String, +} #[async_trait] impl FromS3Response for SetBucketVersioningResponse { @@ -35,7 +41,7 @@ impl FromS3Response for SetBucketVersioningResponse { Ok(SetBucketVersioningResponse { headers: resp.headers().clone(), region: req.get_computed_region(), - bucket_name: bucket, + bucket, }) } } diff --git a/src/s3/types.rs b/src/s3/types.rs index a29449e..c6f723c 100644 --- a/src/s3/types.rs +++ b/src/s3/types.rs @@ -1099,7 +1099,7 @@ impl LifecycleRule { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] /// Lifecycle configuration pub struct LifecycleConfig { pub rules: Vec,