diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 2694803..4f21c6f 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -20,12 +20,11 @@ jobs: - name: Build run: | cargo fmt --all -- --check - cargo clippy --all-targets --all-features -- -A clippy::result_large_err -A clippy::type_complexity -A clippy::too_many_arguments + cargo clippy --all-targets --all-features cargo build --bins --examples --tests --benches --verbose - name: Run tests run: | - truncate --size=6M asiaphotos-2015.zip ./tests/start-server.sh export SERVER_ENDPOINT=localhost:9000 export ACCESS_KEY=minioadmin diff --git a/examples/common.rs b/examples/common.rs new file mode 100644 index 0000000..4616f9b --- /dev/null +++ b/examples/common.rs @@ -0,0 +1,48 @@ +use minio::s3::args::{BucketExistsArgs, MakeBucketArgs}; +use minio::s3::creds::StaticProvider; +use minio::s3::http::BaseUrl; +use minio::s3::{Client, ClientBuilder}; + +#[allow(dead_code)] +pub fn create_client_on_play() -> Result> { + let base_url = "https://play.min.io".parse::()?; + log::info!("Trying to connect to MinIO at: `{:?}`", base_url); + + let static_provider = StaticProvider::new( + "Q3AM3UQ867SPQQA43P2F", + "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", + None, + ); + + let client = ClientBuilder::new(base_url.clone()) + .provider(Some(Box::new(static_provider))) + .build()?; + Ok(client) +} + +pub async fn create_bucket_if_not_exists( + bucket_name: &str, + client: &Client, +) -> Result<(), Box> { + // Check 'bucket_name' bucket exist or not. + let exists: bool = client + .bucket_exists(&BucketExistsArgs::new(bucket_name).unwrap()) + .await + .unwrap(); + + // Make 'bucket_name' bucket if not exist. + if !exists { + client + .make_bucket(&MakeBucketArgs::new(bucket_name).unwrap()) + .await + .unwrap(); + }; + Ok(()) +} + +#[allow(dead_code)] +#[tokio::main] +async fn main() -> Result<(), Box> { + // dummy code just to prevent an error because files in examples need to have a main + Ok(()) +} diff --git a/examples/file_downloader.rs b/examples/file_downloader.rs index ed27731..450228e 100644 --- a/examples/file_downloader.rs +++ b/examples/file_downloader.rs @@ -1,27 +1,30 @@ -use minio::s3::args::{BucketExistsArgs, MakeBucketArgs}; +// 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::builders::ObjectContent; -use minio::s3::client::ClientBuilder; -use minio::s3::creds::StaticProvider; -use minio::s3::http::BaseUrl; use minio::s3::types::S3Api; +use minio::s3::Client; use std::path::Path; #[tokio::main] async fn main() -> Result<(), Box> { env_logger::init(); // Note: set environment variable RUST_LOG="INFO" to log info and higher - - let base_url = "https://play.min.io".parse::()?; - log::info!("Trying to connect to MinIO at: `{:?}`", base_url); - - let static_provider = StaticProvider::new( - "Q3AM3UQ867SPQQA43P2F", - "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", - None, - ); - - let client = ClientBuilder::new(base_url.clone()) - .provider(Some(Box::new(static_provider))) - .build()?; + let client: Client = create_client_on_play()?; let bucket_name: &str = "file-download-rust-bucket"; let object_name: &str = "cat.png"; @@ -30,19 +33,7 @@ async fn main() -> Result<(), Box> { let filename: &Path = Path::new("./examples/cat.png"); let download_path: &str = &format!("/tmp/downloads/{object_name}"); - // Check 'bucket_name' bucket exist or not. - let exists: bool = client - .bucket_exists(&BucketExistsArgs::new(bucket_name).unwrap()) - .await - .unwrap(); - - // Make 'bucket_name' bucket if not exist. - if !exists { - client - .make_bucket(&MakeBucketArgs::new(bucket_name).unwrap()) - .await - .unwrap(); - } + create_bucket_if_not_exists(bucket_name, &client).await?; if filename.exists() { log::info!("File '{}' exists.", &filename.to_str().unwrap()); @@ -64,10 +55,7 @@ async fn main() -> Result<(), Box> { let get_object = client.get_object(bucket_name, object_name).send().await?; - get_object - .content - .to_file(&Path::new(download_path)) - .await?; + get_object.content.to_file(Path::new(download_path)).await?; log::info!("Object '{object_name}' is successfully downloaded to file '{download_path}'."); diff --git a/examples/file_uploader.rs b/examples/file_uploader.rs index 454c983..af7294f 100644 --- a/examples/file_uploader.rs +++ b/examples/file_uploader.rs @@ -12,46 +12,20 @@ // 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 minio::s3::args::{BucketExistsArgs, MakeBucketArgs}; +use crate::common::{create_bucket_if_not_exists, create_client_on_play}; use minio::s3::builders::ObjectContent; -use minio::s3::client::ClientBuilder; -use minio::s3::creds::StaticProvider; -use minio::s3::http::BaseUrl; +use minio::s3::Client; use std::path::Path; #[tokio::main] async fn main() -> Result<(), Box> { env_logger::init(); // Note: set environment variable RUST_LOG="INFO" to log info and higher - - let base_url = "https://play.min.io".parse::()?; - log::info!("Trying to connect to MinIO at: `{:?}`", base_url); - - let static_provider = StaticProvider::new( - "Q3AM3UQ867SPQQA43P2F", - "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", - None, - ); - - let client = ClientBuilder::new(base_url.clone()) - .provider(Some(Box::new(static_provider))) - .build()?; + let client: Client = create_client_on_play()?; let bucket_name: &str = "file-upload-rust-bucket"; - - // Check 'bucket_name' bucket exist or not. - let exists: bool = client - .bucket_exists(&BucketExistsArgs::new(bucket_name).unwrap()) - .await - .unwrap(); - - // Make 'bucket_name' bucket if not exist. - if !exists { - client - .make_bucket(&MakeBucketArgs::new(bucket_name).unwrap()) - .await - .unwrap(); - } + create_bucket_if_not_exists(bucket_name, &client).await?; // File we are going to upload to the bucket let filename: &Path = Path::new("./examples/cat.png"); diff --git a/examples/get_bucket_encryption.rs b/examples/get_bucket_encryption.rs new file mode 100644 index 0000000..4ecccc3 --- /dev/null +++ b/examples/get_bucket_encryption.rs @@ -0,0 +1,35 @@ +// 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::common::{create_bucket_if_not_exists, create_client_on_play}; +use minio::s3::builders::GetBucketEncryption; +use minio::s3::Client; + +mod common; + +#[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 = "encryption-rust-bucket"; + create_bucket_if_not_exists(bucket_name, &client).await?; + + let be: GetBucketEncryption = client.get_bucket_encryption(bucket_name); + + log::info!("{:?}", be); + + Ok(()) +} diff --git a/examples/get_bucket_versioning.rs b/examples/get_bucket_versioning.rs new file mode 100644 index 0000000..9ad1e84 --- /dev/null +++ b/examples/get_bucket_versioning.rs @@ -0,0 +1,35 @@ +// 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::builders::GetBucketVersioning; +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 = "versioning-rust-bucket"; + create_bucket_if_not_exists(bucket_name, &client).await?; + + let bv: GetBucketVersioning = client.get_bucket_versioning(bucket_name); + + log::info!("{:?}", bv); + + Ok(()) +} diff --git a/examples/object_prompt.rs b/examples/object_prompt.rs index 4210e04..b4d81ea 100644 --- a/examples/object_prompt.rs +++ b/examples/object_prompt.rs @@ -13,7 +13,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use minio::s3::args::{BucketExistsArgs, MakeBucketArgs}; +mod common; + +use crate::common::create_bucket_if_not_exists; use minio::s3::builders::{ObjectContent, ObjectPrompt}; use minio::s3::client::ClientBuilder; use minio::s3::creds::StaticProvider; @@ -25,14 +27,11 @@ use std::path::Path; async fn main() -> Result<(), Box> { env_logger::init(); // Note: set environment variable RUST_LOG="INFO" to log info and higher - let base_url = "https://play.min.io".parse::()?; + //Note: object prompt is not supported on play.min.io, you will need point to AIStor + let base_url = "http://localhost:9000".parse::()?; log::info!("Trying to connect to MinIO at: `{:?}`", base_url); - let static_provider = StaticProvider::new( - "Q3AM3UQ867SPQQA43P2F", - "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", - None, - ); + let static_provider = StaticProvider::new("admin", "admin", None); let client = ClientBuilder::new(base_url.clone()) .provider(Some(Box::new(static_provider))) @@ -40,20 +39,7 @@ async fn main() -> Result<(), Box> { .build()?; let bucket_name: &str = "object-prompt-rust-bucket"; - - // Check 'bucket_name' bucket exist or not. - let exists: bool = client - .bucket_exists(&BucketExistsArgs::new(bucket_name).unwrap()) - .await - .unwrap(); - - // Make 'bucket_name' bucket if not exist. - if !exists { - client - .make_bucket(&MakeBucketArgs::new(bucket_name).unwrap()) - .await - .unwrap(); - } + create_bucket_if_not_exists(bucket_name, &client).await?; // File we are going to upload to the bucket let filename: &Path = Path::new("./examples/cat.png"); diff --git a/src/s3/args.rs b/src/s3/args.rs index 580dc83..a46dd46 100644 --- a/src/s3/args.rs +++ b/src/s3/args.rs @@ -1318,9 +1318,6 @@ impl<'a> ComposeObjectArgs<'a> { /// Argument for [delete_bucket_encryption()](crate::s3::client::Client::delete_bucket_encryption) API pub type DeleteBucketEncryptionArgs<'a> = BucketArgs<'a>; -/// Argument for [get_bucket_encryption()](crate::s3::client::Client::get_bucket_encryption) API -pub type GetBucketEncryptionArgs<'a> = BucketArgs<'a>; - #[derive(Clone, Debug)] /// Argument for [set_bucket_encryption()](crate::s3::client::Client::set_bucket_encryption) API pub struct SetBucketEncryptionArgs<'a> { diff --git a/src/s3/builders.rs b/src/s3/builders.rs index 9df7c40..7e59bbf 100644 --- a/src/s3/builders.rs +++ b/src/s3/builders.rs @@ -15,8 +15,11 @@ //! Argument builders for [minio::s3::client::Client](crate::s3::client::Client) APIs -mod buckets; +mod bucket_common; +mod get_bucket_encryption; +mod get_bucket_versioning; mod get_object; +mod list_buckets; mod list_objects; mod listen_bucket_notification; mod object_content; @@ -24,8 +27,11 @@ mod object_prompt; mod put_object; mod remove_objects; -pub use buckets::*; +pub use bucket_common::*; +pub use get_bucket_encryption::*; +pub use get_bucket_versioning::*; pub use get_object::*; +pub use list_buckets::*; pub use list_objects::*; pub use listen_bucket_notification::*; pub use object_content::*; diff --git a/src/s3/builders/bucket_common.rs b/src/s3/builders/bucket_common.rs new file mode 100644 index 0000000..092f230 --- /dev/null +++ b/src/s3/builders/bucket_common.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 std::marker::PhantomData; + +use crate::s3::{client::Client, utils::Multimap}; + +#[derive(Clone, Debug, Default)] +pub struct BucketCommon { + pub(crate) client: Option, + + pub(crate) extra_headers: Option, + pub(crate) extra_query_params: Option, + pub(crate) region: Option, + pub(crate) bucket: String, + + _operation: PhantomData, +} + +impl BucketCommon { + pub fn new(bucket: &str) -> BucketCommon { + BucketCommon { + 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 + } +} diff --git a/src/s3/builders/buckets.rs b/src/s3/builders/buckets.rs deleted file mode 100644 index f4f2b10..0000000 --- a/src/s3/builders/buckets.rs +++ /dev/null @@ -1,140 +0,0 @@ -use http::Method; - -use crate::s3::{ - client::Client, - error::Error, - response::{GetBucketVersioningResponse, ListBucketsResponse}, - types::{S3Api, S3Request, ToS3Request}, - utils::{check_bucket_name, merge, Multimap}, -}; - -/// Argument builder for -/// [list_buckets()](crate::s3::client::Client::list_buckets) API. -#[derive(Clone, Debug, Default)] -pub struct ListBuckets { - client: Option, - - extra_headers: Option, - extra_query_params: Option, -} - -impl S3Api for ListBuckets { - type S3Response = ListBucketsResponse; -} - -impl ToS3Request for ListBuckets { - fn to_s3request(&self) -> Result { - let mut headers = Multimap::new(); - if let Some(v) = &self.extra_headers { - headers = v.clone(); - } - let mut query_params = Multimap::new(); - if let Some(v) = &self.extra_query_params { - query_params = v.clone(); - } - - let req = S3Request::new( - self.client.as_ref().ok_or(Error::NoClientProvided)?, - Method::GET, - ) - .query_params(query_params) - .headers(headers); - Ok(req) - } -} - -impl ListBuckets { - pub fn new() -> Self { - 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 - } -} - -#[derive(Clone, Debug, Default)] -pub struct BucketCommon { - client: Option, - - extra_headers: Option, - extra_query_params: Option, - region: Option, - bucket: String, -} - -impl BucketCommon { - pub fn new(bucket_name: &str) -> Self { - BucketCommon { - bucket: bucket_name.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 - } -} - -/// Argument builder for -/// [get_bucket_versioning()](crate::s3::client::Client::get_bucket_versioning) -/// API -pub type GetBucketVersioning = BucketCommon; - -impl S3Api for GetBucketVersioning { - type S3Response = GetBucketVersioningResponse; -} - -impl ToS3Request for GetBucketVersioning { - fn to_s3request(&self) -> Result { - check_bucket_name(&self.bucket, true)?; - - let mut headers = Multimap::new(); - if let Some(v) = &self.extra_headers { - merge(&mut headers, v); - } - - let mut query_params = Multimap::new(); - if let Some(v) = &self.extra_query_params { - merge(&mut query_params, v); - } - query_params.insert(String::from("versioning"), String::new()); - - let req = S3Request::new( - self.client.as_ref().ok_or(Error::NoClientProvided)?, - 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_encryption.rs b/src/s3/builders/get_bucket_encryption.rs new file mode 100644 index 0000000..5916872 --- /dev/null +++ b/src/s3/builders/get_bucket_encryption.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::builders::BucketCommon; +use crate::s3::error::Error; +use crate::s3::response::GetBucketEncryptionResponse; +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 +pub type GetBucketEncryption = BucketCommon; + +#[derive(Default, Debug)] +pub struct GetBucketEncryptionPhantomData; + +impl S3Api for GetBucketEncryption { + type S3Response = GetBucketEncryptionResponse; +} + +impl ToS3Request for GetBucketEncryption { + fn to_s3request(&self) -> Result { + check_bucket_name(&self.bucket, true)?; + let mut headers = Multimap::new(); + if let Some(v) = &self.extra_headers { + merge(&mut headers, v); + } + + let mut query_params = Multimap::new(); + if let Some(v) = &self.extra_query_params { + merge(&mut query_params, v); + } + query_params.insert(String::from("encryption"), String::new()); + + let req = S3Request::new( + self.client.as_ref().ok_or(Error::NoClientProvided)?, + 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 new file mode 100644 index 0000000..2742a56 --- /dev/null +++ b/src/s3/builders/get_bucket_versioning.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::builders::BucketCommon; +use crate::s3::error::Error; +use crate::s3::response::GetBucketVersioningResponse; +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 +pub type GetBucketVersioning = BucketCommon; + +#[derive(Default, Debug)] +pub struct GetBucketVersioningPhantomData; + +impl S3Api for GetBucketVersioning { + type S3Response = GetBucketVersioningResponse; +} + +impl ToS3Request for GetBucketVersioning { + fn to_s3request(&self) -> Result { + check_bucket_name(&self.bucket, true)?; + let mut headers = Multimap::new(); + if let Some(v) = &self.extra_headers { + merge(&mut headers, v); + } + + let mut query_params = Multimap::new(); + if let Some(v) = &self.extra_query_params { + merge(&mut query_params, v); + } + query_params.insert(String::from("versioning"), String::new()); + + let req = S3Request::new( + self.client.as_ref().ok_or(Error::NoClientProvided)?, + 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_object.rs b/src/s3/builders/get_object.rs index 69f2030..d295c9c 100644 --- a/src/s3/builders/get_object.rs +++ b/src/s3/builders/get_object.rs @@ -24,6 +24,7 @@ use crate::s3::{ utils::{check_bucket_name, merge, to_http_header_value, Multimap, UtcTime}, }; +/// Argument builder for [list_objects()](Client::get_object) API. #[derive(Debug, Clone, Default)] pub struct GetObject { client: Option, diff --git a/src/s3/builders/list_buckets.rs b/src/s3/builders/list_buckets.rs new file mode 100644 index 0000000..f3925e8 --- /dev/null +++ b/src/s3/builders/list_buckets.rs @@ -0,0 +1,80 @@ +// 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 http::Method; + +use crate::s3::response::ListBucketsResponse; +use crate::s3::{ + error::Error, + types::{S3Api, S3Request, ToS3Request}, + utils::Multimap, + Client, +}; + +/// Argument builder for [list_buckets()](Client::list_buckets) API. +#[derive(Clone, Debug, Default)] +pub struct ListBuckets { + client: Option, + + extra_headers: Option, + extra_query_params: Option, +} + +// builder interface +impl ListBuckets { + pub fn new() -> Self { + 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 + } +} + +impl ToS3Request for ListBuckets { + fn to_s3request(&self) -> Result { + let mut headers = Multimap::new(); + if let Some(v) = &self.extra_headers { + headers = v.clone(); + } + let mut query_params = Multimap::new(); + if let Some(v) = &self.extra_query_params { + query_params = v.clone(); + } + + let req = S3Request::new( + self.client.as_ref().ok_or(Error::NoClientProvided)?, + Method::GET, + ) + .query_params(query_params) + .headers(headers); + Ok(req) + } +} + +impl S3Api for ListBuckets { + type S3Response = ListBucketsResponse; +} diff --git a/src/s3/builders/object_content.rs b/src/s3/builders/object_content.rs index 0ba2f16..7018d31 100644 --- a/src/s3/builders/object_content.rs +++ b/src/s3/builders/object_content.rs @@ -200,10 +200,11 @@ impl ObjectContent { .to_path_buf() .join(Path::new(tmp_file_name.as_os_str())); - let mut total = 0; + let mut total_bytes_written = 0; let mut fp = fs::OpenOptions::new() .write(true) - .create(true) + .create(true) // Ensures that the file will be created if it does not already exist + .truncate(true) // Clears the contents (truncates the file size to 0) before writing .open(&tmp_file_path) .await?; let (mut r, _) = self.to_stream().await?; @@ -212,12 +213,12 @@ impl ObjectContent { if bytes.is_empty() { break; } - total += bytes.len() as u64; + total_bytes_written += bytes.len() as u64; fp.write_all(&bytes).await?; } fp.flush().await?; fs::rename(&tmp_file_path, file_path).await?; - Ok(total) + Ok(total_bytes_written) } } diff --git a/src/s3/client.rs b/src/s3/client.rs index 3590bc5..f2b9648 100644 --- a/src/s3/client.rs +++ b/src/s3/client.rs @@ -30,7 +30,7 @@ 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, SseConfig, + RetentionMode, }; use crate::s3::utils::{ from_iso8601utc, get_default_text, get_option_text, get_text, md5sum_hash, md5sum_hash_sb, @@ -47,6 +47,8 @@ use tokio::fs; use xmltree::Element; +mod get_bucket_encryption; +mod get_bucket_versioning; mod get_object; mod list_objects; mod listen_bucket_notification; @@ -54,7 +56,7 @@ mod object_prompt; mod put_object; mod remove_objects; -use super::builders::{GetBucketVersioning, ListBuckets, SegmentedBytes}; +use super::builders::{ListBuckets, SegmentedBytes}; /// Client Builder manufactures a Client using given parameters. #[derive(Debug, Default)] @@ -1602,58 +1604,6 @@ impl Client { }) } - pub async fn get_bucket_encryption( - &self, - args: &GetBucketEncryptionArgs<'_>, - ) -> 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("encryption"), 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())?; - let rule = root - .get_mut_child("Rule") - .ok_or(Error::XmlError(String::from(" tag not found")))?; - let sse_by_default = rule - .get_mut_child("ApplyServerSideEncryptionByDefault") - .ok_or(Error::XmlError(String::from( - " tag not found", - )))?; - - Ok(GetBucketEncryptionResponse { - headers: header_map.clone(), - region: region.clone(), - bucket_name: args.bucket.to_string(), - config: SseConfig { - sse_algorithm: get_text(sse_by_default, "SSEAlgorithm")?, - kms_master_key_id: get_option_text(sse_by_default, "KMSMasterKeyID"), - }, - }) - } - pub async fn get_bucket_lifecycle( &self, args: &GetBucketLifecycleArgs<'_>, @@ -1912,10 +1862,6 @@ impl Client { } } - pub fn get_bucket_versioning(&self, bucket: &str) -> GetBucketVersioning { - GetBucketVersioning::new(bucket).client(self) - } - pub async fn get_object_old( &self, args: &GetObjectArgs<'_>, diff --git a/src/s3/client/get_bucket_encryption.rs b/src/s3/client/get_bucket_encryption.rs new file mode 100644 index 0000000..bdc22a9 --- /dev/null +++ b/src/s3/client/get_bucket_encryption.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::GetBucketEncryption; + +impl Client { + /// Create a GetBucketEncryption request builder. + pub fn get_bucket_encryption(&self, bucket: &str) -> GetBucketEncryption { + GetBucketEncryption::new(bucket).client(self) + } +} diff --git a/src/s3/client/get_bucket_versioning.rs b/src/s3/client/get_bucket_versioning.rs new file mode 100644 index 0000000..775acae --- /dev/null +++ b/src/s3/client/get_bucket_versioning.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::GetBucketVersioning; + +impl Client { + /// Create a GetBucketVersioning request builder. + pub fn get_bucket_versioning(&self, bucket: &str) -> GetBucketVersioning { + GetBucketVersioning::new(bucket).client(self) + } +} diff --git a/src/s3/response.rs b/src/s3/response.rs index 9628218..e4121dc 100644 --- a/src/s3/response.rs +++ b/src/s3/response.rs @@ -25,22 +25,26 @@ use xmltree::Element; use crate::s3::error::Error; use crate::s3::types::{ parse_legal_hold, LifecycleConfig, NotificationConfig, ObjectLockConfig, ReplicationConfig, - RetentionMode, SelectProgress, SseConfig, + RetentionMode, SelectProgress, }; use crate::s3::utils::{ copy_slice, crc32, from_http_header_value, from_iso8601utc, get_text, uint32, UtcTime, }; -mod buckets; +mod get_bucket_encryption; +mod get_bucket_versioning; mod get_object; +mod list_buckets; pub(crate) mod list_objects; mod listen_bucket_notification; mod object_prompt; mod put_object; mod remove_objects; -pub use buckets::{GetBucketVersioningResponse, ListBucketsResponse}; +pub use get_bucket_encryption::GetBucketEncryptionResponse; +pub use get_bucket_versioning::GetBucketVersioningResponse; pub use get_object::GetObjectResponse; +pub use list_buckets::ListBucketsResponse; pub use list_objects::ListObjectsResponse; pub use listen_bucket_notification::ListenBucketNotificationResponse; pub use object_prompt::ObjectPromptResponse; @@ -550,15 +554,6 @@ impl SelectObjectContentResponse { /// Response of [delete_bucket_encryption()](crate::s3::client::Client::delete_bucket_encryption) API pub type DeleteBucketEncryptionResponse = BucketResponse; -#[derive(Clone, Debug)] -/// Response of [get_bucket_encryption()](crate::s3::client::Client::get_bucket_encryption) API -pub struct GetBucketEncryptionResponse { - pub headers: HeaderMap, - pub region: String, - pub bucket_name: String, - pub config: SseConfig, -} - /// Response of [set_bucket_encryption()](crate::s3::client::Client::set_bucket_encryption) API pub type SetBucketEncryptionResponse = BucketResponse; diff --git a/src/s3/response/get_bucket_encryption.rs b/src/s3/response/get_bucket_encryption.rs new file mode 100644 index 0000000..c8e5547 --- /dev/null +++ b/src/s3/response/get_bucket_encryption.rs @@ -0,0 +1,65 @@ +// 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, SseConfig}; +use crate::s3::utils::{get_option_text, get_text}; +use async_trait::async_trait; +use bytes::Buf; +use http::HeaderMap; +use xmltree::Element; + +/// Response of +/// [get_bucket_encryption()](crate::s3::client::Client::get_bucket_encryption) +/// API +#[derive(Clone, Debug)] +pub struct GetBucketEncryptionResponse { + pub headers: HeaderMap, + pub region: String, + pub bucket: String, + pub config: SseConfig, +} + +#[async_trait] +impl FromS3Response for GetBucketEncryptionResponse { + async fn from_s3response<'a>( + req: S3Request<'a>, + resp: reqwest::Response, + ) -> Result { + let headers = resp.headers().clone(); + let body = resp.bytes().await?; + let mut root = Element::parse(body.reader())?; + + let rule = root + .get_mut_child("Rule") + .ok_or(Error::XmlError(String::from(" tag not found")))?; + + let sse_by_default = rule + .get_mut_child("ApplyServerSideEncryptionByDefault") + .ok_or(Error::XmlError(String::from( + " tag not found", + )))?; + + Ok(GetBucketEncryptionResponse { + headers, + region: req.get_computed_region(), + bucket: req.bucket.unwrap().to_string(), // TODO remove unwrap + config: SseConfig { + sse_algorithm: get_text(sse_by_default, "SSEAlgorithm")?, + kms_master_key_id: get_option_text(sse_by_default, "KMSMasterKeyID"), + }, + }) + } +} diff --git a/src/s3/response/get_bucket_versioning.rs b/src/s3/response/get_bucket_versioning.rs new file mode 100644 index 0000000..d4d4330 --- /dev/null +++ b/src/s3/response/get_bucket_versioning.rs @@ -0,0 +1,54 @@ +// 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_option_text; +use async_trait::async_trait; +use bytes::Buf; +use http::HeaderMap; +use xmltree::Element; + +/// Response of +/// [get_bucket_versioning()](crate::s3::client::Client::get_bucket_versioning) +/// API +#[derive(Clone, Debug)] +pub struct GetBucketVersioningResponse { + pub headers: HeaderMap, + pub region: String, + pub bucket: String, + pub status: Option, + pub mfa_delete: Option, +} + +#[async_trait] +impl FromS3Response for GetBucketVersioningResponse { + async fn from_s3response<'a>( + req: S3Request<'a>, + resp: reqwest::Response, + ) -> Result { + let headers = resp.headers().clone(); + let body = resp.bytes().await?; + let root = Element::parse(body.reader())?; + + Ok(GetBucketVersioningResponse { + headers, + region: req.get_computed_region(), + bucket: req.bucket.unwrap().to_string(), // TODO remove unwrap + status: get_option_text(&root, "Status").map(|v| v == "Enabled"), + mfa_delete: get_option_text(&root, "MFADelete").map(|v| v == "Enabled"), + }) + } +} diff --git a/src/s3/response/buckets.rs b/src/s3/response/list_buckets.rs similarity index 52% rename from src/s3/response/buckets.rs rename to src/s3/response/list_buckets.rs index 9745f76..9327584 100644 --- a/src/s3/response/buckets.rs +++ b/src/s3/response/list_buckets.rs @@ -1,14 +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. + +use crate::s3::error::Error; +use crate::s3::types::{Bucket, FromS3Response, S3Request}; +use crate::s3::utils::{from_iso8601utc, get_text}; use async_trait::async_trait; use bytes::Buf; use http::HeaderMap; use xmltree::Element; -use crate::s3::{ - error::Error, - types::{Bucket, FromS3Response, S3Request}, - utils::{from_iso8601utc, get_option_text, get_text}, -}; - /// Response of [list_buckets()](crate::s3::client::Client::list_buckets) API #[derive(Debug, Clone)] pub struct ListBucketsResponse { @@ -44,35 +56,3 @@ impl FromS3Response for ListBucketsResponse { }) } } - -/// Response of -/// [get_bucket_versioning()](crate::s3::client::Client::get_bucket_versioning) -/// API -#[derive(Clone, Debug)] -pub struct GetBucketVersioningResponse { - pub headers: HeaderMap, - pub region: String, - pub bucket: String, - pub status: Option, - pub mfa_delete: Option, -} - -#[async_trait] -impl FromS3Response for GetBucketVersioningResponse { - async fn from_s3response<'a>( - req: S3Request<'a>, - resp: reqwest::Response, - ) -> Result { - let headers = resp.headers().clone(); - let body = resp.bytes().await?; - let root = Element::parse(body.reader())?; - - Ok(GetBucketVersioningResponse { - headers, - region: req.get_computed_region(), - bucket: req.bucket.unwrap().to_string(), - status: get_option_text(&root, "Status").map(|v| v == "Enabled"), - mfa_delete: get_option_text(&root, "MFADelete").map(|v| v == "Enabled"), - }) - } -} diff --git a/src/s3/response/put_object.rs b/src/s3/response/put_object.rs index a5f69e3..a4da35c 100644 --- a/src/s3/response/put_object.rs +++ b/src/s3/response/put_object.rs @@ -24,6 +24,7 @@ use crate::s3::{ utils::get_text, }; +/// Response of [put_object_api()](crate::s3::client::Client::put_object) API #[derive(Debug, Clone)] pub struct PutObjectResponse { pub headers: HeaderMap, diff --git a/tests/tests.rs b/tests/tests.rs index 2d187d6..6afba58 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -39,13 +39,12 @@ use minio::s3::client::Client; use minio::s3::creds::StaticProvider; use minio::s3::error::Error; use minio::s3::http::BaseUrl; -use minio::s3::types::ToStream; +use minio::s3::response::GetBucketVersioningResponse; use minio::s3::types::{ CsvInputSerialization, CsvOutputSerialization, FileHeaderInfo, NotificationConfig, - ObjectLockConfig, PrefixFilterRule, QueueConfig, QuoteFields, RetentionMode, SelectRequest, - SuffixFilterRule, + NotificationRecords, ObjectLockConfig, PrefixFilterRule, QueueConfig, QuoteFields, + RetentionMode, S3Api, SelectRequest, SuffixFilterRule, ToStream, }; -use minio::s3::types::{NotificationRecords, S3Api}; use minio::s3::utils::{to_iso8601utc, utc_now}; struct RandReader { @@ -1263,7 +1262,7 @@ impl ClientTest { .await .unwrap(); - let resp = self + let resp: GetBucketVersioningResponse = self .client .get_bucket_versioning(&bucket_name) .send() @@ -1276,7 +1275,7 @@ impl ClientTest { .await .unwrap(); - let resp = self + let resp: GetBucketVersioningResponse = self .client .get_bucket_versioning(&bucket_name) .send()