// 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 minio::s3::client::DEFAULT_REGION; use minio::s3::error::{Error, S3ServerError}; use minio::s3::minio_error_response::MinioErrorCode; use minio::s3::response::{ BucketExistsResponse, CreateBucketResponse, DeleteBucketResponse, PutObjectContentResponse, }; use minio::s3::response_traits::{HasBucket, HasObject, HasRegion}; use minio::s3::types::{BucketName, ObjectKey, S3Api}; use minio_common::test_context::TestContext; use minio_common::utils::{rand_bucket_name, rand_object_name_utf8}; #[minio_macros::test(no_bucket)] async fn bucket_create(ctx: TestContext) -> Result<(), Error> { let bucket = rand_bucket_name(); // try to create a bucket that does not exist let resp: CreateBucketResponse = ctx .client .create_bucket(&bucket)? .build() .send() .await .unwrap(); assert_eq!(resp.bucket(), Some(&bucket)); assert_eq!(resp.region(), &*DEFAULT_REGION); // check that the bucket exists let resp: BucketExistsResponse = ctx .client .bucket_exists(&bucket)? .build() .send() .await .unwrap(); assert!(resp.exists()); assert_eq!(resp.bucket(), Some(&bucket)); assert_eq!(resp.region(), &*DEFAULT_REGION); // try to create a bucket that already exists let resp: Result = ctx.client.create_bucket(&bucket)?.build().send().await; match resp { Ok(_) => panic!("Bucket already exists, but was created again"), Err(Error::S3Server(S3ServerError::S3Error(e))) if matches!(e.code(), MinioErrorCode::BucketAlreadyOwnedByYou) => { // this is expected, as the bucket already exists } Err(e) => panic!("Unexpected error: {e:?}"), } Ok(()) } #[minio_macros::test(no_bucket)] async fn bucket_delete(ctx: TestContext) -> Result<(), Error> { let bucket = rand_bucket_name(); // try to remove a bucket that does not exist let resp: Result = ctx.client.delete_bucket(&bucket)?.build().send().await; match resp { Ok(_) => panic!("Bucket does not exist, but was removed"), Err(Error::S3Server(S3ServerError::S3Error(e))) if matches!(e.code(), MinioErrorCode::NoSuchBucket) => { // this is expected, as the bucket does not exist } Err(e) => panic!("Unexpected error: {e:?}"), } // create a new bucket let resp: CreateBucketResponse = ctx .client .create_bucket(&bucket)? .build() .send() .await .unwrap(); assert_eq!(resp.bucket(), Some(&bucket)); assert_eq!(resp.region(), &*DEFAULT_REGION); // check that the bucket exists let resp: BucketExistsResponse = ctx .client .bucket_exists(&bucket)? .build() .send() .await .unwrap(); assert!(resp.exists()); assert_eq!(resp.bucket(), Some(&bucket)); assert_eq!(resp.region(), &*DEFAULT_REGION); // try to remove a bucket that exists let resp: DeleteBucketResponse = ctx .client .delete_bucket(&bucket)? .build() .send() .await .unwrap(); assert_eq!(resp.bucket(), Some(&bucket)); assert_eq!(resp.region(), &*DEFAULT_REGION); // check that the bucket does not exist anymore let resp: BucketExistsResponse = ctx .client .bucket_exists(&bucket)? .build() .send() .await .unwrap(); assert!(!resp.exists()); assert_eq!(resp.bucket(), Some(&bucket)); assert_eq!(resp.region(), &*DEFAULT_REGION); Ok(()) } async fn test_bucket_delete_and_purge( ctx: &TestContext, bucket: &str, object: &str, ) -> Result<(), Error> { let resp: PutObjectContentResponse = ctx .client .put_object_content( BucketName::try_from(bucket).unwrap(), ObjectKey::try_from(object).unwrap(), "Hello, World!", )? .build() .send() .await .unwrap(); assert_eq!(resp.bucket(), Some(&BucketName::try_from(bucket).unwrap())); assert_eq!(resp.object(), Some(&ObjectKey::try_from(object).unwrap())); // try to remove the bucket without purging, this should fail because the bucket is not empty let resp: Result = ctx .client .delete_bucket(BucketName::try_from(bucket).unwrap()) .unwrap() .build() .send() .await; assert!(resp.is_err()); // try to remove the bucket with purging, this should succeed let resp: DeleteBucketResponse = ctx .client .delete_and_purge_bucket(BucketName::try_from(bucket).unwrap()) .await .unwrap(); assert_eq!(resp.bucket(), Some(&BucketName::try_from(bucket).unwrap())); Ok(()) } /// Test purging a bucket with an object that contains utf8 characters. #[minio_macros::test] async fn bucket_delete_and_purge_1(ctx: TestContext, bucket: BucketName) -> Result<(), Error> { let object = rand_object_name_utf8(20); test_bucket_delete_and_purge(&ctx, bucket.as_str(), object.as_str()).await } /// Test purging a bucket with an object that contains white space characters. #[minio_macros::test] async fn bucket_delete_and_purge_2(ctx: TestContext, bucket: BucketName) -> Result<(), Error> { test_bucket_delete_and_purge(&ctx, bucket.as_str(), "a b+c").await }