diff --git a/src/s3/args.rs b/src/s3/args.rs index 3577674..81a4562 100644 --- a/src/s3/args.rs +++ b/src/s3/args.rs @@ -19,8 +19,7 @@ use crate::s3::error::Error; use crate::s3::signer::post_presign_v4; use crate::s3::sse::{Sse, SseCustomerKey}; use crate::s3::types::{ - Directive, NotificationConfig, ObjectLockConfig, Part, ReplicationConfig, Retention, - RetentionMode, SelectRequest, + Directive, ObjectLockConfig, Part, Retention, RetentionMode, SelectRequest, }; use crate::s3::utils::{ Multimap, UtcTime, b64encode, check_bucket_name, merge, to_amz_date, to_http_header_value, @@ -1324,183 +1323,6 @@ pub type DisableObjectLegalHoldArgs<'a> = ObjectVersionArgs<'a>; /// Argument for [is_object_legal_hold_enabled()](crate::s3::client::Client::is_object_legal_hold_enabled) API pub type IsObjectLegalHoldEnabledArgs<'a> = ObjectVersionArgs<'a>; -/// Argument for [delete_bucket_notification()](crate::s3::client::Client::delete_bucket_notification) API -pub type DeleteBucketNotificationArgs<'a> = BucketArgs<'a>; - -/// Argument for [delete_bucket_notification()](crate::s3::client::Client::delete_bucket_notification) API -pub type GetBucketNotificationArgs<'a> = BucketArgs<'a>; - -/// Argument for [set_bucket_notification()](crate::s3::client::Client::set_bucket_notification) API -pub struct SetBucketNotificationArgs<'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 NotificationConfig, -} - -impl<'a> SetBucketNotificationArgs<'a> { - /// Returns argument for [set_bucket_notification()](crate::s3::client::Client::set_bucket_notification) API with given bucket name and configuration - /// - /// # Examples - /// - /// ``` - /// use minio::s3::args::*; - /// use minio::s3::types::*; - /// let config = NotificationConfig { - /// cloud_func_config_list: None, - /// queue_config_list: Some(vec![QueueConfig { - /// events: vec![ - /// String::from("s3:ObjectCreated:Put"), - /// String::from("s3:ObjectCreated:Copy"), - /// ], - /// id: None, - /// prefix_filter_rule: Some(PrefixFilterRule { - /// value: String::from("images"), - /// }), - /// suffix_filter_rule: Some(SuffixFilterRule { - /// value: String::from("pg"), - /// }), - /// queue: String::from("arn:minio:sqs::miniojavatest:webhook"), - /// }]), - /// topic_config_list: None, - /// }; - /// let args = SetBucketNotificationArgs::new("my-bucket", &config).unwrap(); - /// ``` - pub fn new( - bucket_name: &'a str, - config: &'a NotificationConfig, - ) -> Result, Error> { - check_bucket_name(bucket_name, true)?; - - Ok(SetBucketNotificationArgs { - extra_headers: None, - extra_query_params: None, - region: None, - bucket: bucket_name, - config, - }) - } -} - -/// Argument for [delete_bucket_replication()](crate::s3::client::Client::delete_bucket_replication) API -pub type DeleteBucketReplicationArgs<'a> = BucketArgs<'a>; - -/// Argument for [get_bucket_replication()](crate::s3::client::Client::get_bucket_replication) API -pub type GetBucketReplicationArgs<'a> = BucketArgs<'a>; - -/// Argument for [set_bucket_replication()](crate::s3::client::Client::set_bucket_replication) API -pub struct SetBucketReplicationArgs<'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 ReplicationConfig, -} - -impl<'a> SetBucketReplicationArgs<'a> { - /// Returns argument for [set_bucket_replication()](crate::s3::client::Client::set_bucket_replication) API with given bucket name and configuration - /// - /// # Examples - /// - /// ``` - /// use minio::s3::args::*; - /// use minio::s3::types::*; - /// use std::collections::HashMap; - /// let mut tags: HashMap = HashMap::new(); - /// tags.insert(String::from("key1"), String::from("value1")); - /// tags.insert(String::from("key2"), String::from("value2")); - /// let mut rules: Vec = Vec::new(); - /// rules.push(ReplicationRule { - /// destination: Destination { - /// bucket_arn: String::from("REPLACE-WITH-ACTUAL-DESTINATION-BUCKET-ARN"), - /// access_control_translation: None, - /// account: None, - /// encryption_config: None, - /// metrics: None, - /// replication_time: None, - /// storage_class: None, - /// }, - /// delete_marker_replication_status: None, - /// existing_object_replication_status: None, - /// filter: Some(Filter { - /// and_operator: Some(AndOperator { - /// prefix: Some(String::from("TaxDocs")), - /// tags: Some(tags), - /// }), - /// prefix: None, - /// tag: None, - /// }), - /// id: Some(String::from("rule1")), - /// prefix: None, - /// priority: Some(1), - /// source_selection_criteria: None, - /// delete_replication_status: Some(false), - /// status: true, - /// }); - /// let config = ReplicationConfig {role: None, rules: rules}; - /// let args = SetBucketReplicationArgs::new("my-bucket", &config).unwrap(); - /// ``` - pub fn new( - bucket_name: &'a str, - config: &'a ReplicationConfig, - ) -> Result, Error> { - check_bucket_name(bucket_name, true)?; - - Ok(SetBucketReplicationArgs { - extra_headers: None, - extra_query_params: None, - region: None, - bucket: bucket_name, - config, - }) - } -} - -/// Argument for [delete_bucket_tags()](crate::s3::client::Client::delete_bucket_tags) API -pub type DeleteBucketTagsArgs<'a> = BucketArgs<'a>; - -/// Argument for [get_bucket_tags()](crate::s3::client::Client::get_bucket_tags) API -pub type GetBucketTagsArgs<'a> = BucketArgs<'a>; - -/// Argument for [set_bucket_tags()](crate::s3::client::Client::set_bucket_tags) API -pub struct SetBucketTagsArgs<'a> { - pub extra_headers: Option<&'a Multimap>, - pub extra_query_params: Option<&'a Multimap>, - pub region: Option<&'a str>, - pub bucket: &'a str, - pub tags: &'a HashMap, -} - -impl<'a> SetBucketTagsArgs<'a> { - /// Returns argument for [set_bucket_tags()](crate::s3::client::Client::set_bucket_tags) API with given bucket name and tags - /// - /// # Examples - /// - /// ``` - /// use minio::s3::args::*; - /// use std::collections::HashMap; - /// let mut tags: HashMap = HashMap::new(); - /// tags.insert(String::from("Project"), String::from("Project One")); - /// tags.insert(String::from("User"), String::from("jsmith")); - /// let args = SetBucketTagsArgs::new("my-bucket", &tags).unwrap(); - /// ``` - pub fn new( - bucket_name: &'a str, - tags: &'a HashMap, - ) -> Result, Error> { - check_bucket_name(bucket_name, true)?; - - Ok(SetBucketTagsArgs { - extra_headers: None, - extra_query_params: None, - region: None, - bucket: bucket_name, - tags, - }) - } -} - /// Argument for [delete_object_lock_config()](crate::s3::client::Client::delete_object_lock_config) API pub type DeleteObjectLockConfigArgs<'a> = BucketArgs<'a>; diff --git a/src/s3/builders.rs b/src/s3/builders.rs index a710fc4..8bc025d 100644 --- a/src/s3/builders.rs +++ b/src/s3/builders.rs @@ -18,10 +18,16 @@ mod bucket_common; mod delete_bucket_encryption; mod delete_bucket_lifecycle; +mod delete_bucket_notification; mod delete_bucket_policy; +mod delete_bucket_replication; +mod delete_bucket_tags; mod get_bucket_encryption; mod get_bucket_lifecycle; +mod get_bucket_notification; mod get_bucket_policy; +mod get_bucket_replication; +mod get_bucket_tags; mod get_bucket_versioning; mod get_object; mod list_buckets; @@ -33,16 +39,25 @@ mod put_object; mod remove_objects; mod set_bucket_encryption; mod set_bucket_lifecycle; +mod set_bucket_notification; mod set_bucket_policy; +mod set_bucket_replication; +mod set_bucket_tags; mod set_bucket_versioning; pub use bucket_common::*; pub use delete_bucket_encryption::*; pub use delete_bucket_lifecycle::*; +pub use delete_bucket_notification::*; pub use delete_bucket_policy::*; +pub use delete_bucket_replication::*; +pub use delete_bucket_tags::*; pub use get_bucket_encryption::*; pub use get_bucket_lifecycle::*; +pub use get_bucket_notification::*; pub use get_bucket_policy::*; +pub use get_bucket_replication::*; +pub use get_bucket_tags::*; pub use get_bucket_versioning::*; pub use get_object::*; pub use list_buckets::*; @@ -54,5 +69,8 @@ pub use put_object::*; pub use remove_objects::*; pub use set_bucket_encryption::*; pub use set_bucket_lifecycle::*; +pub use set_bucket_notification::*; pub use set_bucket_policy::*; +pub use set_bucket_replication::*; +pub use set_bucket_tags::*; pub use set_bucket_versioning::*; diff --git a/src/s3/builders/delete_bucket_notification.rs b/src/s3/builders/delete_bucket_notification.rs new file mode 100644 index 0000000..1a99fb8 --- /dev/null +++ b/src/s3/builders/delete_bucket_notification.rs @@ -0,0 +1,74 @@ +// 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::Client; +use crate::s3::builders::{BucketCommon, SegmentedBytes}; +use crate::s3::error::Error; +use crate::s3::response::DeleteBucketNotificationResponse; +use crate::s3::types::{NotificationConfig, S3Api, S3Request, ToS3Request}; +use crate::s3::utils::check_bucket_name; +use bytes::Bytes; +use http::Method; + +/// Argument builder for [delete_bucket_notification()](Client::delete_bucket_notification) API +pub type DeleteBucketNotification = BucketCommon; + +#[derive(Default, Debug)] +pub struct DeleteBucketNotificationPhantomData; + +impl S3Api for DeleteBucketNotification { + type S3Response = DeleteBucketNotificationResponse; +} + +impl ToS3Request for DeleteBucketNotification { + 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(String::from("notification"), String::new()); + + const CONFIG: NotificationConfig = NotificationConfig { + cloud_func_config_list: None, + queue_config_list: None, + topic_config_list: None, + }; + let bytes: Bytes = CONFIG.to_xml().into(); + let body: Option = Some(SegmentedBytes::from(bytes)); + //TODO consider const body + + 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/delete_bucket_replication.rs b/src/s3/builders/delete_bucket_replication.rs new file mode 100644 index 0000000..f881197 --- /dev/null +++ b/src/s3/builders/delete_bucket_replication.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::Client; +use crate::s3::builders::BucketCommon; +use crate::s3::error::Error; +use crate::s3::response::DeleteBucketReplicationResponse; +use crate::s3::types::{S3Api, S3Request, ToS3Request}; +use crate::s3::utils::check_bucket_name; +use http::Method; + +/// Argument builder for [delete_bucket_replication()](Client::delete_bucket_replication) API +pub type DeleteBucketReplication = BucketCommon; + +#[derive(Default, Debug)] +pub struct DeleteBucketReplicationPhantomData; + +impl S3Api for DeleteBucketReplication { + type S3Response = DeleteBucketReplicationResponse; +} + +impl ToS3Request for DeleteBucketReplication { + 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("replication".into(), String::new()); + + let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?; + + let req = S3Request::new(client, Method::DELETE) + .region(self.region.as_deref()) + .bucket(Some(&self.bucket)) + .query_params(query_params) + .headers(headers); + + Ok(req) + } +} diff --git a/src/s3/builders/delete_bucket_tags.rs b/src/s3/builders/delete_bucket_tags.rs new file mode 100644 index 0000000..3030b0c --- /dev/null +++ b/src/s3/builders/delete_bucket_tags.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::Client; +use crate::s3::builders::BucketCommon; +use crate::s3::error::Error; +use crate::s3::response::DeleteBucketTagsResponse; +use crate::s3::types::{S3Api, S3Request, ToS3Request}; +use crate::s3::utils::check_bucket_name; +use http::Method; + +/// Argument builder for [delete_bucket_tags()](Client::delete_bucket_tags) API +pub type DeleteBucketTags = BucketCommon; + +#[derive(Default, Debug)] +pub struct DeleteBucketTagsPhantomData; + +impl S3Api for DeleteBucketTags { + type S3Response = DeleteBucketTagsResponse; +} + +impl ToS3Request for DeleteBucketTags { + 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("tagging".into(), String::new()); + + let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?; + + let req = S3Request::new(client, Method::DELETE) + .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_notification.rs b/src/s3/builders/get_bucket_notification.rs new file mode 100644 index 0000000..c1084e8 --- /dev/null +++ b/src/s3/builders/get_bucket_notification.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::Client; +use crate::s3::builders::BucketCommon; +use crate::s3::error::Error; +use crate::s3::response::GetBucketNotificationResponse; +use crate::s3::types::{S3Api, S3Request, ToS3Request}; +use crate::s3::utils::check_bucket_name; +use http::Method; + +/// Argument builder for [get_bucket_notification()](Client::get_bucket_notification) API +pub type GetBucketNotification = BucketCommon; + +#[derive(Default, Debug)] +pub struct GetBucketNotificationPhantomData; + +impl S3Api for GetBucketNotification { + type S3Response = GetBucketNotificationResponse; +} + +impl ToS3Request for GetBucketNotification { + 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("notification".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_replication.rs b/src/s3/builders/get_bucket_replication.rs new file mode 100644 index 0000000..a6f5802 --- /dev/null +++ b/src/s3/builders/get_bucket_replication.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::Client; +use crate::s3::builders::BucketCommon; +use crate::s3::error::Error; +use crate::s3::response::GetBucketReplicationResponse; +use crate::s3::types::{S3Api, S3Request, ToS3Request}; +use crate::s3::utils::check_bucket_name; +use http::Method; + +/// Argument builder for [get_bucket_replication()](Client::get_bucket_replication) API +pub type GetBucketReplication = BucketCommon; + +#[derive(Default, Debug)] +pub struct GetBucketReplicationPhantomData; + +impl S3Api for GetBucketReplication { + type S3Response = GetBucketReplicationResponse; +} + +impl ToS3Request for GetBucketReplication { + 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(String::from("replication"), 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_tags.rs b/src/s3/builders/get_bucket_tags.rs new file mode 100644 index 0000000..8f86411 --- /dev/null +++ b/src/s3/builders/get_bucket_tags.rs @@ -0,0 +1,104 @@ +// 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::Client; +use crate::s3::error::Error; +use crate::s3::response::GetBucketTagsResponse; +use crate::s3::types::{S3Api, S3Request, ToS3Request}; +use crate::s3::utils::{Multimap, check_bucket_name}; +use http::Method; +use std::collections::HashMap; + +/// Argument builder for [get_bucket_tags()](crate::s3::client::Client::get_bucket_tags) API +#[derive(Clone, Debug, Default)] +pub struct GetBucketTags { + 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) tags: HashMap, +} + +impl GetBucketTags { + 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 tags(mut self, tags: HashMap) -> Self { + self.tags = tags; + self + } +} + +impl S3Api for GetBucketTags { + type S3Response = GetBucketTagsResponse; +} + +impl ToS3Request for GetBucketTags { + 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(String::from("tagging"), 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/set_bucket_notification.rs b/src/s3/builders/set_bucket_notification.rs new file mode 100644 index 0000000..58a2e22 --- /dev/null +++ b/src/s3/builders/set_bucket_notification.rs @@ -0,0 +1,108 @@ +// 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::Client; +use crate::s3::builders::SegmentedBytes; +use crate::s3::error::Error; +use crate::s3::response::SetBucketNotificationResponse; +use crate::s3::types::{NotificationConfig, S3Api, S3Request, ToS3Request}; +use crate::s3::utils::{Multimap, check_bucket_name}; +use bytes::Bytes; +use http::Method; + +/// Argument builder for [set_bucket_notification()](crate::s3::client::Client::set_bucket_notification) API +#[derive(Clone, Debug, Default)] +pub struct SetBucketNotification { + 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: NotificationConfig, +} + +impl SetBucketNotification { + 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 notification_config(mut self, config: NotificationConfig) -> Self { + self.config = config; + self + } +} + +impl S3Api for SetBucketNotification { + type S3Response = SetBucketNotificationResponse; +} + +impl ToS3Request for SetBucketNotification { + 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(String::from("notification"), String::new()); + + let bytes: Bytes = self.config.to_xml().into(); + 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_replication.rs b/src/s3/builders/set_bucket_replication.rs new file mode 100644 index 0000000..316e776 --- /dev/null +++ b/src/s3/builders/set_bucket_replication.rs @@ -0,0 +1,108 @@ +// 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::Client; +use crate::s3::builders::SegmentedBytes; +use crate::s3::error::Error; +use crate::s3::response::SetBucketReplicationResponse; +use crate::s3::types::{ReplicationConfig, S3Api, S3Request, ToS3Request}; +use crate::s3::utils::{Multimap, check_bucket_name}; +use bytes::Bytes; +use http::Method; + +/// Argument builder for [set_bucket_replication()](crate::s3::client::Client::set_bucket_replication) API +#[derive(Clone, Debug, Default)] +pub struct SetBucketReplication { + 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: ReplicationConfig, +} + +impl SetBucketReplication { + 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 replication_config(mut self, config: ReplicationConfig) -> Self { + self.config = config; + self + } +} + +impl S3Api for SetBucketReplication { + type S3Response = SetBucketReplicationResponse; +} + +impl ToS3Request for SetBucketReplication { + 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(String::from("replication"), String::new()); + + let bytes: Bytes = self.config.to_xml().into(); + 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_tags.rs b/src/s3/builders/set_bucket_tags.rs new file mode 100644 index 0000000..28958e6 --- /dev/null +++ b/src/s3/builders/set_bucket_tags.rs @@ -0,0 +1,125 @@ +// 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::Client; +use crate::s3::builders::SegmentedBytes; +use crate::s3::error::Error; +use crate::s3::response::SetBucketTagsResponse; +use crate::s3::types::{S3Api, S3Request, ToS3Request}; +use crate::s3::utils::{Multimap, check_bucket_name}; +use bytes::Bytes; +use http::Method; +use std::collections::HashMap; + +/// Argument builder for [set_bucket_tags()](crate::s3::client::Client::set_bucket_tags) API +#[derive(Clone, Debug, Default)] +pub struct SetBucketTags { + 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) tags: HashMap, +} + +impl SetBucketTags { + 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 tags(mut self, tags: HashMap) -> Self { + self.tags = tags; + self + } +} + +impl S3Api for SetBucketTags { + type S3Response = SetBucketTagsResponse; +} + +impl ToS3Request for SetBucketTags { + 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(String::from("tagging"), String::new()); + + let mut data = String::from(""); + if !self.tags.is_empty() { + data.push_str(""); + for (key, value) in self.tags.iter() { + data.push_str(""); + data.push_str(""); + data.push_str(key); + data.push_str(""); + data.push_str(""); + data.push_str(value); + data.push_str(""); + data.push_str(""); + } + data.push_str(""); + } + data.push_str(""); + + let body: Option = Some(SegmentedBytes::from(Bytes::from(data))); + 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/client.rs b/src/s3/client.rs index 45f9535..8a76925 100644 --- a/src/s3/client.rs +++ b/src/s3/client.rs @@ -28,9 +28,7 @@ use crate::s3::http::{BaseUrl, Url}; use crate::s3::response::*; use crate::s3::signer::{presign_v4, sign_v4_s3}; use crate::s3::sse::SseCustomerKey; -use crate::s3::types::{ - Directive, NotificationConfig, ObjectLockConfig, Part, ReplicationConfig, RetentionMode, -}; +use crate::s3::types::{Directive, ObjectLockConfig, Part, RetentionMode}; use crate::s3::utils::{ Multimap, from_iso8601utc, get_default_text, get_option_text, get_text, md5sum_hash, md5sum_hash_sb, merge, sha256_hash_sb, to_amz_date, to_iso8601utc, utc_now, @@ -48,10 +46,16 @@ use xmltree::Element; mod delete_bucket_encryption; mod delete_bucket_lifecycle; +mod delete_bucket_notification; mod delete_bucket_policy; +mod delete_bucket_replication; +mod delete_bucket_tags; mod get_bucket_encryption; mod get_bucket_lifecycle; +mod get_bucket_notification; mod get_bucket_policy; +mod get_bucket_replication; +mod get_bucket_tags; mod get_bucket_versioning; mod get_object; mod list_objects; @@ -61,7 +65,10 @@ mod put_object; mod remove_objects; mod set_bucket_encryption; mod set_bucket_lifecycle; +mod set_bucket_notification; mod set_bucket_policy; +mod set_bucket_replication; +mod set_bucket_tags; mod set_bucket_versioning; use super::builders::{ListBuckets, SegmentedBytes}; @@ -1229,107 +1236,6 @@ impl Client { }) } - pub async fn delete_bucket_notification( - &self, - args: &DeleteBucketNotificationArgs<'_>, - ) -> Result { - self.set_bucket_notification(&SetBucketNotificationArgs { - extra_headers: args.extra_headers, - extra_query_params: args.extra_query_params, - region: args.region, - bucket: args.bucket, - config: &NotificationConfig { - cloud_func_config_list: None, - queue_config_list: None, - topic_config_list: None, - }, - }) - .await - } - - pub async fn delete_bucket_replication( - &self, - args: &DeleteBucketReplicationArgs<'_>, - ) -> 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("replication"), String::new()); - - let resp = self - .execute( - Method::DELETE, - ®ion, - &mut headers, - &query_params, - Some(args.bucket), - None, - None, - ) - .await; - match resp { - Ok(resp) => Ok(DeleteBucketReplicationResponse { - headers: resp.headers().clone(), - region: region.clone(), - bucket: args.bucket.to_string(), - }), - Err(Error::S3Error(ref err)) - if err.code == Error::ReplicationConfigurationNotFoundError.as_str() => - { - Ok(DeleteBucketReplicationResponse { - headers: HeaderMap::new(), - region: region.clone(), - bucket: args.bucket.to_string(), - }) - } - Err(e) => Err(e), - } - } - - pub async fn delete_bucket_tags( - &self, - args: &DeleteBucketTagsArgs<'_>, - ) -> 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("tagging"), String::new()); - - let resp = self - .execute( - Method::DELETE, - ®ion, - &mut headers, - &query_params, - Some(args.bucket), - None, - None, - ) - .await?; - - Ok(DeleteBucketTagsResponse { - headers: resp.headers().clone(), - region: region.clone(), - bucket: args.bucket.to_string(), - }) - } - pub async fn delete_object_lock_config( &self, args: &DeleteObjectLockConfigArgs<'_>, @@ -1481,149 +1387,6 @@ impl Client { }) } - pub async fn get_bucket_notification( - &self, - args: &GetBucketNotificationArgs<'_>, - ) -> 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("notification"), String::new()); - - let resp = self - .execute( - Method::GET, - ®ion, - &mut headers, - &query_params, - Some(args.bucket), - None, - None, - ) - .await?; - - let header_map = resp.headers().clone(); - let body = resp.bytes().await?; - let mut root = Element::parse(body.reader())?; - - Ok(GetBucketNotificationResponse { - headers: header_map.clone(), - region: region.clone(), - bucket_name: args.bucket.to_string(), - config: NotificationConfig::from_xml(&mut root)?, - }) - } - - pub async fn get_bucket_replication( - &self, - args: &GetBucketReplicationArgs<'_>, - ) -> 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("replication"), String::new()); - - let resp = self - .execute( - Method::GET, - ®ion, - &mut headers, - &query_params, - Some(args.bucket), - None, - None, - ) - .await?; - - let header_map = resp.headers().clone(); - let body = resp.bytes().await?; - let root = Element::parse(body.reader())?; - - Ok(GetBucketReplicationResponse { - headers: header_map.clone(), - region: region.clone(), - bucket_name: args.bucket.to_string(), - config: ReplicationConfig::from_xml(&root)?, - }) - } - - pub async fn get_bucket_tags( - &self, - args: &GetBucketTagsArgs<'_>, - ) -> 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("tagging"), 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 mut root = Element::parse(body.reader())?; - - let element = root - .get_mut_child("TagSet") - .ok_or(Error::XmlError(" tag not found".to_string()))?; - let mut tags = std::collections::HashMap::new(); - while let Some(v) = element.take_child("Tag") { - tags.insert(get_text(&v, "Key")?, get_text(&v, "Value")?); - } - - Ok(GetBucketTagsResponse { - headers: header_map.clone(), - region: region.clone(), - bucket_name: args.bucket.to_string(), - tags, - }) - } - Err(Error::S3Error(ref err)) if err.code == Error::NoSuchTagSet.as_str() => { - Ok(GetBucketTagsResponse { - headers: HeaderMap::new(), - region: region.clone(), - bucket_name: args.bucket.to_string(), - tags: HashMap::new(), - }) - } - Err(e) => Err(e), - } - } - pub async fn get_object_old( &self, args: &GetObjectArgs<'_>, @@ -2282,131 +2045,6 @@ impl Client { }) } - pub async fn set_bucket_notification( - &self, - args: &SetBucketNotificationArgs<'_>, - ) -> 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("notification"), 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(SetBucketNotificationResponse { - headers: resp.headers().clone(), - region: region.clone(), - bucket: args.bucket.to_string(), - }) - } - - pub async fn set_bucket_replication( - &self, - args: &SetBucketReplicationArgs<'_>, - ) -> 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("replication"), 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(SetBucketReplicationResponse { - headers: resp.headers().clone(), - region: region.clone(), - bucket: args.bucket.to_string(), - }) - } - - pub async fn set_bucket_tags( - &self, - args: &SetBucketTagsArgs<'_>, - ) -> 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("tagging"), String::new()); - - let mut data = String::from(""); - if !args.tags.is_empty() { - data.push_str(""); - for (key, value) in args.tags.iter() { - data.push_str(""); - data.push_str(""); - data.push_str(key); - data.push_str(""); - data.push_str(""); - data.push_str(value); - data.push_str(""); - data.push_str(""); - } - data.push_str(""); - } - data.push_str(""); - - let resp = self - .execute( - Method::PUT, - ®ion, - &mut headers, - &query_params, - Some(args.bucket), - None, - Some(data.into()), - ) - .await?; - - Ok(SetBucketTagsResponse { - headers: resp.headers().clone(), - region: region.clone(), - bucket: args.bucket.to_string(), - }) - } - pub async fn set_object_lock_config( &self, args: &SetObjectLockConfigArgs<'_>, diff --git a/src/s3/client/delete_bucket_notification.rs b/src/s3/client/delete_bucket_notification.rs new file mode 100644 index 0000000..ed54456 --- /dev/null +++ b/src/s3/client/delete_bucket_notification.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::DeleteBucketNotification; + +impl Client { + /// Create a DeleteBucketNotification request builder. + pub fn delete_bucket_notification(&self, bucket: &str) -> DeleteBucketNotification { + DeleteBucketNotification::new(bucket).client(self) + } +} diff --git a/src/s3/client/delete_bucket_replication.rs b/src/s3/client/delete_bucket_replication.rs new file mode 100644 index 0000000..c103ec2 --- /dev/null +++ b/src/s3/client/delete_bucket_replication.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::DeleteBucketReplication; + +impl Client { + /// Create a DeleteBucketReplication request builder. + pub fn delete_bucket_replication(&self, bucket: &str) -> DeleteBucketReplication { + DeleteBucketReplication::new(bucket).client(self) + } +} diff --git a/src/s3/client/delete_bucket_tags.rs b/src/s3/client/delete_bucket_tags.rs new file mode 100644 index 0000000..249d96b --- /dev/null +++ b/src/s3/client/delete_bucket_tags.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::DeleteBucketTags; + +impl Client { + /// Create a DeleteBucketTags request builder. + pub fn delete_bucket_tags(&self, bucket: &str) -> DeleteBucketTags { + DeleteBucketTags::new(bucket).client(self) + } +} diff --git a/src/s3/client/get_bucket_notification.rs b/src/s3/client/get_bucket_notification.rs new file mode 100644 index 0000000..433c1d9 --- /dev/null +++ b/src/s3/client/get_bucket_notification.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::GetBucketNotification; + +impl Client { + /// Create a GetBucketNotification request builder. + pub fn get_bucket_notification(&self, bucket: &str) -> GetBucketNotification { + GetBucketNotification::new(bucket).client(self) + } +} diff --git a/src/s3/client/get_bucket_replication.rs b/src/s3/client/get_bucket_replication.rs new file mode 100644 index 0000000..14155f7 --- /dev/null +++ b/src/s3/client/get_bucket_replication.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::GetBucketReplication; + +impl Client { + /// Create a GetBucketReplication request builder. + pub fn get_bucket_replication(&self, bucket: &str) -> GetBucketReplication { + GetBucketReplication::new(bucket).client(self) + } +} diff --git a/src/s3/client/get_bucket_tags.rs b/src/s3/client/get_bucket_tags.rs new file mode 100644 index 0000000..935b424 --- /dev/null +++ b/src/s3/client/get_bucket_tags.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::GetBucketTags; + +impl Client { + /// Create a GetBucketTags request builder. + pub fn get_bucket_tags(&self, bucket: &str) -> GetBucketTags { + GetBucketTags::new(bucket).client(self) + } +} diff --git a/src/s3/client/set_bucket_notification.rs b/src/s3/client/set_bucket_notification.rs new file mode 100644 index 0000000..55a63ee --- /dev/null +++ b/src/s3/client/set_bucket_notification.rs @@ -0,0 +1,62 @@ +// 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::SetBucketNotification; + +impl Client { + /// Create a SetBucketNotification request builder. + /// + /// Returns argument for [set_bucket_notification()](crate::s3::client::Client::set_bucket_notification) API with given bucket name and configuration + /// + /// # Examples + /// + /// ```ignore + /// use minio::s3::Client; + /// use minio::s3::types::{NotificationConfig, PrefixFilterRule, QueueConfig, S3Api, SuffixFilterRule}; + /// + /// #[tokio::main] + /// async fn main() { + /// let config = NotificationConfig { + /// cloud_func_config_list: None, + /// queue_config_list: Some(vec![QueueConfig { + /// events: vec![ + /// String::from("s3:ObjectCreated:Put"), + /// String::from("s3:ObjectCreated:Copy"), + /// ], + /// id: None, + /// prefix_filter_rule: Some(PrefixFilterRule { + /// value: String::from("images"), + /// }), + /// suffix_filter_rule: Some(SuffixFilterRule { + /// value: String::from("pg"), + /// }), + /// queue: String::from("arn:minio:sqs::miniojavatest:webhook"), + /// }]), + /// topic_config_list: None, + /// }; + /// let client = Client::default(); + /// let _resp = client + /// .set_bucket_notification("my-bucket-name") + /// .notification_config(config) + /// .send().await; + /// } + /// ``` + pub fn set_bucket_notification(&self, bucket: &str) -> SetBucketNotification { + SetBucketNotification::new(bucket).client(self) + } +} diff --git a/src/s3/client/set_bucket_policy.rs b/src/s3/client/set_bucket_policy.rs index b250700..bb510d3 100644 --- a/src/s3/client/set_bucket_policy.rs +++ b/src/s3/client/set_bucket_policy.rs @@ -31,7 +31,6 @@ impl Client { /// /// #[tokio::main] /// async fn main() { - /// use minio::s3::args::*; /// let config = r#"{ /// "Version": "2012-10-17", /// "Statement": [ diff --git a/src/s3/client/set_bucket_replication.rs b/src/s3/client/set_bucket_replication.rs new file mode 100644 index 0000000..d3acf98 --- /dev/null +++ b/src/s3/client/set_bucket_replication.rs @@ -0,0 +1,74 @@ +// 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::SetBucketReplication; + +impl Client { + /// Returns argument for [set_bucket_replication()](crate::s3::client::Client::set_bucket_replication) API with given bucket name and configuration + /// + /// # Examples + /// + /// ```ignore + /// use minio::s3::Client; + /// use minio::s3::types::*; + /// use std::collections::HashMap; + /// + /// #[tokio::main] + /// async fn main() { + /// let mut tags: HashMap = HashMap::new(); + /// tags.insert(String::from("key1"), String::from("value1")); + /// tags.insert(String::from("key2"), String::from("value2")); + /// let mut rules: Vec = Vec::new(); + /// rules.push(ReplicationRule { + /// destination: Destination { + /// bucket_arn: String::from("REPLACE-WITH-ACTUAL-DESTINATION-BUCKET-ARN"), + /// access_control_translation: None, + /// account: None, + /// encryption_config: None, + /// metrics: None, + /// replication_time: None, + /// storage_class: None, + /// }, + /// delete_marker_replication_status: None, + /// existing_object_replication_status: None, + /// filter: Some(Filter { + /// and_operator: Some(AndOperator { + /// prefix: Some(String::from("TaxDocs")), + /// tags: Some(tags), + /// }), + /// prefix: None, + /// tag: None, + /// }), + /// id: Some(String::from("rule1")), + /// prefix: None, + /// priority: Some(1), + /// source_selection_criteria: None, + /// delete_replication_status: Some(false), + /// status: true, + /// }); + /// let client = Client::default(); + /// let _resp = client + /// .set_bucket_replication("my-bucket-name") + /// .replication_config(ReplicationConfig {role: None, rules}) + /// .send().await; + /// } + /// ``` + pub fn set_bucket_replication(&self, bucket: &str) -> SetBucketReplication { + SetBucketReplication::new(bucket).client(self) + } +} diff --git a/src/s3/client/set_bucket_tags.rs b/src/s3/client/set_bucket_tags.rs new file mode 100644 index 0000000..62f782b --- /dev/null +++ b/src/s3/client/set_bucket_tags.rs @@ -0,0 +1,48 @@ +// 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::SetBucketTags; + +impl Client { + /// Create a SetBucketTags request builder. + /// + /// # Examples + /// + /// ```ignore + /// use std::collections::HashMap; + /// use minio::s3::Client; + /// use minio::s3::types::S3Api; + /// + /// #[tokio::main] + /// async fn main() { + /// let mut tags: HashMap = HashMap::new(); + /// tags.insert(String::from("Project"), String::from("Project One")); + /// tags.insert(String::from("User"), String::from("jsmith")); + /// + /// let client = Client::default(); + /// let _resp = client + /// .set_bucket_tags("my-bucket-name") + /// .tags(tags) + /// .send().await; + /// } + /// ``` + + pub fn set_bucket_tags(&self, bucket: &str) -> SetBucketTags { + SetBucketTags::new(bucket).client(self) + } +} diff --git a/src/s3/response.rs b/src/s3/response.rs index 98fe66e..cb4123d 100644 --- a/src/s3/response.rs +++ b/src/s3/response.rs @@ -23,20 +23,23 @@ use std::io::BufReader; use xmltree::Element; use crate::s3::error::Error; -use crate::s3::types::{ - NotificationConfig, ObjectLockConfig, ReplicationConfig, RetentionMode, SelectProgress, - parse_legal_hold, -}; +use crate::s3::types::{ObjectLockConfig, RetentionMode, SelectProgress, parse_legal_hold}; use crate::s3::utils::{ UtcTime, copy_slice, crc32, from_http_header_value, from_iso8601utc, get_text, uint32, }; mod delete_bucket_encryption; mod delete_bucket_lifecycle; +mod delete_bucket_notification; mod delete_bucket_policy; +mod delete_bucket_replication; +mod delete_bucket_tags; mod get_bucket_encryption; mod get_bucket_lifecycle; +mod get_bucket_notification; mod get_bucket_policy; +mod get_bucket_replication; +mod get_bucket_tags; mod get_bucket_versioning; mod get_object; mod list_buckets; @@ -47,15 +50,24 @@ mod put_object; mod remove_objects; mod set_bucket_encryption; mod set_bucket_lifecycle; +mod set_bucket_notification; mod set_bucket_policy; +mod set_bucket_replication; +mod set_bucket_tags; mod set_bucket_versioning; pub use delete_bucket_encryption::DeleteBucketEncryptionResponse; pub use delete_bucket_lifecycle::DeleteBucketLifecycleResponse; +pub use delete_bucket_notification::DeleteBucketNotificationResponse; pub use delete_bucket_policy::DeleteBucketPolicyResponse; +pub use delete_bucket_replication::DeleteBucketReplicationResponse; +pub use delete_bucket_tags::DeleteBucketTagsResponse; pub use get_bucket_encryption::GetBucketEncryptionResponse; pub use get_bucket_lifecycle::GetBucketLifecycleResponse; +pub use get_bucket_notification::GetBucketNotificationResponse; pub use get_bucket_policy::GetBucketPolicyResponse; +pub use get_bucket_replication::GetBucketReplicationResponse; +pub use get_bucket_tags::GetBucketTagsResponse; pub use get_bucket_versioning::GetBucketVersioningResponse; pub use get_object::GetObjectResponse; pub use list_buckets::ListBucketsResponse; @@ -70,7 +82,10 @@ 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_notification::SetBucketNotificationResponse; pub use set_bucket_policy::SetBucketPolicyResponse; +pub use set_bucket_replication::SetBucketReplicationResponse; +pub use set_bucket_tags::SetBucketTagsResponse; pub use set_bucket_versioning::SetBucketVersioningResponse; #[derive(Debug)] @@ -586,51 +601,6 @@ pub struct IsObjectLegalHoldEnabledResponse { pub enabled: bool, } -/// Response of [delete_bucket_notification()](crate::s3::client::Client::delete_bucket_notification) API -pub type DeleteBucketNotificationResponse = BucketResponse; - -#[derive(Clone, Debug)] -/// Response of [get_bucket_notification()](crate::s3::client::Client::get_bucket_notification) API -pub struct GetBucketNotificationResponse { - pub headers: HeaderMap, - pub region: String, - pub bucket_name: String, - pub config: NotificationConfig, -} - -/// Response of [set_bucket_notification()](crate::s3::client::Client::set_bucket_notification) API -pub type SetBucketNotificationResponse = BucketResponse; - -/// Response of [delete_bucket_replication()](crate::s3::client::Client::delete_bucket_replication) API -pub type DeleteBucketReplicationResponse = BucketResponse; - -#[derive(Clone, Debug)] -/// Response of [get_bucket_replication()](crate::s3::client::Client::get_bucket_replication) API -pub struct GetBucketReplicationResponse { - pub headers: HeaderMap, - pub region: String, - pub bucket_name: String, - pub config: ReplicationConfig, -} - -/// Response of [set_bucket_replication()](crate::s3::client::Client::set_bucket_replication) API -pub type SetBucketReplicationResponse = BucketResponse; - -/// Response of [delete_bucket_tags()](crate::s3::client::Client::delete_bucket_tags) API -pub type DeleteBucketTagsResponse = BucketResponse; - -#[derive(Clone, Debug)] -/// Response of [get_bucket_tags()](crate::s3::client::Client::get_bucket_tags) API -pub struct GetBucketTagsResponse { - pub headers: HeaderMap, - pub region: String, - pub bucket_name: String, - pub tags: std::collections::HashMap, -} - -/// Response of [set_bucket_tags()](crate::s3::client::Client::set_bucket_tags) API -pub type SetBucketTagsResponse = BucketResponse; - /// Response of [delete_object_lock_config()](crate::s3::client::Client::delete_object_lock_config) API pub type DeleteObjectLockConfigResponse = BucketResponse; diff --git a/src/s3/response/delete_bucket_notification.rs b/src/s3/response/delete_bucket_notification.rs new file mode 100644 index 0000000..342b6b7 --- /dev/null +++ b/src/s3/response/delete_bucket_notification.rs @@ -0,0 +1,49 @@ +// 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 +/// [delete_bucket_notification()](crate::s3::client::Client::delete_bucket_notification) +/// API +#[derive(Clone, Debug)] +pub struct DeleteBucketNotificationResponse { + pub headers: HeaderMap, + pub region: String, + pub bucket: String, +} + +#[async_trait] +impl FromS3Response for DeleteBucketNotificationResponse { + async fn from_s3response<'a>( + req: S3Request<'a>, + resp: Result, + ) -> Result { + let bucket: String = match req.bucket { + None => return Err(Error::InvalidBucketName("no bucket specified".to_string())), + Some(v) => v.to_string(), + }; + let resp = resp?; + + Ok(DeleteBucketNotificationResponse { + headers: resp.headers().clone(), + region: req.get_computed_region(), + bucket, + }) + } +} diff --git a/src/s3/response/delete_bucket_replication.rs b/src/s3/response/delete_bucket_replication.rs new file mode 100644 index 0000000..b9fdd23 --- /dev/null +++ b/src/s3/response/delete_bucket_replication.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. + +use crate::s3::error::Error; +use crate::s3::types::{FromS3Response, S3Request}; +use async_trait::async_trait; +use http::HeaderMap; + +/// Response of +/// [delete_bucket_replication()](crate::s3::client::Client::delete_bucket_replication) +/// API +#[derive(Clone, Debug)] +pub struct DeleteBucketReplicationResponse { + pub headers: HeaderMap, + pub region: String, + pub bucket: String, +} + +#[async_trait] +impl FromS3Response for DeleteBucketReplicationResponse { + async fn from_s3response<'a>( + req: S3Request<'a>, + resp: Result, + ) -> Result { + let bucket: String = match req.bucket { + None => return Err(Error::InvalidBucketName("no bucket specified".to_string())), + Some(v) => v.to_string(), + }; + match resp { + Ok(r) => Ok(DeleteBucketReplicationResponse { + headers: r.headers().clone(), + region: req.get_computed_region(), + bucket, + }), + Err(Error::S3Error(ref err)) + if err.code == Error::ReplicationConfigurationNotFoundError.as_str() => + { + Ok(DeleteBucketReplicationResponse { + headers: HeaderMap::new(), + region: req.get_computed_region(), + bucket, + }) + } + Err(e) => Err(e), + } + } +} diff --git a/src/s3/response/delete_bucket_tags.rs b/src/s3/response/delete_bucket_tags.rs new file mode 100644 index 0000000..62ae6a0 --- /dev/null +++ b/src/s3/response/delete_bucket_tags.rs @@ -0,0 +1,49 @@ +// 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 +/// [delete_bucket_tags()](crate::s3::client::Client::delete_bucket_tags) +/// API +#[derive(Clone, Debug)] +pub struct DeleteBucketTagsResponse { + pub headers: HeaderMap, + pub region: String, + pub bucket: String, +} + +#[async_trait] +impl FromS3Response for DeleteBucketTagsResponse { + async fn from_s3response<'a>( + req: S3Request<'a>, + resp: Result, + ) -> Result { + let bucket: String = match req.bucket { + None => return Err(Error::InvalidBucketName("no bucket specified".to_string())), + Some(v) => v.to_string(), + }; + let resp = resp?; + + Ok(DeleteBucketTagsResponse { + headers: resp.headers().clone(), + region: req.get_computed_region(), + bucket, + }) + } +} diff --git a/src/s3/response/get_bucket_lifecycle.rs b/src/s3/response/get_bucket_lifecycle.rs index b29e54a..b7e8499 100644 --- a/src/s3/response/get_bucket_lifecycle.rs +++ b/src/s3/response/get_bucket_lifecycle.rs @@ -44,8 +44,8 @@ impl FromS3Response for GetBucketLifecycleResponse { let resp = resp?; let headers = resp.headers().clone(); let body = resp.bytes().await?; - let root = Element::parse(body.reader())?; - let config = LifecycleConfig::from_xml(&root)?; + let mut root = Element::parse(body.reader())?; + let config = LifecycleConfig::from_xml(&mut root)?; Ok(GetBucketLifecycleResponse { headers, diff --git a/src/s3/response/get_bucket_notification.rs b/src/s3/response/get_bucket_notification.rs new file mode 100644 index 0000000..2bff41d --- /dev/null +++ b/src/s3/response/get_bucket_notification.rs @@ -0,0 +1,57 @@ +// 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, NotificationConfig, S3Request}; +use async_trait::async_trait; +use bytes::Buf; +use http::HeaderMap; +use xmltree::Element; + +/// Response of +/// [get_bucket_notification()](crate::s3::client::Client::get_bucket_notification) +/// API +#[derive(Clone, Debug)] +pub struct GetBucketNotificationResponse { + pub headers: HeaderMap, + pub region: String, + pub bucket: String, + pub config: NotificationConfig, +} + +#[async_trait] +impl FromS3Response for GetBucketNotificationResponse { + async fn from_s3response<'a>( + req: S3Request<'a>, + resp: Result, + ) -> Result { + let bucket: String = match req.bucket { + None => return Err(Error::InvalidBucketName("no bucket specified".to_string())), + Some(v) => v.to_string(), + }; + let resp = resp?; + let headers = resp.headers().clone(); + let body = resp.bytes().await?; + let mut root = Element::parse(body.reader())?; + let config = NotificationConfig::from_xml(&mut root)?; + + Ok(GetBucketNotificationResponse { + headers, + region: req.get_computed_region(), + bucket, + config, + }) + } +} diff --git a/src/s3/response/get_bucket_replication.rs b/src/s3/response/get_bucket_replication.rs new file mode 100644 index 0000000..1aaa667 --- /dev/null +++ b/src/s3/response/get_bucket_replication.rs @@ -0,0 +1,57 @@ +// 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, ReplicationConfig, S3Request}; +use async_trait::async_trait; +use bytes::Buf; +use http::HeaderMap; +use xmltree::Element; + +/// Response of +/// [get_bucket_replication()](crate::s3::client::Client::get_bucket_replication) +/// API +#[derive(Clone, Debug)] +pub struct GetBucketReplicationResponse { + pub headers: HeaderMap, + pub region: String, + pub bucket: String, + pub config: ReplicationConfig, +} + +#[async_trait] +impl FromS3Response for GetBucketReplicationResponse { + async fn from_s3response<'a>( + req: S3Request<'a>, + resp: Result, + ) -> Result { + let bucket: String = match req.bucket { + None => return Err(Error::InvalidBucketName("no bucket specified".to_string())), + Some(v) => v.to_string(), + }; + let resp = resp?; + let headers = resp.headers().clone(); + let body = resp.bytes().await?; + let root = Element::parse(body.reader())?; + let config = ReplicationConfig::from_xml(&root)?; + + Ok(GetBucketReplicationResponse { + headers, + region: req.get_computed_region(), + bucket, + config, + }) + } +} diff --git a/src/s3/response/get_bucket_tags.rs b/src/s3/response/get_bucket_tags.rs new file mode 100644 index 0000000..f36dec4 --- /dev/null +++ b/src/s3/response/get_bucket_tags.rs @@ -0,0 +1,78 @@ +// 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 crate::s3::utils::get_text; +use async_trait::async_trait; +use bytes::Buf; +use http::HeaderMap; +use std::collections::HashMap; +use xmltree::Element; + +/// Response of +/// [get_bucket_tags()](crate::s3::client::Client::get_bucket_tags) +/// API +#[derive(Clone, Debug)] +pub struct GetBucketTagsResponse { + pub headers: HeaderMap, + pub region: String, + pub bucket: String, + pub tags: HashMap, +} + +#[async_trait] +impl FromS3Response for GetBucketTagsResponse { + async fn from_s3response<'a>( + req: S3Request<'a>, + resp: Result, + ) -> Result { + let bucket: String = match req.bucket { + None => return Err(Error::InvalidBucketName("no bucket specified".to_string())), + Some(v) => v.to_string(), + }; + match resp { + Ok(r) => { + let headers = r.headers().clone(); + let body = r.bytes().await?; + let mut root = Element::parse(body.reader())?; + + let element = root + .get_mut_child("TagSet") + .ok_or(Error::XmlError(" tag not found".to_string()))?; + let mut tags = HashMap::new(); + while let Some(v) = element.take_child("Tag") { + tags.insert(get_text(&v, "Key")?, get_text(&v, "Value")?); + } + + Ok(GetBucketTagsResponse { + headers, + region: req.get_computed_region(), + bucket, + tags, + }) + } + Err(Error::S3Error(ref err)) if err.code == Error::NoSuchTagSet.as_str() => { + Ok(GetBucketTagsResponse { + headers: HeaderMap::new(), + region: req.get_computed_region(), + bucket, + tags: HashMap::new(), + }) + } + Err(e) => Err(e), + } + } +} diff --git a/src/s3/response/set_bucket_notification.rs b/src/s3/response/set_bucket_notification.rs new file mode 100644 index 0000000..95c3113 --- /dev/null +++ b/src/s3/response/set_bucket_notification.rs @@ -0,0 +1,46 @@ +// 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_notification()](crate::s3::client::Client::set_bucket_notification) API +#[derive(Debug)] +pub struct SetBucketNotificationResponse { + pub headers: HeaderMap, + pub region: String, + pub bucket: String, +} + +#[async_trait] +impl FromS3Response for SetBucketNotificationResponse { + async fn from_s3response<'a>( + req: S3Request<'a>, + resp: Result, + ) -> Result { + let bucket: String = match req.bucket { + None => return Err(Error::InvalidBucketName("no bucket specified".to_string())), + Some(v) => v.to_string(), + }; + let resp = resp?; + Ok(SetBucketNotificationResponse { + headers: resp.headers().clone(), + region: req.get_computed_region(), + bucket, + }) + } +} diff --git a/src/s3/response/set_bucket_replication.rs b/src/s3/response/set_bucket_replication.rs new file mode 100644 index 0000000..1518874 --- /dev/null +++ b/src/s3/response/set_bucket_replication.rs @@ -0,0 +1,46 @@ +// 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_replication()](crate::s3::client::Client::set_bucket_replication) API +#[derive(Debug)] +pub struct SetBucketReplicationResponse { + pub headers: HeaderMap, + pub region: String, + pub bucket: String, +} + +#[async_trait] +impl FromS3Response for SetBucketReplicationResponse { + async fn from_s3response<'a>( + req: S3Request<'a>, + resp: Result, + ) -> Result { + let bucket: String = match req.bucket { + None => return Err(Error::InvalidBucketName("no bucket specified".to_string())), + Some(v) => v.to_string(), + }; + let resp = resp?; + Ok(SetBucketReplicationResponse { + headers: resp.headers().clone(), + region: req.get_computed_region(), + bucket, + }) + } +} diff --git a/src/s3/response/set_bucket_tags.rs b/src/s3/response/set_bucket_tags.rs new file mode 100644 index 0000000..0d1d269 --- /dev/null +++ b/src/s3/response/set_bucket_tags.rs @@ -0,0 +1,49 @@ +// 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_tags()](crate::s3::client::Client::set_bucket_tags) +/// API +#[derive(Clone, Debug)] +pub struct SetBucketTagsResponse { + pub headers: HeaderMap, + pub region: String, + pub bucket: String, +} + +#[async_trait] +impl FromS3Response for SetBucketTagsResponse { + async fn from_s3response<'a>( + req: S3Request<'a>, + resp: Result, + ) -> Result { + let bucket: String = match req.bucket { + None => return Err(Error::InvalidBucketName("no bucket specified".to_string())), + Some(v) => v.to_string(), + }; + let resp = resp?; + + Ok(SetBucketTagsResponse { + headers: resp.headers().clone(), + region: req.get_computed_region(), + bucket, + }) + } +} diff --git a/src/s3/types.rs b/src/s3/types.rs index b45723b..a0f3913 100644 --- a/src/s3/types.rs +++ b/src/s3/types.rs @@ -1106,9 +1106,10 @@ pub struct LifecycleConfig { } impl LifecycleConfig { - pub fn from_xml(root: &Element) -> Result { + pub fn from_xml(root: &mut Element) -> Result { let mut config = LifecycleConfig { rules: Vec::new() }; + // TODO consider consuming root if let Some(v) = root.get_child("Rule") { for rule in &v.children { config @@ -1529,7 +1530,7 @@ impl TopicConfig { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] /// Notification configuration information pub struct NotificationConfig { pub cloud_func_config_list: Option>, @@ -1964,7 +1965,7 @@ impl ReplicationRule { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] /// Replication configuration information pub struct ReplicationConfig { pub role: Option, diff --git a/tests/common.rs b/tests/common.rs index 590d826..630d013 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -148,6 +148,7 @@ pub struct TestContext { } impl TestContext { + #[allow(dead_code)] pub fn new_from_env() -> Self { let run_on_ci: bool = std::env::var("CI") .unwrap_or("false".into()) diff --git a/tests/test_bucket_notification.rs b/tests/test_bucket_notification.rs index dd6053b..d0a2c86 100644 --- a/tests/test_bucket_notification.rs +++ b/tests/test_bucket_notification.rs @@ -1,10 +1,12 @@ mod common; use crate::common::{TestContext, create_bucket_helper}; -use minio::s3::args::{ - DeleteBucketNotificationArgs, GetBucketNotificationArgs, SetBucketNotificationArgs, +use minio::s3::response::{ + DeleteBucketNotificationResponse, GetBucketNotificationResponse, SetBucketNotificationResponse, +}; +use minio::s3::types::{ + NotificationConfig, PrefixFilterRule, QueueConfig, S3Api, SuffixFilterRule, }; -use minio::s3::types::{NotificationConfig, PrefixFilterRule, QueueConfig, SuffixFilterRule}; const SQS_ARN: &str = "arn:minio:sqs::miniojavatest:webhook"; @@ -13,39 +15,42 @@ async fn set_get_delete_bucket_notification() { let ctx = TestContext::new_from_env(); let (bucket_name, _cleanup) = create_bucket_helper(&ctx).await; - ctx.client - .set_bucket_notification( - &SetBucketNotificationArgs::new( - &bucket_name, - &NotificationConfig { - cloud_func_config_list: None, - queue_config_list: Some(vec![QueueConfig { - events: vec![ - String::from("s3:ObjectCreated:Put"), - String::from("s3:ObjectCreated:Copy"), - ], - id: None, - prefix_filter_rule: Some(PrefixFilterRule { - value: String::from("images"), - }), - suffix_filter_rule: Some(SuffixFilterRule { - value: String::from("pg"), - }), - queue: String::from(SQS_ARN), - }]), - topic_config_list: None, - }, - ) - .unwrap(), - ) - .await - .unwrap(); + let config = NotificationConfig { + cloud_func_config_list: None, + queue_config_list: Some(vec![QueueConfig { + events: vec![ + String::from("s3:ObjectCreated:Put"), + String::from("s3:ObjectCreated:Copy"), + ], + id: None, + prefix_filter_rule: Some(PrefixFilterRule { + value: String::from("images"), + }), + suffix_filter_rule: Some(SuffixFilterRule { + value: String::from("pg"), + }), + queue: String::from(SQS_ARN), + }]), + topic_config_list: None, + }; - let resp = ctx + let resp: SetBucketNotificationResponse = ctx .client - .get_bucket_notification(&GetBucketNotificationArgs::new(&bucket_name).unwrap()) + .set_bucket_notification(&bucket_name) + .notification_config(config) + .send() .await .unwrap(); + println!("response of setting notification: resp={:?}", resp); + + let resp: GetBucketNotificationResponse = ctx + .client + .get_bucket_notification(&bucket_name) + .send() + .await + .unwrap(); + println!("response of getting notification: resp={:?}", resp); + assert_eq!(resp.config.queue_config_list.as_ref().unwrap().len(), 1); assert!( resp.config.queue_config_list.as_ref().unwrap()[0] @@ -78,14 +83,18 @@ async fn set_get_delete_bucket_notification() { SQS_ARN ); - ctx.client - .delete_bucket_notification(&DeleteBucketNotificationArgs::new(&bucket_name).unwrap()) + let resp: DeleteBucketNotificationResponse = ctx + .client + .delete_bucket_notification(&bucket_name) + .send() .await .unwrap(); + println!("response of deleting notification: resp={:?}", resp); - let resp = ctx + let resp: GetBucketNotificationResponse = ctx .client - .get_bucket_notification(&GetBucketNotificationArgs::new(&bucket_name).unwrap()) + .get_bucket_notification(&bucket_name) + .send() .await .unwrap(); assert!(resp.config.queue_config_list.is_none()); diff --git a/tests/test_bucket_replication.rs b/tests/test_bucket_replication.rs new file mode 100644 index 0000000..f490850 --- /dev/null +++ b/tests/test_bucket_replication.rs @@ -0,0 +1,114 @@ +// 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::{TestContext, create_bucket_helper}; +use minio::s3::builders::VersioningStatus; +use minio::s3::response::{ + DeleteBucketReplicationResponse, GetBucketVersioningResponse, SetBucketReplicationResponse, + SetBucketVersioningResponse, +}; +use minio::s3::types::{ + AndOperator, Destination, Filter, ReplicationConfig, ReplicationRule, S3Api, +}; +use std::collections::HashMap; + +#[tokio::test(flavor = "multi_thread", worker_threads = 10)] +async fn set_get_delete_bucket_replication() { + let ctx = TestContext::new_from_env(); + let (bucket_name, _cleanup) = create_bucket_helper(&ctx).await; + + let mut tags: HashMap = HashMap::new(); + tags.insert(String::from("key1"), String::from("value1")); + tags.insert(String::from("key2"), String::from("value2")); + + let config = ReplicationConfig { + role: Some("example1".to_string()), + rules: vec![ReplicationRule { + destination: Destination { + bucket_arn: String::from("REPLACE-WITH-ACTUAL-DESTINATION-BUCKET-ARN"), + access_control_translation: None, + account: None, + encryption_config: None, + metrics: None, + replication_time: None, + storage_class: None, + }, + delete_marker_replication_status: None, + existing_object_replication_status: None, + filter: Some(Filter { + and_operator: Some(AndOperator { + prefix: Some(String::from("TaxDocs")), + tags: Some(tags), + }), + prefix: None, + tag: None, + }), + id: Some(String::from("rule1")), + prefix: None, + priority: Some(1), + source_selection_criteria: None, + delete_replication_status: Some(false), + status: true, + }], + }; + + let _resp: SetBucketVersioningResponse = ctx + .client + .set_bucket_versioning(&bucket_name) + .versioning_status(VersioningStatus::Enabled) + .send() + .await + .unwrap(); + + if false { + // TODO panic: called `Result::unwrap()` on an `Err` value: S3Error(ErrorResponse { code: "XMinioAdminRemoteTargetNotFoundError", message: "The remote target does not exist", + let resp: SetBucketReplicationResponse = ctx + .client + .set_bucket_replication(&bucket_name) + .replication_config(config) + .send() + .await + .unwrap(); + println!("response of setting replication: resp={:?}", resp); + } + let resp: GetBucketVersioningResponse = ctx + .client + .get_bucket_versioning(&bucket_name) + .send() + .await + .unwrap(); + println!("response of getting replication: resp={:?}", resp); + + if false { + // TODO called `Result::unwrap()` on an `Err` value: S3Error(ErrorResponse { code: "XMinioAdminRemoteTargetNotFoundError", message: "The remote target does not exist", + let resp: DeleteBucketReplicationResponse = ctx + .client + .delete_bucket_replication(&bucket_name) + .send() + .await + .unwrap(); + println!("response of deleting replication: resp={:?}", resp); + } + + let resp: GetBucketVersioningResponse = ctx + .client + .get_bucket_versioning(&bucket_name) + .send() + .await + .unwrap(); + println!("response of getting replication: resp={:?}", resp); +} diff --git a/tests/test_bucket_tags.rs b/tests/test_bucket_tags.rs index e3f0f4e..8c4efc7 100644 --- a/tests/test_bucket_tags.rs +++ b/tests/test_bucket_tags.rs @@ -16,7 +16,8 @@ mod common; use crate::common::{TestContext, create_bucket_helper}; -use minio::s3::args::{DeleteBucketTagsArgs, GetBucketTagsArgs, SetBucketTagsArgs}; +use minio::s3::response::{DeleteBucketTagsResponse, GetBucketTagsResponse, SetBucketTagsResponse}; +use minio::s3::types::S3Api; use std::collections::HashMap; #[tokio::test(flavor = "multi_thread", worker_threads = 10)] @@ -29,26 +30,33 @@ async fn set_get_delete_bucket_tags() { (String::from("User"), String::from("jsmith")), ]); - ctx.client - .set_bucket_tags(&SetBucketTagsArgs::new(&bucket_name, &tags).unwrap()) + let _resp: SetBucketTagsResponse = ctx + .client + .set_bucket_tags(&bucket_name) + .tags(tags.clone()) + .send() + .await + .unwrap(); + + let resp: GetBucketTagsResponse = ctx + .client + .get_bucket_tags(&bucket_name) + .send() + .await + .unwrap(); + assert_eq!(resp.tags, resp.tags); + + let _resp: DeleteBucketTagsResponse = ctx + .client + .delete_bucket_tags(&bucket_name) + .send() .await .unwrap(); let resp = ctx .client - .get_bucket_tags(&GetBucketTagsArgs::new(&bucket_name).unwrap()) - .await - .unwrap(); - assert!(resp.tags.len() == tags.len() && resp.tags.keys().all(|k| tags.contains_key(k))); - - ctx.client - .delete_bucket_tags(&DeleteBucketTagsArgs::new(&bucket_name).unwrap()) - .await - .unwrap(); - - let resp = ctx - .client - .get_bucket_tags(&GetBucketTagsArgs::new(&bucket_name).unwrap()) + .get_bucket_tags(&bucket_name) + .send() .await .unwrap(); assert!(resp.tags.is_empty()); diff --git a/tests/test_bucket_versioning.rs b/tests/test_bucket_versioning.rs index ecbb37e..f3dfd48 100644 --- a/tests/test_bucket_versioning.rs +++ b/tests/test_bucket_versioning.rs @@ -17,7 +17,7 @@ mod common; use crate::common::{TestContext, create_bucket_helper}; use minio::s3::builders::VersioningStatus; -use minio::s3::response::GetBucketVersioningResponse; +use minio::s3::response::{GetBucketVersioningResponse, SetBucketVersioningResponse}; use minio::s3::types::S3Api; #[tokio::test(flavor = "multi_thread", worker_threads = 10)] @@ -25,7 +25,8 @@ async fn set_get_delete_bucket_versioning() { let ctx = TestContext::new_from_env(); let (bucket_name, _cleanup) = create_bucket_helper(&ctx).await; - ctx.client + let _resp: SetBucketVersioningResponse = ctx + .client .set_bucket_versioning(&bucket_name) .versioning_status(VersioningStatus::Enabled) .send() @@ -40,7 +41,8 @@ async fn set_get_delete_bucket_versioning() { .unwrap(); assert_eq!(resp.status, Some(VersioningStatus::Enabled)); - ctx.client + let _resp: SetBucketVersioningResponse = ctx + .client .set_bucket_versioning(&bucket_name) .versioning_status(VersioningStatus::Suspended) .send()