refactored all functions (#145)

* refactored stat_object

refactored select_object_content

refactor get_presigned_object_url

refactor get_presigned_policy_form_data

refactored upload-part-copy

* fixed object.unwrap

* update region

* made client Arc

* made client Arc

* update client

* update tests

* update segmented_bytes

* bench updated

* cleanup version handling

* cleanup of headers: multimap

* added inner in Client

* updated clients: added Into<String>in API

* Separated http_client and shared client items in Client
This commit is contained in:
Henk-Jan Lebbink 2025-04-23 19:18:18 +02:00 committed by GitHub
parent f23572dce8
commit 58d9203153
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
213 changed files with 9571 additions and 7201 deletions

View File

@ -23,7 +23,7 @@ jobs:
cargo clippy --all-targets --all-features cargo clippy --all-targets --all-features
cargo build --bins --examples --tests --benches --verbose cargo build --bins --examples --tests --benches --verbose
- name: Run tests - name: Run tests S3
run: | run: |
./tests/start-server.sh ./tests/start-server.sh
export SERVER_ENDPOINT=localhost:9000 export SERVER_ENDPOINT=localhost:9000

View File

@ -48,7 +48,7 @@ ring = { version = "0.17.14", optional = true, default-features = false, feature
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140" serde_json = "1.0.140"
sha2 = { version = "0.10.8", optional = true } sha2 = { version = "0.10.8", optional = true }
tokio = { version = "1.44.1", features = ["full"] } tokio = { version = "1.44.2", features = ["full"] }
tokio-stream = "0.1.17" tokio-stream = "0.1.17"
tokio-util = { version = "0.7.14", features = ["io"] } tokio-util = { version = "0.7.14", features = ["io"] }
urlencoding = "2.1.3" urlencoding = "2.1.3"
@ -59,7 +59,7 @@ http = "1.3.1"
[dev-dependencies] [dev-dependencies]
minio_common = { path = "./common" } minio_common = { path = "./common" }
async-std = { version = "1.13.1", features = ["attributes", "tokio1"] } async-std = { version = "1.13.1", features = ["attributes", "tokio1"] }
clap = { version = "4.5.34", features = ["derive"] } clap = { version = "4.5.35", features = ["derive"] }
quickcheck = "1.0.3" quickcheck = "1.0.3"
criterion = "0.5.1" criterion = "0.5.1"
@ -76,6 +76,9 @@ name = "file_downloader"
[[example]] [[example]]
name = "object_prompt" name = "object_prompt"
[[example]]
name = "append_object"
[[bench]] [[bench]]
name = "s3-api" name = "s3-api"
path = "benches/s3/api_benchmarks.rs" path = "benches/s3/api_benchmarks.rs"

View File

@ -21,14 +21,15 @@ mod bench_bucket_replication;
mod bench_bucket_tags; mod bench_bucket_tags;
mod bench_bucket_versioning; mod bench_bucket_versioning;
mod bench_list_bucket; mod bench_list_bucket;
mod bench_object_append;
mod bench_object_copy;
mod bench_object_legal_hold; mod bench_object_legal_hold;
mod bench_object_lock_config; mod bench_object_lock_config;
mod bench_object_put;
mod bench_object_retention; mod bench_object_retention;
mod bench_object_tags; mod bench_object_tags;
mod common_benches; mod common_benches;
mod bench_object_copy;
use criterion::{Criterion, criterion_group, criterion_main}; use criterion::{Criterion, criterion_group, criterion_main};
use std::time::Duration; use std::time::Duration;
@ -43,9 +44,12 @@ use crate::bench_bucket_tags::*;
use crate::bench_bucket_versioning::*; use crate::bench_bucket_versioning::*;
use crate::bench_list_bucket::*; use crate::bench_list_bucket::*;
#[allow(unused_imports)] #[allow(unused_imports)]
use crate::bench_object_append::bench_object_append;
#[allow(unused_imports)]
use crate::bench_object_copy::*; use crate::bench_object_copy::*;
use crate::bench_object_legal_hold::*; use crate::bench_object_legal_hold::*;
use crate::bench_object_lock_config::*; use crate::bench_object_lock_config::*;
use crate::bench_object_put::bench_object_put;
use crate::bench_object_retention::*; use crate::bench_object_retention::*;
use crate::bench_object_tags::*; use crate::bench_object_tags::*;
@ -54,9 +58,9 @@ criterion_group!(
config = Criterion::default() config = Criterion::default()
.configure_from_args() .configure_from_args()
.warm_up_time(Duration::from_secs_f32(0.01)) .warm_up_time(Duration::from_secs_f32(0.01))
.sample_size(100) .sample_size(1000)
.nresamples(1001) .nresamples(1001)
.measurement_time(Duration::from_secs_f32(0.5)); .measurement_time(Duration::from_secs_f32(10.0));
targets = targets =
bench_bucket_exists, bench_bucket_exists,
bench_set_bucket_lifecycle, bench_set_bucket_lifecycle,
@ -83,7 +87,9 @@ criterion_group!(
bench_get_bucket_versioning, bench_get_bucket_versioning,
// //
bench_list_buckets, bench_list_buckets,
//bench_object_copy, //TODO first refactor object_copy bench_object_copy_internal,
bench_object_append,
bench_object_put,
// //
bench_enable_object_legal_hold, bench_enable_object_legal_hold,
bench_disable_object_legal_hold, bench_disable_object_legal_hold,

View File

@ -23,6 +23,6 @@ pub(crate) fn bench_bucket_exists(criterion: &mut Criterion) {
"bucket_exists", "bucket_exists",
criterion, criterion,
|| async { Ctx2::new().await }, || async { Ctx2::new().await },
|ctx| BucketExists::new(&ctx.bucket).client(&ctx.client), |ctx| BucketExists::new(ctx.client.clone(), ctx.bucket.clone()),
); );
} }

View File

@ -27,8 +27,7 @@ pub(crate) fn bench_set_bucket_lifecycle(criterion: &mut Criterion) {
|| async { Ctx2::new().await }, || async { Ctx2::new().await },
|ctx| { |ctx| {
let config = create_bucket_lifecycle_config_examples(); let config = create_bucket_lifecycle_config_examples();
SetBucketLifecycle::new(&ctx.bucket) SetBucketLifecycle::new(ctx.client.clone(), ctx.bucket.clone())
.client(&ctx.client)
.life_cycle_config(config) .life_cycle_config(config)
}, },
) )
@ -40,15 +39,15 @@ pub(crate) fn bench_get_bucket_lifecycle(criterion: &mut Criterion) {
|| async { || async {
let ctx = Ctx2::new().await; let ctx = Ctx2::new().await;
let config = create_bucket_lifecycle_config_examples(); let config = create_bucket_lifecycle_config_examples();
SetBucketLifecycle::new(&ctx.bucket) ctx.client
.client(&ctx.client) .set_bucket_lifecycle(&ctx.bucket)
.life_cycle_config(config) .life_cycle_config(config)
.send() .send()
.await .await
.unwrap(); .unwrap();
ctx ctx
}, },
|ctx| GetBucketLifecycle::new(&ctx.bucket).client(&ctx.client), |ctx| GetBucketLifecycle::new(ctx.client.clone(), ctx.bucket.clone()),
) )
} }
pub(crate) fn bench_delete_bucket_lifecycle(criterion: &mut Criterion) { pub(crate) fn bench_delete_bucket_lifecycle(criterion: &mut Criterion) {
@ -56,6 +55,6 @@ pub(crate) fn bench_delete_bucket_lifecycle(criterion: &mut Criterion) {
"delete_bucket_lifecycle", "delete_bucket_lifecycle",
criterion, criterion,
|| async { Ctx2::new().await }, || async { Ctx2::new().await },
|ctx| DeleteBucketLifecycle::new(&ctx.bucket).client(&ctx.client), |ctx| DeleteBucketLifecycle::new(ctx.client.clone(), ctx.bucket.clone()),
) )
} }

View File

@ -28,8 +28,7 @@ pub(crate) fn bench_set_bucket_notification(criterion: &mut Criterion) {
|| async { Ctx2::new().await }, || async { Ctx2::new().await },
|ctx| { |ctx| {
let config = create_bucket_notification_config_example(); let config = create_bucket_notification_config_example();
SetBucketNotification::new(&ctx.bucket) SetBucketNotification::new(ctx.client.clone(), ctx.bucket.clone())
.client(&ctx.client)
.notification_config(config) .notification_config(config)
}, },
) )
@ -42,15 +41,15 @@ pub(crate) fn bench_get_bucket_notification(criterion: &mut Criterion) {
|| async { || async {
let ctx = Ctx2::new().await; let ctx = Ctx2::new().await;
let config = create_bucket_notification_config_example(); let config = create_bucket_notification_config_example();
SetBucketNotification::new(&ctx.bucket) ctx.client
.client(&ctx.client) .set_bucket_notification(&ctx.bucket)
.notification_config(config) .notification_config(config)
.send() .send()
.await .await
.unwrap(); .unwrap();
ctx ctx
}, },
|ctx| GetBucketNotification::new(&ctx.bucket).client(&ctx.client), |ctx| GetBucketNotification::new(ctx.client.clone(), ctx.bucket.clone()),
) )
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -59,6 +58,6 @@ pub(crate) fn bench_delete_bucket_notification(criterion: &mut Criterion) {
"delete_bucket_notification", "delete_bucket_notification",
criterion, criterion,
|| async { Ctx2::new().await }, || async { Ctx2::new().await },
|ctx| DeleteBucketNotification::new(&ctx.bucket).client(&ctx.client), |ctx| DeleteBucketNotification::new(ctx.client.clone(), ctx.bucket.clone()),
) )
} }

View File

@ -27,9 +27,7 @@ pub(crate) fn bench_set_bucket_policy(criterion: &mut Criterion) {
|| async { Ctx2::new().await }, || async { Ctx2::new().await },
|ctx| { |ctx| {
let config = create_bucket_policy_config_example(&ctx.bucket); let config = create_bucket_policy_config_example(&ctx.bucket);
SetBucketPolicy::new(&ctx.bucket) SetBucketPolicy::new(ctx.client.clone(), ctx.bucket.clone()).config(config)
.client(&ctx.client)
.config(config)
}, },
) )
} }
@ -40,15 +38,14 @@ pub(crate) fn bench_get_bucket_policy(criterion: &mut Criterion) {
|| async { || async {
let ctx = Ctx2::new().await; let ctx = Ctx2::new().await;
let config = create_bucket_policy_config_example(&ctx.bucket); let config = create_bucket_policy_config_example(&ctx.bucket);
SetBucketPolicy::new(&ctx.bucket) SetBucketPolicy::new(ctx.client.clone(), ctx.bucket.clone())
.client(&ctx.client)
.config(config) .config(config)
.send() .send()
.await .await
.unwrap(); .unwrap();
ctx ctx
}, },
|ctx| GetBucketPolicy::new(&ctx.bucket).client(&ctx.client), |ctx| GetBucketPolicy::new(ctx.client.clone(), ctx.bucket.clone()),
) )
} }
pub(crate) fn bench_delete_bucket_policy(criterion: &mut Criterion) { pub(crate) fn bench_delete_bucket_policy(criterion: &mut Criterion) {
@ -56,6 +53,6 @@ pub(crate) fn bench_delete_bucket_policy(criterion: &mut Criterion) {
"delete_bucket_policy", "delete_bucket_policy",
criterion, criterion,
|| async { Ctx2::new().await }, || async { Ctx2::new().await },
|ctx| DeleteBucketPolicy::new(&ctx.bucket).client(&ctx.client), |ctx| DeleteBucketPolicy::new(ctx.client.clone(), ctx.bucket.clone()),
) )
} }

View File

@ -53,8 +53,7 @@ pub(crate) fn bench_set_bucket_replication(criterion: &mut Criterion) {
|ctx| { |ctx| {
let config = let config =
create_bucket_replication_config_example(ctx.aux_bucket.clone().unwrap().as_str()); create_bucket_replication_config_example(ctx.aux_bucket.clone().unwrap().as_str());
SetBucketReplication::new(&ctx.bucket) SetBucketReplication::new(ctx.client.clone(), ctx.bucket.clone())
.client(&ctx.client)
.replication_config(config) .replication_config(config)
}, },
) )
@ -86,7 +85,7 @@ pub(crate) fn bench_get_bucket_replication(criterion: &mut Criterion) {
ctx ctx
}, },
|ctx| GetBucketReplication::new(&ctx.bucket).client(&ctx.client), |ctx| GetBucketReplication::new(ctx.client.clone(), ctx.bucket.clone()),
) )
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -116,6 +115,6 @@ pub(crate) fn bench_delete_bucket_replication(criterion: &mut Criterion) {
ctx ctx
}, },
|ctx| DeleteBucketReplication::new(&ctx.bucket).client(&ctx.client), |ctx| DeleteBucketReplication::new(ctx.client.clone(), ctx.bucket.clone()),
) )
} }

View File

@ -13,34 +13,36 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::common_benches::{Ctx2, benchmark_s3_api}; use crate::common_benches::{Ctx2, benchmark_s3_api, skip_express_mode};
use criterion::Criterion; use criterion::Criterion;
use minio::s3::builders::{DeleteBucketTags, GetBucketTags, SetBucketTags}; use minio::s3::builders::{DeleteBucketTags, GetBucketTags, SetBucketTags};
use minio::s3::response::SetBucketTagsResponse;
use minio::s3::types::S3Api; use minio::s3::types::S3Api;
use minio_common::example::create_tags_example; use minio_common::example::create_tags_example;
pub(crate) fn bench_set_bucket_tags(criterion: &mut Criterion) { pub(crate) fn bench_set_bucket_tags(criterion: &mut Criterion) {
if skip_express_mode("bench_set_bucket_tags") {
return;
}
benchmark_s3_api( benchmark_s3_api(
"set_bucket_tags", "set_bucket_tags",
criterion, criterion,
|| async { Ctx2::new().await }, || async { Ctx2::new().await },
|ctx| { |ctx| {
SetBucketTags::new(&ctx.bucket) SetBucketTags::new(ctx.client.clone(), ctx.bucket.clone()).tags(create_tags_example())
.client(&ctx.client)
.tags(create_tags_example())
}, },
) )
} }
pub(crate) fn bench_get_bucket_tags(criterion: &mut Criterion) { pub(crate) fn bench_get_bucket_tags(criterion: &mut Criterion) {
if skip_express_mode("bench_get_bucket_tags") {
return;
}
benchmark_s3_api( benchmark_s3_api(
"get_bucket_tags", "get_bucket_tags",
criterion, criterion,
|| async { || async {
let ctx = Ctx2::new().await; let ctx = Ctx2::new().await;
let _resp: SetBucketTagsResponse = ctx ctx.client
.client
.set_bucket_tags(&ctx.bucket) .set_bucket_tags(&ctx.bucket)
.tags(create_tags_example()) .tags(create_tags_example())
.send() .send()
@ -48,14 +50,17 @@ pub(crate) fn bench_get_bucket_tags(criterion: &mut Criterion) {
.unwrap(); .unwrap();
ctx ctx
}, },
|ctx| GetBucketTags::new(&ctx.bucket).client(&ctx.client), |ctx| GetBucketTags::new(ctx.client.clone(), ctx.bucket.clone()),
) )
} }
pub(crate) fn bench_delete_bucket_tags(criterion: &mut Criterion) { pub(crate) fn bench_delete_bucket_tags(criterion: &mut Criterion) {
if skip_express_mode("bench_delete_bucket_tags") {
return;
}
benchmark_s3_api( benchmark_s3_api(
"delete_bucket_tags", "delete_bucket_tags",
criterion, criterion,
|| async { Ctx2::new().await }, || async { Ctx2::new().await },
|ctx| DeleteBucketTags::new(&ctx.bucket).client(&ctx.client), |ctx| DeleteBucketTags::new(ctx.client.clone(), ctx.bucket.clone()),
) )
} }

View File

@ -13,29 +13,33 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::common_benches::{Ctx2, benchmark_s3_api}; use crate::common_benches::{Ctx2, benchmark_s3_api, skip_express_mode};
use criterion::Criterion; use criterion::Criterion;
use minio::s3::builders::{GetBucketVersioning, SetBucketVersioning, VersioningStatus}; use minio::s3::builders::{GetBucketVersioning, SetBucketVersioning, VersioningStatus};
pub(crate) fn bench_get_bucket_versioning(criterion: &mut Criterion) { pub(crate) fn bench_get_bucket_versioning(criterion: &mut Criterion) {
if skip_express_mode("bench_get_bucket_versioning") {
return;
}
benchmark_s3_api( benchmark_s3_api(
"get_bucket_versioning", "get_bucket_versioning",
criterion, criterion,
|| async { Ctx2::new().await }, || async { Ctx2::new().await },
|ctx| GetBucketVersioning::new(&ctx.bucket).client(&ctx.client), |ctx| GetBucketVersioning::new(ctx.client.clone(), ctx.bucket.clone()),
) )
} }
pub(crate) fn bench_set_bucket_versioning(criterion: &mut Criterion) { pub(crate) fn bench_set_bucket_versioning(criterion: &mut Criterion) {
if skip_express_mode("bench_set_bucket_versioning") {
return;
}
benchmark_s3_api( benchmark_s3_api(
"set_bucket_versioning", "set_bucket_versioning",
criterion, criterion,
|| async { Ctx2::new().await }, || async { Ctx2::new().await },
|ctx| { |ctx| {
let status = VersioningStatus::Enabled; SetBucketVersioning::new(ctx.client.clone(), ctx.bucket.clone())
SetBucketVersioning::new(&ctx.bucket) .versioning_status(VersioningStatus::Enabled)
.client(&ctx.client)
.versioning_status(status)
}, },
) )
} }

View File

@ -23,6 +23,6 @@ pub(crate) fn bench_list_buckets(criterion: &mut Criterion) {
"list_buckets", "list_buckets",
criterion, criterion,
|| async { Ctx2::new().await }, || async { Ctx2::new().await },
|ctx| ListBuckets::new().client(&ctx.client), |ctx| ListBuckets::new(ctx.client.clone()),
) )
} }

View File

@ -0,0 +1,56 @@
// MinIO Rust Library for Amazon S3 Compatible Cloud Storage
// Copyright 2025 MinIO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::common_benches::{Ctx2, benchmark_s3_api};
use criterion::Criterion;
use minio::s3::builders::AppendObject;
use minio::s3::response::StatObjectResponse;
use minio::s3::segmented_bytes::SegmentedBytes;
use minio::s3::types::S3Api;
use minio_common::test_context::TestContext;
use tokio::task;
#[allow(dead_code)]
pub(crate) fn bench_object_append(criterion: &mut Criterion) {
if !TestContext::new_from_env().client.is_minio_express() {
println!("Skipping benchmark because it is NOT running in MinIO Express mode");
return;
}
benchmark_s3_api(
"object_append",
criterion,
|| async { Ctx2::new_with_object(false).await },
|ctx| {
let content1 = "Hello world 2";
let data1: SegmentedBytes = SegmentedBytes::from(content1.to_string());
let resp: StatObjectResponse = task::block_in_place(|| {
tokio::runtime::Runtime::new()?
.block_on(ctx.client.stat_object(&ctx.bucket, &ctx.object).send())
})
.unwrap();
let offset_bytes: u64 = resp.size;
AppendObject::new(
ctx.client.clone(),
ctx.bucket.clone(),
ctx.object.clone(),
data1,
offset_bytes,
)
},
)
}

View File

@ -13,23 +13,22 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#[allow(unused_imports)]
use crate::common_benches::{Ctx2, benchmark_s3_api}; use crate::common_benches::{Ctx2, benchmark_s3_api};
use criterion::Criterion; use criterion::Criterion;
use minio::s3::builders::{CopyObjectInternal, CopySource};
use minio_common::utils::rand_object_name;
#[allow(dead_code)] pub(crate) fn bench_object_copy_internal(criterion: &mut Criterion) {
pub(crate) fn bench_object_copy(_criterion: &mut Criterion) {
/*
benchmark_s3_api( benchmark_s3_api(
"object_copy", "object_copy_internal",
criterion, criterion,
|| async { Ctx2::new_with_object(false).await }, || async { Ctx2::new_with_object(false).await },
|ctx| { |ctx| {
let _object_name_dst = rand_object_name(); let object_name_src = &ctx.object;
//TODO refactor copy object for this to be possible let object_name_dst = rand_object_name();
todo!() CopyObjectInternal::new(ctx.client.clone(), ctx.bucket.clone(), object_name_dst)
.source(CopySource::new(&ctx.bucket, object_name_src).unwrap())
}, },
); )
*/
} }

View File

@ -13,46 +13,62 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::common_benches::{Ctx2, benchmark_s3_api}; use crate::common_benches::{Ctx2, benchmark_s3_api, skip_express_mode};
use criterion::Criterion; use criterion::Criterion;
use minio::s3::builders::{ use minio::s3::builders::{
DisableObjectLegalHold, EnableObjectLegalHold, IsObjectLegalHoldEnabled, DisableObjectLegalHold, EnableObjectLegalHold, IsObjectLegalHoldEnabled,
}; };
use minio::s3::types::S3Api;
pub(crate) fn bench_enable_object_legal_hold(criterion: &mut Criterion) { pub(crate) fn bench_enable_object_legal_hold(criterion: &mut Criterion) {
if skip_express_mode("bench_enable_object_legal_hold") {
return;
}
benchmark_s3_api( benchmark_s3_api(
"enable_object_legal_hold", "enable_object_legal_hold",
criterion, criterion,
|| async { Ctx2::new_with_object(true).await }, || async { Ctx2::new_with_object(true).await },
|ctx| { |ctx| {
EnableObjectLegalHold::new(&ctx.bucket) EnableObjectLegalHold::new(ctx.client.clone(), ctx.bucket.clone(), ctx.object.clone())
.client(&ctx.client)
.object(ctx.object.clone())
}, },
) )
} }
pub(crate) fn bench_disable_object_legal_hold(criterion: &mut Criterion) { pub(crate) fn bench_disable_object_legal_hold(criterion: &mut Criterion) {
if skip_express_mode("bench_disable_object_legal_hold") {
return;
}
benchmark_s3_api( benchmark_s3_api(
"disable_object_legal_hold", "disable_object_legal_hold",
criterion, criterion,
|| async { Ctx2::new_with_object(true).await }, || async { Ctx2::new_with_object(true).await },
|ctx| { |ctx| {
DisableObjectLegalHold::new(&ctx.bucket) DisableObjectLegalHold::new(ctx.client.clone(), ctx.bucket.clone(), ctx.object.clone())
.client(&ctx.client)
.object(ctx.object.clone())
}, },
) )
} }
pub(crate) fn bench_is_object_legal_hold(criterion: &mut Criterion) { pub(crate) fn bench_is_object_legal_hold(criterion: &mut Criterion) {
if skip_express_mode("bench_is_object_legal_hold") {
return;
}
benchmark_s3_api( benchmark_s3_api(
"is_object_legal_hold", "is_object_legal_hold",
criterion, criterion,
|| async { Ctx2::new().await }, || async {
let ctx = Ctx2::new_with_object(true).await;
ctx.client
.enable_object_legal_hold(&ctx.bucket, &ctx.object)
.send()
.await
.unwrap();
ctx
},
|ctx| { |ctx| {
IsObjectLegalHoldEnabled::new(&ctx.bucket) IsObjectLegalHoldEnabled::new(
.client(&ctx.client) ctx.client.clone(),
.object(ctx.object.clone()) ctx.bucket.clone(),
ctx.object.clone(),
)
}, },
) )
} }

View File

@ -13,37 +13,44 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::common_benches::{Ctx2, benchmark_s3_api}; use crate::common_benches::{Ctx2, benchmark_s3_api, skip_express_mode};
use criterion::Criterion; use criterion::Criterion;
use minio::s3::builders::{DeleteObjectLockConfig, GetObjectLockConfig, SetObjectLockConfig}; use minio::s3::builders::{DeleteObjectLockConfig, GetObjectLockConfig, SetObjectLockConfig};
use minio_common::example::create_object_lock_config_example; use minio_common::example::create_object_lock_config_example;
pub(crate) fn bench_set_object_lock_config(criterion: &mut Criterion) { pub(crate) fn bench_set_object_lock_config(criterion: &mut Criterion) {
if skip_express_mode("bench_set_object_lock_config") {
return;
}
benchmark_s3_api( benchmark_s3_api(
"set_object_lock_config", "set_object_lock_config",
criterion, criterion,
|| async { Ctx2::new_with_object(true).await }, || async { Ctx2::new_with_object(true).await },
|ctx| { |ctx| {
let config = create_object_lock_config_example(); let config = create_object_lock_config_example();
SetObjectLockConfig::new(&ctx.bucket) SetObjectLockConfig::new(ctx.client.clone(), ctx.bucket.clone()).config(config)
.client(&ctx.client)
.config(config)
}, },
) )
} }
pub(crate) fn bench_get_object_lock_config(criterion: &mut Criterion) { pub(crate) fn bench_get_object_lock_config(criterion: &mut Criterion) {
if skip_express_mode("bench_get_object_lock_config") {
return;
}
benchmark_s3_api( benchmark_s3_api(
"get_object_lock_config", "get_object_lock_config",
criterion, criterion,
|| async { Ctx2::new_with_object(true).await }, || async { Ctx2::new_with_object(true).await },
|ctx| GetObjectLockConfig::new(&ctx.bucket).client(&ctx.client), |ctx| GetObjectLockConfig::new(ctx.client.clone(), ctx.bucket.clone()),
) )
} }
pub(crate) fn bench_delete_object_lock_config(criterion: &mut Criterion) { pub(crate) fn bench_delete_object_lock_config(criterion: &mut Criterion) {
if skip_express_mode("bench_delete_object_lock_config") {
return;
}
benchmark_s3_api( benchmark_s3_api(
"delete_object_lock_config", "delete_object_lock_config",
criterion, criterion,
|| async { Ctx2::new_with_object(true).await }, || async { Ctx2::new_with_object(true).await },
|ctx| DeleteObjectLockConfig::new(&ctx.bucket).client(&ctx.client), |ctx| DeleteObjectLockConfig::new(ctx.client.clone(), ctx.bucket.clone()),
) )
} }

View File

@ -0,0 +1,43 @@
// 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_benches::{Ctx2, benchmark_s3_api};
use criterion::Criterion;
use minio::s3::builders::{ObjectContent, PutObject};
use minio::s3::segmented_bytes::SegmentedBytes;
use minio_common::rand_src::RandSrc;
use minio_common::utils::rand_object_name;
use tokio::task;
pub(crate) fn bench_object_put(criterion: &mut Criterion) {
benchmark_s3_api(
"object_put",
criterion,
|| async { Ctx2::new().await },
|ctx| {
let object_name: String = rand_object_name();
let size = 1024 * 1024_u64; // 1MB
let object_content = ObjectContent::new_from_stream(RandSrc::new(size), Some(size));
let data: SegmentedBytes = task::block_in_place(|| {
tokio::runtime::Runtime::new()?.block_on(object_content.to_segmented_bytes())
})
.unwrap();
PutObject::new(ctx.client.clone(), ctx.bucket.clone(), object_name, data)
},
)
}

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::common_benches::{Ctx2, benchmark_s3_api}; use crate::common_benches::{Ctx2, benchmark_s3_api, skip_express_mode};
use criterion::Criterion; use criterion::Criterion;
use minio::s3::builders::{GetObjectRetention, SetObjectRetention}; use minio::s3::builders::{GetObjectRetention, SetObjectRetention};
@ -22,28 +22,31 @@ use minio::s3::types::{RetentionMode, S3Api};
use minio::s3::utils::utc_now; use minio::s3::utils::utc_now;
pub(crate) fn bench_set_object_retention(criterion: &mut Criterion) { pub(crate) fn bench_set_object_retention(criterion: &mut Criterion) {
if skip_express_mode("bench_set_object_retention") {
return;
}
benchmark_s3_api( benchmark_s3_api(
"set_object_retention", "set_object_retention",
criterion, criterion,
|| async { Ctx2::new_with_object(true).await }, || async { Ctx2::new_with_object(true).await },
|ctx| { |ctx| {
SetObjectRetention::new(&ctx.bucket) SetObjectRetention::new(ctx.client.clone(), ctx.bucket.clone(), ctx.object.clone())
.client(&ctx.client)
.object(ctx.object.clone())
.retention_mode(Some(RetentionMode::GOVERNANCE)) .retention_mode(Some(RetentionMode::GOVERNANCE))
.retain_until_date(Some(utc_now() + chrono::Duration::days(1))) .retain_until_date(Some(utc_now() + chrono::Duration::days(1)))
}, },
) )
} }
pub(crate) fn bench_get_object_retention(criterion: &mut Criterion) { pub(crate) fn bench_get_object_retention(criterion: &mut Criterion) {
if skip_express_mode("bench_get_object_retention") {
return;
}
benchmark_s3_api( benchmark_s3_api(
"get_object_retention", "get_object_retention",
criterion, criterion,
|| async { || async {
let ctx = Ctx2::new_with_object(true).await; let ctx = Ctx2::new_with_object(true).await;
let _resp: SetObjectRetentionResponse = SetObjectRetention::new(&ctx.bucket) let _resp: SetObjectRetentionResponse =
.client(&ctx.client) SetObjectRetention::new(ctx.client.clone(), ctx.bucket.clone(), ctx.object.clone())
.object(ctx.object.clone())
.retention_mode(Some(RetentionMode::GOVERNANCE)) .retention_mode(Some(RetentionMode::GOVERNANCE))
.retain_until_date(Some(utc_now() + chrono::Duration::days(1))) .retain_until_date(Some(utc_now() + chrono::Duration::days(1)))
.send() .send()
@ -51,10 +54,6 @@ pub(crate) fn bench_get_object_retention(criterion: &mut Criterion) {
.unwrap(); .unwrap();
ctx ctx
}, },
|ctx| { |ctx| GetObjectRetention::new(ctx.client.clone(), ctx.bucket.clone(), ctx.object.clone()),
GetObjectRetention::new(&ctx.bucket)
.client(&ctx.client)
.object(ctx.object.clone())
},
) )
} }

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::common_benches::{Ctx2, benchmark_s3_api}; use crate::common_benches::{Ctx2, benchmark_s3_api, skip_express_mode};
use criterion::Criterion; use criterion::Criterion;
use minio::s3::builders::{GetObjectTags, SetObjectTags}; use minio::s3::builders::{GetObjectTags, SetObjectTags};
@ -22,37 +22,38 @@ use minio::s3::types::S3Api;
use minio_common::example::create_tags_example; use minio_common::example::create_tags_example;
pub(crate) fn bench_set_object_tags(criterion: &mut Criterion) { pub(crate) fn bench_set_object_tags(criterion: &mut Criterion) {
if skip_express_mode("bench_set_object_tags") {
return;
}
benchmark_s3_api( benchmark_s3_api(
"set_object_tags", "set_object_tags",
criterion, criterion,
|| async { Ctx2::new_with_object(false).await }, || async { Ctx2::new_with_object(false).await },
|ctx| { |ctx| {
SetObjectTags::new(&ctx.bucket) SetObjectTags::new(ctx.client.clone(), ctx.bucket.clone(), ctx.object.clone())
.client(&ctx.client)
.object(ctx.object.clone())
.tags(create_tags_example()) .tags(create_tags_example())
}, },
) )
} }
pub(crate) fn bench_get_object_tags(criterion: &mut Criterion) { pub(crate) fn bench_get_object_tags(criterion: &mut Criterion) {
if skip_express_mode("bench_get_object_tags") {
return;
}
benchmark_s3_api( benchmark_s3_api(
"get_object_tags", "get_object_tags",
criterion, criterion,
|| async { || async {
let ctx = Ctx2::new_with_object(false).await; let ctx = Ctx2::new_with_object(false).await;
let _resp: SetObjectTagsResponse = SetObjectTags::new(&ctx.bucket)
.client(&ctx.client) let _resp: SetObjectTagsResponse = ctx
.object(ctx.object.clone()) .client
.set_object_tags(&ctx.bucket, &ctx.object)
.tags(create_tags_example()) .tags(create_tags_example())
.send() .send()
.await .await
.unwrap(); .unwrap();
ctx ctx
}, },
|ctx| { |ctx| GetObjectTags::new(ctx.client.clone(), ctx.bucket.clone(), ctx.object.clone()),
GetObjectTags::new(&ctx.bucket)
.client(&ctx.client)
.object(ctx.object.clone())
},
) )
} }

View File

@ -17,13 +17,14 @@ use criterion::Criterion;
use minio::s3::Client; use minio::s3::Client;
use minio::s3::error::Error; use minio::s3::error::Error;
use minio::s3::response::{MakeBucketResponse, PutObjectContentResponse}; use minio::s3::response::{MakeBucketResponse, PutObjectContentResponse};
use minio::s3::types::{FromS3Response, S3Api}; use minio::s3::types::{FromS3Response, S3Api, S3Request};
use minio_common::cleanup_guard::CleanupGuard; use minio_common::cleanup_guard::CleanupGuard;
use minio_common::test_context::TestContext; use minio_common::test_context::TestContext;
use minio_common::utils::{ use minio_common::utils::{
get_bytes_from_response, get_response_from_bytes, rand_bucket_name, rand_object_name, get_bytes_from_response, get_response_from_bytes, rand_bucket_name, rand_object_name,
}; };
use std::env; use std::env;
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
pub(crate) struct Ctx2 { pub(crate) struct Ctx2 {
@ -67,7 +68,7 @@ impl Ctx2 {
.send() .send()
.await .await
.unwrap(); .unwrap();
let cleanup = CleanupGuard::new(&ctx.client, &bucket_name); let cleanup = CleanupGuard::new(ctx.client.clone(), &bucket_name);
let object_name = rand_object_name(); let object_name = rand_object_name();
let data = bytes::Bytes::from("hello, world".to_string().into_bytes()); let data = bytes::Bytes::from("hello, world".to_string().into_bytes());
let _resp: PutObjectContentResponse = ctx let _resp: PutObjectContentResponse = ctx
@ -90,7 +91,7 @@ impl Ctx2 {
pub async fn new_aux(&mut self) -> String { pub async fn new_aux(&mut self) -> String {
let bucket_name: String = rand_bucket_name(); let bucket_name: String = rand_bucket_name();
self.aux_bucket = Some(bucket_name.clone()); self.aux_bucket = Some(bucket_name.clone());
self._aux_cleanup = Some(CleanupGuard::new(&self.client, &bucket_name)); self._aux_cleanup = Some(CleanupGuard::new(self.client.clone(), &bucket_name));
let _resp: MakeBucketResponse = self let _resp: MakeBucketResponse = self
.client .client
.make_bucket(&bucket_name) .make_bucket(&bucket_name)
@ -140,7 +141,7 @@ pub(crate) fn benchmark_s3_api<ApiType, GlobalSetupFuture>(
// Per-iteration setup for initial request // Per-iteration setup for initial request
let api = per_iter_setup(&ctx); let api = per_iter_setup(&ctx);
let request = api.to_s3request().unwrap(); let request: S3Request = api.to_s3request().unwrap();
// Execute the request to get a response, store the bytes for swift cloning // Execute the request to get a response, store the bytes for swift cloning
let bytes: bytes::Bytes = rt.block_on(async { let bytes: bytes::Bytes = rt.block_on(async {
@ -165,3 +166,11 @@ pub(crate) fn benchmark_s3_api<ApiType, GlobalSetupFuture>(
group.finish(); group.finish();
} }
pub(crate) fn skip_express_mode(bench_name: &str) -> bool {
let skip = TestContext::new_from_env().client.is_minio_express();
if skip {
println!("Skipping benchmark '{}' (MinIO Express mode)", bench_name);
}
skip
}

View File

@ -15,6 +15,7 @@
use async_std::future::timeout; use async_std::future::timeout;
use minio::s3::Client; use minio::s3::Client;
use std::thread; use std::thread;
/// Cleanup guard that removes the bucket when it is dropped /// Cleanup guard that removes the bucket when it is dropped
@ -25,10 +26,10 @@ pub struct CleanupGuard {
impl CleanupGuard { impl CleanupGuard {
#[allow(dead_code)] #[allow(dead_code)]
pub fn new(client: &Client, bucket_name: &str) -> Self { pub fn new<S: Into<String>>(client: Client, bucket_name: S) -> Self {
Self { Self {
client: client.clone(), client,
bucket_name: bucket_name.to_string(), bucket_name: bucket_name.into(),
} }
} }
} }
@ -57,7 +58,9 @@ impl Drop for CleanupGuard {
Ok(_) => { Ok(_) => {
//println!("Bucket {} removed successfully", bucket_name), //println!("Bucket {} removed successfully", bucket_name),
} }
Err(e) => println!("Error removing bucket {}: {:?}", bucket_name, e), Err(_e) => {
//println!("Error removing bucket {}: {:?}", bucket_name, e)
}
}, },
Err(_) => println!("Timeout after 60s while removing bucket {}", bucket_name), Err(_) => println!("Timeout after 60s while removing bucket {}", bucket_name),
} }

View File

@ -13,11 +13,13 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use minio::s3::args::PostPolicy; use chrono::{DateTime, Utc};
use minio::s3::builders::PostPolicy;
use minio::s3::types::{ use minio::s3::types::{
AndOperator, Destination, Filter, LifecycleConfig, LifecycleRule, NotificationConfig, AndOperator, CsvInputSerialization, CsvOutputSerialization, Destination, FileHeaderInfo,
ObjectLockConfig, PrefixFilterRule, QueueConfig, ReplicationConfig, ReplicationRule, Filter, LifecycleConfig, LifecycleRule, NotificationConfig, ObjectLockConfig, PrefixFilterRule,
RetentionMode, SuffixFilterRule, QueueConfig, QuoteFields, ReplicationConfig, ReplicationRule, RetentionMode, SelectRequest,
SuffixFilterRule,
}; };
use minio::s3::utils::utc_now; use minio::s3::utils::utc_now;
use std::collections::HashMap; use std::collections::HashMap;
@ -185,7 +187,7 @@ pub fn create_object_lock_config_example() -> ObjectLockConfig {
ObjectLockConfig::new(RetentionMode::GOVERNANCE, Some(DURATION_DAYS), None).unwrap() ObjectLockConfig::new(RetentionMode::GOVERNANCE, Some(DURATION_DAYS), None).unwrap()
} }
pub fn create_post_policy_example(bucket_name: &str, object_name: &str) -> PostPolicy { pub fn create_post_policy_example(bucket_name: &str, object_name: &str) -> PostPolicy {
let expiration = utc_now() + chrono::Duration::days(5); let expiration: DateTime<Utc> = utc_now() + chrono::Duration::days(5);
let mut policy = PostPolicy::new(&bucket_name, expiration).unwrap(); let mut policy = PostPolicy::new(&bucket_name, expiration).unwrap();
policy.add_equals_condition("key", &object_name).unwrap(); policy.add_equals_condition("key", &object_name).unwrap();
@ -194,3 +196,38 @@ pub fn create_post_policy_example(bucket_name: &str, object_name: &str) -> PostP
.unwrap(); .unwrap();
policy policy
} }
/// return (body, data)
pub fn create_select_content_data() -> (String, String) {
let mut data = String::new();
data.push_str("1997,Ford,E350,\"ac, abs, moon\",3000.00\n");
data.push_str("1999,Chevy,\"Venture \"\"Extended Edition\"\"\",,4900.00\n");
data.push_str("1999,Chevy,\"Venture \"\"Extended Edition, Very Large\"\"\",,5000.00\n");
data.push_str("1996,Jeep,Grand Cherokee,\"MUST SELL!\n");
data.push_str("air, moon roof, loaded\",4799.00\n");
let body = String::from("Year,Make,Model,Description,Price\n") + &data;
(body, data)
}
pub fn create_select_content_request() -> SelectRequest {
let request = SelectRequest::new_csv_input_output(
"select * from S3Object",
CsvInputSerialization {
compression_type: None,
allow_quoted_record_delimiter: false,
comments: None,
field_delimiter: None,
file_header_info: Some(FileHeaderInfo::USE),
quote_character: None,
quote_escape_character: None,
record_delimiter: None,
},
CsvOutputSerialization {
field_delimiter: None,
quote_character: None,
quote_escape_character: None,
quote_fields: Some(QuoteFields::ASNEEDED),
record_delimiter: None,
},
)
.unwrap();
request
}

View File

@ -23,12 +23,12 @@ use std::path::{Path, PathBuf};
#[derive(Clone)] #[derive(Clone)]
pub struct TestContext { pub struct TestContext {
pub client: Client,
pub base_url: BaseUrl, pub base_url: BaseUrl,
pub access_key: String, pub access_key: String,
pub secret_key: String, pub secret_key: String,
pub ignore_cert_check: Option<bool>, pub ignore_cert_check: Option<bool>,
pub ssl_cert_file: Option<PathBuf>, pub ssl_cert_file: Option<PathBuf>,
pub client: Client,
} }
impl TestContext { impl TestContext {
@ -66,12 +66,12 @@ impl TestContext {
.unwrap(); .unwrap();
Self { Self {
client,
base_url, base_url,
access_key, access_key,
secret_key, secret_key,
ignore_cert_check: Some(ignore_cert_check), ignore_cert_check: Some(ignore_cert_check),
ssl_cert_file: ssl_cert_file.map(PathBuf::from), ssl_cert_file: ssl_cert_file.map(PathBuf::from),
client,
} }
} else { } else {
const DEFAULT_SERVER_ENDPOINT: &str = "https://play.min.io/"; const DEFAULT_SERVER_ENDPOINT: &str = "https://play.min.io/";
@ -123,12 +123,12 @@ impl TestContext {
.unwrap(); .unwrap();
Self { Self {
client,
base_url, base_url,
access_key, access_key,
secret_key, secret_key,
ignore_cert_check: Some(ignore_cert_check), ignore_cert_check: Some(ignore_cert_check),
ssl_cert_file: Some(ssl_cert_file), ssl_cert_file: Some(ssl_cert_file),
client,
} }
} }
} }
@ -144,7 +144,7 @@ impl TestContext {
/// - `CleanupGuard` - A guard that automatically deletes the bucket when dropped. /// - `CleanupGuard` - A guard that automatically deletes the bucket when dropped.
/// ///
/// # Example /// # Example
/// ```rust /// ```ignore
/// let (bucket_name, guard) = client.create_bucket_helper().await; /// let (bucket_name, guard) = client.create_bucket_helper().await;
/// println!("Created temporary bucket: {}", bucket_name); /// println!("Created temporary bucket: {}", bucket_name);
/// // The bucket will be removed when `guard` is dropped. /// // The bucket will be removed when `guard` is dropped.
@ -152,7 +152,7 @@ impl TestContext {
pub async fn create_bucket_helper(&self) -> (String, CleanupGuard) { pub async fn create_bucket_helper(&self) -> (String, CleanupGuard) {
let bucket_name = rand_bucket_name(); let bucket_name = rand_bucket_name();
let _resp = self.client.make_bucket(&bucket_name).send().await.unwrap(); let _resp = self.client.make_bucket(&bucket_name).send().await.unwrap();
let guard = CleanupGuard::new(&self.client, &bucket_name); let guard = CleanupGuard::new(self.client.clone(), &bucket_name);
(bucket_name, guard) (bucket_name, guard)
} }
} }

View File

@ -24,7 +24,7 @@ pub fn rand_bucket_name() -> String {
} }
pub fn rand_object_name() -> String { pub fn rand_object_name() -> String {
Alphanumeric.sample_string(&mut rand::thread_rng(), 8) Alphanumeric.sample_string(&mut rand::thread_rng(), 20)
} }
pub async fn get_bytes_from_response(v: Result<reqwest::Response, Error>) -> bytes::Bytes { pub async fn get_bytes_from_response(v: Result<reqwest::Response, Error>) -> bytes::Bytes {

85
examples/append_object.rs Normal file
View File

@ -0,0 +1,85 @@
// 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_localhost};
use minio::s3::Client;
use minio::s3::response::{AppendObjectResponse, StatObjectResponse};
use minio::s3::segmented_bytes::SegmentedBytes;
use minio::s3::types::S3Api;
use rand::Rng;
use rand::distributions::Alphanumeric;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
env_logger::init(); // Note: set environment variable RUST_LOG="INFO" to log info and higher
let client: Client = create_client_on_localhost()?;
if !client.is_minio_express() {
println!("Need (MinIO) Express mode to run this example");
return Ok(());
}
let bucket_name: &str = "append-test-bucket";
create_bucket_if_not_exists(bucket_name, &client).await?;
let object_name: &str = "append-test-object";
let n_segments = 1000;
let segment_size = 1024 * 1024; // 1 KB
let mut offset_bytes = 0;
for i in 0..n_segments {
let rand_str: String = random_string(segment_size);
let data_size = rand_str.len() as u64;
let data: SegmentedBytes = SegmentedBytes::from(rand_str);
let resp: AppendObjectResponse = client
.append_object(bucket_name, object_name, data, offset_bytes)
.send()
.await?;
offset_bytes += data_size;
if resp.object_size != offset_bytes {
panic!(
"from the append_object: size mismatch: expected {}, got {}",
resp.object_size, offset_bytes
)
}
//println!("Append response: {:#?}", resp);
let resp: StatObjectResponse = client.stat_object(bucket_name, object_name).send().await?;
if resp.size != offset_bytes {
panic!(
"from the stat_Object: size mismatch: expected {}, got {}",
resp.size, offset_bytes
)
}
println!("{}/{}", i, n_segments);
//println!("Stat response: {:#?}", resp);
}
Ok(())
}
fn random_string(len: usize) -> String {
rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(len)
.map(char::from)
.collect()
}

View File

@ -21,6 +21,19 @@ pub fn create_client_on_play() -> Result<Client, Box<dyn std::error::Error + Sen
Ok(client) Ok(client)
} }
#[allow(dead_code)]
pub fn create_client_on_localhost() -> Result<Client, Box<dyn std::error::Error + Send + Sync>> {
let base_url = "http://localhost:9000/".parse::<BaseUrl>()?;
log::info!("Trying to connect to MinIO at: `{:?}`", base_url);
let static_provider = StaticProvider::new("minioadmin", "minioadmin", 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( pub async fn create_bucket_if_not_exists(
bucket_name: &str, bucket_name: &str,
client: &Client, client: &Client,

View File

@ -16,10 +16,11 @@
mod common; mod common;
use crate::common::create_bucket_if_not_exists; use crate::common::create_bucket_if_not_exists;
use minio::s3::builders::{ObjectContent, ObjectPrompt}; use minio::s3::builders::ObjectContent;
use minio::s3::client::ClientBuilder; use minio::s3::client::ClientBuilder;
use minio::s3::creds::StaticProvider; use minio::s3::creds::StaticProvider;
use minio::s3::http::BaseUrl; use minio::s3::http::BaseUrl;
use minio::s3::response::ObjectPromptResponse;
use minio::s3::types::S3Api; use minio::s3::types::S3Api;
use std::path::Path; use std::path::Path;
@ -65,12 +66,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
filename.display() filename.display()
); );
let op = ObjectPrompt::new(bucket_name, object_name, "what is it about?") let resp: ObjectPromptResponse = client
.object_prompt(bucket_name, object_name, "what is it about?")
//.lambda_arn("arn:minio:s3-object-lambda::_:webhook") // this is the default value //.lambda_arn("arn:minio:s3-object-lambda::_:webhook") // this is the default value
.client(&client); .send()
.await?;
let res = op.send().await?; log::info!("Object prompt result: '{}'", resp.prompt_response);
log::info!("Object prompt result: '{}'", res.prompt_response);
Ok(()) Ok(())
} }

View File

@ -17,7 +17,7 @@ use clap::Parser;
use log::info; use log::info;
use minio::s3::response::BucketExistsResponse; use minio::s3::response::BucketExistsResponse;
use minio::s3::types::S3Api; use minio::s3::types::S3Api;
use minio::s3::{builders::ObjectContent, client::ClientBuilder, creds::StaticProvider}; use minio::s3::{Client, builders::ObjectContent, client::ClientBuilder, creds::StaticProvider};
use std::path::PathBuf; use std::path::PathBuf;
/// Upload a file to the given bucket and object path on the MinIO Play server. /// Upload a file to the given bucket and object path on the MinIO Play server.
@ -41,7 +41,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
None, None,
); );
let client = ClientBuilder::new("https://play.min.io".parse()?) let client: Client = ClientBuilder::new("https://play.min.io".parse()?)
.provider(Some(Box::new(static_provider))) .provider(Some(Box::new(static_provider)))
.build()?; .build()?;

File diff suppressed because it is too large Load Diff

View File

@ -15,8 +15,10 @@
//! Argument builders for [minio::s3::client::Client](crate::s3::client::Client) APIs //! Argument builders for [minio::s3::client::Client](crate::s3::client::Client) APIs
mod append_object;
mod bucket_common; mod bucket_common;
mod bucket_exists; mod bucket_exists;
mod copy_object;
mod delete_bucket_encryption; mod delete_bucket_encryption;
mod delete_bucket_lifecycle; mod delete_bucket_lifecycle;
mod delete_bucket_notification; mod delete_bucket_notification;
@ -38,16 +40,19 @@ mod get_object;
mod get_object_lock_config; mod get_object_lock_config;
mod get_object_retention; mod get_object_retention;
mod get_object_tags; mod get_object_tags;
mod get_presigned_object_url;
mod get_presigned_policy_form_data;
mod get_region;
mod is_object_legal_hold_enabled; mod is_object_legal_hold_enabled;
mod list_buckets; mod list_buckets;
mod list_objects; mod list_objects;
mod listen_bucket_notification; mod listen_bucket_notification;
mod make_bucket; mod make_bucket;
mod object_content;
mod object_prompt; mod object_prompt;
mod put_object; mod put_object;
mod remove_bucket; mod remove_bucket;
mod remove_objects; mod remove_objects;
mod select_object_content;
mod set_bucket_encryption; mod set_bucket_encryption;
mod set_bucket_lifecycle; mod set_bucket_lifecycle;
mod set_bucket_notification; mod set_bucket_notification;
@ -58,9 +63,13 @@ mod set_bucket_versioning;
mod set_object_lock_config; mod set_object_lock_config;
mod set_object_retention; mod set_object_retention;
mod set_object_tags; mod set_object_tags;
mod stat_object;
pub use crate::s3::object_content::*;
pub use append_object::*;
pub use bucket_common::*; pub use bucket_common::*;
pub use bucket_exists::*; pub use bucket_exists::*;
pub use copy_object::*;
pub use delete_bucket_encryption::*; pub use delete_bucket_encryption::*;
pub use delete_bucket_lifecycle::*; pub use delete_bucket_lifecycle::*;
pub use delete_bucket_notification::*; pub use delete_bucket_notification::*;
@ -82,16 +91,19 @@ pub use get_object::*;
pub use get_object_lock_config::*; pub use get_object_lock_config::*;
pub use get_object_retention::*; pub use get_object_retention::*;
pub use get_object_tags::*; pub use get_object_tags::*;
pub use get_presigned_object_url::*;
pub use get_presigned_policy_form_data::*;
pub use get_region::*;
pub use is_object_legal_hold_enabled::*; pub use is_object_legal_hold_enabled::*;
pub use list_buckets::*; pub use list_buckets::*;
pub use list_objects::*; pub use list_objects::*;
pub use listen_bucket_notification::*; pub use listen_bucket_notification::*;
pub use make_bucket::*; pub use make_bucket::*;
pub use object_content::*;
pub use object_prompt::*; pub use object_prompt::*;
pub use put_object::*; pub use put_object::*;
pub use remove_bucket::*; pub use remove_bucket::*;
pub use remove_objects::*; pub use remove_objects::*;
pub use select_object_content::*;
pub use set_bucket_encryption::*; pub use set_bucket_encryption::*;
pub use set_bucket_lifecycle::*; pub use set_bucket_lifecycle::*;
pub use set_bucket_notification::*; pub use set_bucket_notification::*;
@ -102,3 +114,4 @@ pub use set_bucket_versioning::*;
pub use set_object_lock_config::*; pub use set_object_lock_config::*;
pub use set_object_retention::*; pub use set_object_retention::*;
pub use set_object_tags::*; pub use set_object_tags::*;
pub use stat_object::*;

View File

@ -0,0 +1,331 @@
// 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::{
ContentStream, MAX_MULTIPART_COUNT, ObjectContent, Size, calc_part_info,
};
use crate::s3::error::Error;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::{AppendObjectResponse, StatObjectResponse};
use crate::s3::segmented_bytes::SegmentedBytes;
use crate::s3::sse::Sse;
use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::{check_bucket_name, check_object_name};
use http::Method;
use std::sync::Arc;
// region: append-object
#[derive(Debug, Clone, Default)]
pub struct AppendObject {
client: Client,
extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>,
bucket: String,
object: String,
region: Option<String>,
sse: Option<Arc<dyn Sse>>,
data: SegmentedBytes,
/// value of x-amz-write-offset-bytes
offset_bytes: u64,
}
impl AppendObject {
pub fn new(
client: Client,
bucket: String,
object: String,
data: SegmentedBytes,
offset_bytes: u64,
) -> Self {
Self {
client,
bucket,
object,
offset_bytes,
data,
..Default::default()
}
}
pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers;
self
}
pub fn extra_query_params(mut self, extra_query_params: Option<Multimap>) -> Self {
self.extra_query_params = extra_query_params;
self
}
}
impl S3Api for AppendObject {
type S3Response = AppendObjectResponse;
}
impl ToS3Request for AppendObject {
fn to_s3request(self) -> Result<S3Request, Error> {
{
check_bucket_name(&self.bucket, true)?;
check_object_name(&self.object)?;
if let Some(v) = &self.sse {
if v.tls_required() && !self.client.is_secure() {
return Err(Error::SseTlsRequired(None));
}
}
}
let mut headers: Multimap = self.extra_headers.unwrap_or_default();
headers.add("x-amz-write-offset-bytes", self.offset_bytes.to_string());
Ok(S3Request::new(self.client, Method::PUT)
.region(self.region)
.bucket(Some(self.bucket))
.query_params(self.extra_query_params.unwrap_or_default())
.object(Some(self.object))
.headers(headers)
.body(Some(self.data)))
}
}
// endregion: append-object
// region: append-object-content
/// AppendObjectContent takes a `ObjectContent` stream and appends it to MinIO/S3.
///
/// It is a higher level API and handles multipart appends transparently.
pub struct AppendObjectContent {
client: Client,
extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>,
region: Option<String>,
bucket: String,
object: String,
sse: Option<Arc<dyn Sse>>,
part_size: Size,
// source data
input_content: ObjectContent,
// Computed.
content_stream: ContentStream,
part_count: Option<u16>,
/// Value of x-amz-write-offset-bytes
offset_bytes: u64,
}
impl AppendObjectContent {
pub fn new(
client: Client,
bucket: String,
object: String,
content: impl Into<ObjectContent>,
) -> Self {
Self {
client,
bucket,
object,
input_content: content.into(),
extra_headers: None,
extra_query_params: None,
region: None,
sse: None,
part_size: Size::Unknown,
content_stream: ContentStream::empty(),
part_count: None,
offset_bytes: 0,
}
}
pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers;
self
}
pub fn extra_query_params(mut self, extra_query_params: Option<Multimap>) -> Self {
self.extra_query_params = extra_query_params;
self
}
pub fn region(mut self, region: Option<String>) -> Self {
self.region = region;
self
}
pub fn part_size(mut self, part_size: impl Into<Size>) -> Self {
self.part_size = part_size.into();
self
}
pub fn offset_bytes(mut self, offset_bytes: u64) -> Self {
self.offset_bytes = offset_bytes;
self
}
pub async fn send(mut self) -> Result<AppendObjectResponse, Error> {
{
check_bucket_name(&self.bucket, true)?;
check_object_name(&self.object)?;
if let Some(v) = &self.sse {
if v.tls_required() && !self.client.is_secure() {
return Err(Error::SseTlsRequired(None));
}
}
}
{
let mut headers: Multimap = match self.extra_headers {
Some(ref headers) => headers.clone(),
None => Multimap::new(),
};
headers.add("x-amz-write-offset-bytes", self.offset_bytes.to_string());
self.extra_query_params = Some(headers);
}
self.content_stream = std::mem::take(&mut self.input_content)
.to_content_stream()
.await
.map_err(Error::IOError)?;
// object_size may be Size::Unknown.
let object_size = self.content_stream.get_size();
let (part_size, n_expected_parts) = calc_part_info(object_size, self.part_size)?;
// Set the chosen part size and part count.
self.part_size = Size::Known(part_size);
self.part_count = n_expected_parts;
// Read the first part.
let seg_bytes = self.content_stream.read_upto(part_size as usize).await?;
// get the length (if any) of the current file
let resp: StatObjectResponse = self
.client
.stat_object(&self.bucket, &self.object)
.send()
.await?;
//println!("statObjectResponse={:#?}", resp);
let current_file_size = resp.size;
// In the first part read, if:
//
// - object_size is unknown AND we got less than the part size, OR
// - we are expecting only one part to be uploaded,
//
// we upload it as a simple put object.
if (object_size.is_unknown() && (seg_bytes.len() as u64) < part_size)
|| n_expected_parts == Some(1)
{
let ao = AppendObject {
client: self.client,
extra_headers: self.extra_headers,
extra_query_params: self.extra_query_params,
bucket: self.bucket,
object: self.object,
region: self.region,
offset_bytes: current_file_size,
sse: self.sse,
data: seg_bytes,
};
ao.send().await
} else if object_size.is_known() && (seg_bytes.len() as u64) < part_size {
// Not enough data!
let expected = object_size.as_u64().unwrap();
let got = seg_bytes.len() as u64;
Err(Error::InsufficientData(expected, got))
} else {
// Otherwise, we start a multipart append.
self.send_mpa(part_size, current_file_size, seg_bytes).await
}
}
/// multipart append
async fn send_mpa(
&mut self,
part_size: u64,
object_size: u64,
first_part: SegmentedBytes,
) -> Result<AppendObjectResponse, Error> {
let mut done = false;
let mut part_number = 0;
let mut last_resp: Option<AppendObjectResponse> = None;
let mut next_offset_bytes: u64 = object_size;
//println!("initial offset_bytes: {}", next_offset_bytes);
let mut first_part = Some(first_part);
while !done {
let part_content: SegmentedBytes = {
if let Some(v) = first_part.take() {
v
} else {
self.content_stream.read_upto(part_size as usize).await?
}
};
part_number += 1;
let buffer_size = part_content.len() as u64;
assert!(
buffer_size <= part_size,
"{:?} <= {:?}",
buffer_size,
part_size
);
if buffer_size == 0 && part_number > 1 {
// We are done as we appended at least 1 part and we have
// reached the end of the stream.
break;
}
// Check if we have too many parts to upload.
if self.part_count.is_none() && part_number > MAX_MULTIPART_COUNT {
return Err(Error::TooManyParts);
}
// Append the part now.
let append_object = AppendObject {
client: self.client.clone(),
extra_headers: self.extra_headers.clone(),
extra_query_params: self.extra_query_params.clone(),
bucket: self.bucket.clone(),
object: self.object.clone(),
region: self.region.clone(),
sse: self.sse.clone(),
data: part_content,
offset_bytes: next_offset_bytes,
};
let resp: AppendObjectResponse = append_object.send().await?;
//println!("AppendObjectResponse: object_size={:?}", resp.object_size);
next_offset_bytes = resp.object_size;
// Finally check if we are done.
if buffer_size < part_size {
done = true;
last_resp = Some(resp);
}
}
Ok(last_resp.unwrap())
}
}
// endregion: append-object-content

View File

@ -15,11 +15,12 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use crate::s3::{client::Client, utils::Multimap}; use crate::s3::client::Client;
use crate::s3::multimap::Multimap;
#[derive(Clone, Debug, Default)] #[derive(Clone, Default)]
pub struct BucketCommon<A> { pub struct BucketCommon<A> {
pub(crate) client: Option<Client>, pub(crate) client: Client,
pub(crate) extra_headers: Option<Multimap>, pub(crate) extra_headers: Option<Multimap>,
pub(crate) extra_query_params: Option<Multimap>, pub(crate) extra_query_params: Option<Multimap>,
@ -30,18 +31,14 @@ pub struct BucketCommon<A> {
} }
impl<A: Default> BucketCommon<A> { impl<A: Default> BucketCommon<A> {
pub fn new(bucket: &str) -> BucketCommon<A> { pub fn new(client: Client, bucket: String) -> BucketCommon<A> {
BucketCommon { BucketCommon {
bucket: bucket.to_owned(), client,
bucket,
..Default::default() ..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers; self.extra_headers = extra_headers;
self self

View File

@ -13,7 +13,6 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::s3::Client;
use crate::s3::builders::BucketCommon; use crate::s3::builders::BucketCommon;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::response::BucketExistsResponse; use crate::s3::response::BucketExistsResponse;
@ -32,30 +31,13 @@ impl S3Api for BucketExists {
} }
impl ToS3Request for BucketExists { impl ToS3Request for BucketExists {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
let headers = self Ok(S3Request::new(self.client, Method::HEAD)
.extra_headers .region(self.region)
.as_ref() .bucket(Some(self.bucket))
.filter(|v| !v.is_empty()) .query_params(self.extra_query_params.unwrap_or_default())
.cloned() .headers(self.extra_headers.unwrap_or_default()))
.unwrap_or_default();
let query_params = self
.extra_query_params
.as_ref()
.filter(|v| !v.is_empty())
.cloned()
.unwrap_or_default();
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?;
let req = S3Request::new(client, Method::HEAD)
.region(self.region.as_deref())
.bucket(Some(&self.bucket))
.query_params(query_params)
.headers(headers);
Ok(req)
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -13,12 +13,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::s3::Client;
use crate::s3::builders::BucketCommon; use crate::s3::builders::BucketCommon;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::response::DeleteBucketEncryptionResponse; use crate::s3::response::DeleteBucketEncryptionResponse;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::check_bucket_name; use crate::s3::utils::{check_bucket_name, insert};
use http::Method; use http::Method;
/// Argument builder for [delete_bucket_encryption()](Client::delete_bucket_encryption) API /// Argument builder for [delete_bucket_encryption()](Client::delete_bucket_encryption) API
@ -32,32 +31,13 @@ impl S3Api for DeleteBucketEncryption {
} }
impl ToS3Request for DeleteBucketEncryption { impl ToS3Request for DeleteBucketEncryption {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
let headers = self Ok(S3Request::new(self.client, Method::DELETE)
.extra_headers .region(self.region)
.as_ref() .bucket(Some(self.bucket))
.filter(|v| !v.is_empty()) .query_params(insert(self.extra_query_params, "encryption"))
.cloned() .headers(self.extra_headers.unwrap_or_default()))
.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("encryption".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)
} }
} }

View File

@ -13,12 +13,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::s3::Client;
use crate::s3::builders::BucketCommon; use crate::s3::builders::BucketCommon;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::response::DeleteBucketLifecycleResponse; use crate::s3::response::DeleteBucketLifecycleResponse;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::check_bucket_name; use crate::s3::utils::{check_bucket_name, insert};
use http::Method; use http::Method;
/// Argument builder for [delete_bucket_lifecycle()](Client::delete_bucket_lifecycle) API /// Argument builder for [delete_bucket_lifecycle()](Client::delete_bucket_lifecycle) API
@ -32,32 +31,13 @@ impl S3Api for DeleteBucketLifecycle {
} }
impl ToS3Request for DeleteBucketLifecycle { impl ToS3Request for DeleteBucketLifecycle {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
let headers = self Ok(S3Request::new(self.client, Method::DELETE)
.extra_headers .region(self.region)
.as_ref() .bucket(Some(self.bucket))
.filter(|v| !v.is_empty()) .query_params(insert(self.extra_query_params, "lifecycle"))
.cloned() .headers(self.extra_headers.unwrap_or_default()))
.unwrap_or_default();
let mut query_params = self
.extra_query_params
.as_ref()
.filter(|v| !v.is_empty())
.cloned()
.unwrap_or_default();
query_params.insert("lifecycle".into(), String::new());
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?;
let req = S3Request::new(client, Method::DELETE)
.region(self.region.as_deref())
.bucket(Some(&self.bucket))
.query_params(query_params)
.headers(headers);
Ok(req)
} }
} }

View File

@ -13,12 +13,12 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::s3::Client; use crate::s3::builders::BucketCommon;
use crate::s3::builders::{BucketCommon, SegmentedBytes};
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::response::DeleteBucketNotificationResponse; use crate::s3::response::DeleteBucketNotificationResponse;
use crate::s3::segmented_bytes::SegmentedBytes;
use crate::s3::types::{NotificationConfig, S3Api, S3Request, ToS3Request}; use crate::s3::types::{NotificationConfig, S3Api, S3Request, ToS3Request};
use crate::s3::utils::check_bucket_name; use crate::s3::utils::{check_bucket_name, insert};
use bytes::Bytes; use bytes::Bytes;
use http::Method; use http::Method;
@ -33,24 +33,9 @@ impl S3Api for DeleteBucketNotification {
} }
impl ToS3Request for DeleteBucketNotification { impl ToS3Request for DeleteBucketNotification {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; 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 { const CONFIG: NotificationConfig = NotificationConfig {
cloud_func_config_list: None, cloud_func_config_list: None,
queue_config_list: None, queue_config_list: None,
@ -60,15 +45,11 @@ impl ToS3Request for DeleteBucketNotification {
let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(bytes)); let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(bytes));
//TODO consider const body //TODO consider const body
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?; Ok(S3Request::new(self.client, Method::PUT)
.region(self.region)
let req = S3Request::new(client, Method::PUT) .bucket(Some(self.bucket))
.region(self.region.as_deref()) .query_params(insert(self.extra_query_params, "notification"))
.bucket(Some(&self.bucket)) .headers(self.extra_headers.unwrap_or_default())
.query_params(query_params) .body(body))
.headers(headers)
.body(body);
Ok(req)
} }
} }

View File

@ -13,12 +13,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::s3::Client;
use crate::s3::builders::BucketCommon; use crate::s3::builders::BucketCommon;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::response::DeleteBucketPolicyResponse; use crate::s3::response::DeleteBucketPolicyResponse;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::check_bucket_name; use crate::s3::utils::{check_bucket_name, insert};
use http::Method; use http::Method;
/// Argument builder for [delete_bucket_policy()](Client::delete_bucket_policy) API /// Argument builder for [delete_bucket_policy()](Client::delete_bucket_policy) API
@ -32,32 +31,13 @@ impl S3Api for DeleteBucketPolicy {
} }
impl ToS3Request for DeleteBucketPolicy { impl ToS3Request for DeleteBucketPolicy {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
let headers = self Ok(S3Request::new(self.client, Method::DELETE)
.extra_headers .region(self.region)
.as_ref() .bucket(Some(self.bucket))
.filter(|v| !v.is_empty()) .query_params(insert(self.extra_query_params, "policy"))
.cloned() .headers(self.extra_headers.unwrap_or_default()))
.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("policy"), 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)
} }
} }

View File

@ -13,12 +13,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::s3::Client;
use crate::s3::builders::BucketCommon; use crate::s3::builders::BucketCommon;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::response::DeleteBucketReplicationResponse; use crate::s3::response::DeleteBucketReplicationResponse;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::check_bucket_name; use crate::s3::utils::{check_bucket_name, insert};
use http::Method; use http::Method;
/// Argument builder for [delete_bucket_replication()](Client::delete_bucket_replication) API /// Argument builder for [delete_bucket_replication()](Client::delete_bucket_replication) API
@ -32,32 +31,13 @@ impl S3Api for DeleteBucketReplication {
} }
impl ToS3Request for DeleteBucketReplication { impl ToS3Request for DeleteBucketReplication {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
let headers = self Ok(S3Request::new(self.client, Method::DELETE)
.extra_headers .region(self.region)
.as_ref() .bucket(Some(self.bucket))
.filter(|v| !v.is_empty()) .query_params(insert(self.extra_query_params, "replication"))
.cloned() .headers(self.extra_headers.unwrap_or_default()))
.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)
} }
} }

View File

@ -13,12 +13,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::s3::Client;
use crate::s3::builders::BucketCommon; use crate::s3::builders::BucketCommon;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::response::DeleteBucketTagsResponse; use crate::s3::response::DeleteBucketTagsResponse;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::check_bucket_name; use crate::s3::utils::{check_bucket_name, insert};
use http::Method; use http::Method;
/// Argument builder for [delete_bucket_tags()](Client::delete_bucket_tags) API /// Argument builder for [delete_bucket_tags()](Client::delete_bucket_tags) API
@ -32,32 +31,13 @@ impl S3Api for DeleteBucketTags {
} }
impl ToS3Request for DeleteBucketTags { impl ToS3Request for DeleteBucketTags {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
let headers = self Ok(S3Request::new(self.client, Method::DELETE)
.extra_headers .region(self.region)
.as_ref() .bucket(Some(self.bucket))
.filter(|v| !v.is_empty()) .query_params(insert(self.extra_query_params, "tagging"))
.cloned() .headers(self.extra_headers.unwrap_or_default()))
.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)
} }
} }

View File

@ -13,12 +13,12 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::s3::Client; use crate::s3::builders::BucketCommon;
use crate::s3::builders::{BucketCommon, SegmentedBytes};
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::response::DeleteObjectLockConfigResponse; use crate::s3::response::DeleteObjectLockConfigResponse;
use crate::s3::segmented_bytes::SegmentedBytes;
use crate::s3::types::{ObjectLockConfig, S3Api, S3Request, ToS3Request}; use crate::s3::types::{ObjectLockConfig, S3Api, S3Request, ToS3Request};
use crate::s3::utils::check_bucket_name; use crate::s3::utils::{check_bucket_name, insert};
use bytes::Bytes; use bytes::Bytes;
use http::Method; use http::Method;
@ -33,24 +33,9 @@ impl S3Api for DeleteObjectLockConfig {
} }
impl ToS3Request for DeleteObjectLockConfig { impl ToS3Request for DeleteObjectLockConfig {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; 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("object-lock"), String::new());
let config = ObjectLockConfig { let config = ObjectLockConfig {
retention_mode: None, retention_mode: None,
retention_duration_days: None, retention_duration_days: None,
@ -60,15 +45,11 @@ impl ToS3Request for DeleteObjectLockConfig {
let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(bytes)); let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(bytes));
//TODO consider const body //TODO consider const body
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?; Ok(S3Request::new(self.client, Method::PUT)
.region(self.region)
let req = S3Request::new(client, Method::PUT) .bucket(Some(self.bucket))
.region(self.region.as_deref()) .query_params(insert(self.extra_query_params, "object-lock"))
.bucket(Some(&self.bucket)) .headers(self.extra_headers.unwrap_or_default())
.query_params(query_params) .body(body))
.headers(headers)
.body(body);
Ok(req)
} }
} }

View File

@ -15,36 +15,35 @@
use crate::s3::Client; use crate::s3::Client;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::DeleteObjectTagsResponse; use crate::s3::response::DeleteObjectTagsResponse;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::{Multimap, check_bucket_name}; use crate::s3::utils::{check_bucket_name, check_object_name, insert};
use http::Method; use http::Method;
/// Argument builder for [delete_object_tags()](Client::delete_object_tags) API /// Argument builder for [delete_object_tags()](Client::delete_object_tags) API
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct DeleteObjectTags { pub struct DeleteObjectTags {
pub client: Option<Client>, client: Client,
pub extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
pub extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
pub region: Option<String>, region: Option<String>,
pub bucket: String, bucket: String,
pub object: String, object: String,
pub version_id: Option<String>, version_id: Option<String>,
} }
impl DeleteObjectTags { impl DeleteObjectTags {
pub fn new(bucket: &str) -> Self { pub fn new(client: Client, bucket: String, object: String) -> Self {
Self { Self {
bucket: bucket.to_owned(), client,
bucket,
object,
..Default::default() ..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers; self.extra_headers = extra_headers;
@ -61,11 +60,6 @@ impl DeleteObjectTags {
self self
} }
pub fn object(mut self, object: String) -> Self {
self.object = object;
self
}
pub fn version_id(mut self, version_id: Option<String>) -> Self { pub fn version_id(mut self, version_id: Option<String>) -> Self {
self.version_id = version_id; self.version_id = version_id;
self self
@ -77,36 +71,18 @@ impl S3Api for DeleteObjectTags {
} }
impl ToS3Request for DeleteObjectTags { impl ToS3Request for DeleteObjectTags {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
check_object_name(&self.object)?;
let headers = self let mut query_params: Multimap = insert(self.extra_query_params, "tagging");
.extra_headers query_params.add_version(self.version_id);
.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();
if let Some(v) = &self.version_id { Ok(S3Request::new(self.client, Method::DELETE)
query_params.insert(String::from("versionId"), v.to_string()); .region(self.region)
} .bucket(Some(self.bucket))
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) .query_params(query_params)
.object(Some(&self.object)) .object(Some(self.object))
.headers(headers); .headers(self.extra_headers.unwrap_or_default()))
Ok(req)
} }
} }

View File

@ -14,41 +14,39 @@
// limitations under the License. // limitations under the License.
use crate::s3::Client; use crate::s3::Client;
use crate::s3::builders::SegmentedBytes;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::DisableObjectLegalHoldResponse; use crate::s3::response::DisableObjectLegalHoldResponse;
use crate::s3::segmented_bytes::SegmentedBytes;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::{Multimap, check_bucket_name, md5sum_hash}; use crate::s3::utils::{check_bucket_name, check_object_name, insert, md5sum_hash};
use bytes::Bytes; use bytes::Bytes;
use http::Method; use http::Method;
/// Argument builder for [disable_object_legal_hold()](Client::disable_object_legal_hold) API /// Argument builder for [disable_object_legal_hold()](Client::disable_object_legal_hold) API
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct DisableObjectLegalHold { pub struct DisableObjectLegalHold {
pub(crate) client: Option<Client>, client: Client,
pub(crate) extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
pub(crate) extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
pub(crate) region: Option<String>, region: Option<String>,
pub(crate) bucket: String, bucket: String,
pub(crate) object: String, object: String,
pub(crate) version_id: Option<String>, version_id: Option<String>,
} }
impl DisableObjectLegalHold { impl DisableObjectLegalHold {
pub fn new(bucket: &str) -> Self { pub fn new(client: Client, bucket: String, object: String) -> Self {
Self { Self {
bucket: bucket.to_owned(), client,
bucket,
object,
..Default::default() ..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers; self.extra_headers = extra_headers;
self self
@ -59,11 +57,6 @@ impl DisableObjectLegalHold {
self self
} }
pub fn object(mut self, object: String) -> Self {
self.object = object;
self
}
pub fn version_id(mut self, version_id: Option<String>) -> Self { pub fn version_id(mut self, version_id: Option<String>) -> Self {
self.version_id = version_id; self.version_id = version_id;
self self
@ -75,42 +68,25 @@ impl S3Api for DisableObjectLegalHold {
} }
impl ToS3Request for DisableObjectLegalHold { impl ToS3Request for DisableObjectLegalHold {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
check_object_name(&self.object)?;
let mut headers = self let mut headers: Multimap = self.extra_headers.unwrap_or_default();
.extra_headers let mut query_params: Multimap = insert(self.extra_query_params, "legal-hold");
.as_ref() query_params.add_version(self.version_id);
.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();
if let Some(v) = &self.version_id {
query_params.insert(String::from("versionId"), v.to_string());
}
query_params.insert(String::from("legal-hold"), String::new());
const PAYLOAD: &str = "<LegalHold><Status>OFF</Status></LegalHold>"; const PAYLOAD: &str = "<LegalHold><Status>OFF</Status></LegalHold>";
headers.insert(String::from("Content-MD5"), md5sum_hash(PAYLOAD.as_ref())); headers.add("Content-MD5", md5sum_hash(PAYLOAD.as_ref()));
let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(Bytes::from(PAYLOAD))); let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(Bytes::from(PAYLOAD)));
//TODO consider const body //TODO consider const body
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?; Ok(S3Request::new(self.client, Method::PUT)
.region(self.region)
let req = S3Request::new(client, Method::PUT) .bucket(Some(self.bucket))
.region(self.region.as_deref())
.bucket(Some(&self.bucket))
.query_params(query_params) .query_params(query_params)
.headers(headers) .headers(headers)
.object(Some(&self.object)) .object(Some(self.object))
.body(body); .body(body))
Ok(req)
} }
} }

View File

@ -14,41 +14,39 @@
// limitations under the License. // limitations under the License.
use crate::s3::Client; use crate::s3::Client;
use crate::s3::builders::SegmentedBytes;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::EnableObjectLegalHoldResponse; use crate::s3::response::EnableObjectLegalHoldResponse;
use crate::s3::segmented_bytes::SegmentedBytes;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::{Multimap, check_bucket_name, md5sum_hash}; use crate::s3::utils::{check_bucket_name, check_object_name, insert, md5sum_hash};
use bytes::Bytes; use bytes::Bytes;
use http::Method; use http::Method;
/// Argument builder for [enable_object_legal_hold()](Client::enable_object_legal_hold) API /// Argument builder for [enable_object_legal_hold()](Client::enable_object_legal_hold) API
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct EnableObjectLegalHold { pub struct EnableObjectLegalHold {
pub(crate) client: Option<Client>, client: Client,
pub(crate) extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
pub(crate) extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
pub(crate) region: Option<String>, region: Option<String>,
pub(crate) bucket: String, bucket: String,
pub(crate) object: String, object: String,
pub(crate) version_id: Option<String>, version_id: Option<String>,
} }
impl EnableObjectLegalHold { impl EnableObjectLegalHold {
pub fn new(bucket: &str) -> Self { pub fn new(client: Client, bucket: String, object: String) -> Self {
Self { Self {
bucket: bucket.to_owned(), client,
bucket,
object,
..Default::default() ..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers; self.extra_headers = extra_headers;
self self
@ -59,11 +57,6 @@ impl EnableObjectLegalHold {
self self
} }
pub fn object(mut self, object: String) -> Self {
self.object = object;
self
}
pub fn version_id(mut self, version_id: Option<String>) -> Self { pub fn version_id(mut self, version_id: Option<String>) -> Self {
self.version_id = version_id; self.version_id = version_id;
self self
@ -75,42 +68,25 @@ impl S3Api for EnableObjectLegalHold {
} }
impl ToS3Request for EnableObjectLegalHold { impl ToS3Request for EnableObjectLegalHold {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
check_object_name(&self.object)?;
let mut headers = self let mut headers: Multimap = self.extra_headers.unwrap_or_default();
.extra_headers let mut query_params: Multimap = insert(self.extra_query_params, "legal-hold");
.as_ref() query_params.add_version(self.version_id);
.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();
if let Some(v) = &self.version_id {
query_params.insert(String::from("versionId"), v.to_string());
}
query_params.insert(String::from("legal-hold"), String::new());
const PAYLOAD: &str = "<LegalHold><Status>ON</Status></LegalHold>"; const PAYLOAD: &str = "<LegalHold><Status>ON</Status></LegalHold>";
headers.insert(String::from("Content-MD5"), md5sum_hash(PAYLOAD.as_ref())); headers.add("Content-MD5", md5sum_hash(PAYLOAD.as_ref()));
let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(Bytes::from(PAYLOAD))); let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(Bytes::from(PAYLOAD)));
//TODO consider const body //TODO consider const body
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?; Ok(S3Request::new(self.client, Method::PUT)
.region(self.region)
let req = S3Request::new(client, Method::PUT) .bucket(Some(self.bucket))
.region(self.region.as_deref())
.bucket(Some(&self.bucket))
.query_params(query_params) .query_params(query_params)
.headers(headers) .headers(headers)
.object(Some(&self.object)) .object(Some(self.object))
.body(body); .body(body))
Ok(req)
} }
} }

View File

@ -17,7 +17,7 @@ use crate::s3::builders::BucketCommon;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::response::GetBucketEncryptionResponse; use crate::s3::response::GetBucketEncryptionResponse;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::{Multimap, check_bucket_name, merge}; use crate::s3::utils::{check_bucket_name, insert};
use http::Method; use http::Method;
/// Argument builder for [get_bucket_encryption()](crate::s3::client::Client::get_bucket_encryption) API /// Argument builder for [get_bucket_encryption()](crate::s3::client::Client::get_bucket_encryption) API
@ -31,27 +31,13 @@ impl S3Api for GetBucketEncryption {
} }
impl ToS3Request for GetBucketEncryption { impl ToS3Request for GetBucketEncryption {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; 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(); Ok(S3Request::new(self.client, Method::GET)
if let Some(v) = &self.extra_query_params { .region(self.region)
merge(&mut query_params, v); .bucket(Some(self.bucket))
} .query_params(insert(self.extra_query_params, "encryption"))
query_params.insert(String::from("encryption"), String::new()); .headers(self.extra_headers.unwrap_or_default()))
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)
} }
} }

View File

@ -13,12 +13,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::s3::Client;
use crate::s3::builders::BucketCommon; use crate::s3::builders::BucketCommon;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::response::GetBucketLifecycleResponse; use crate::s3::response::GetBucketLifecycleResponse;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::check_bucket_name; use crate::s3::utils::{check_bucket_name, insert};
use http::Method; use http::Method;
/// Argument builder for [get_bucket_lifecycle()](Client::get_bucket_lifecycle) API /// Argument builder for [get_bucket_lifecycle()](Client::get_bucket_lifecycle) API
@ -32,32 +31,13 @@ impl S3Api for GetBucketLifecycle {
} }
impl ToS3Request for GetBucketLifecycle { impl ToS3Request for GetBucketLifecycle {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
let headers = self Ok(S3Request::new(self.client, Method::GET)
.extra_headers .region(self.region)
.as_ref() .bucket(Some(self.bucket))
.filter(|v| !v.is_empty()) .query_params(insert(self.extra_query_params, "lifecycle"))
.cloned() .headers(self.extra_headers.unwrap_or_default()))
.unwrap_or_default();
let mut query_params = self
.extra_query_params
.as_ref()
.filter(|v| !v.is_empty())
.cloned()
.unwrap_or_default();
query_params.insert("lifecycle".into(), String::new());
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?;
let req = S3Request::new(client, Method::GET)
.region(self.region.as_deref())
.bucket(Some(&self.bucket))
.query_params(query_params)
.headers(headers);
Ok(req)
} }
} }

View File

@ -13,12 +13,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::s3::Client;
use crate::s3::builders::BucketCommon; use crate::s3::builders::BucketCommon;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::response::GetBucketNotificationResponse; use crate::s3::response::GetBucketNotificationResponse;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::check_bucket_name; use crate::s3::utils::{check_bucket_name, insert};
use http::Method; use http::Method;
/// Argument builder for [get_bucket_notification()](Client::get_bucket_notification) API /// Argument builder for [get_bucket_notification()](Client::get_bucket_notification) API
@ -32,32 +31,13 @@ impl S3Api for GetBucketNotification {
} }
impl ToS3Request for GetBucketNotification { impl ToS3Request for GetBucketNotification {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
let headers = self Ok(S3Request::new(self.client, Method::GET)
.extra_headers .region(self.region)
.as_ref() .bucket(Some(self.bucket))
.filter(|v| !v.is_empty()) .query_params(insert(self.extra_query_params, "notification"))
.cloned() .headers(self.extra_headers.unwrap_or_default()))
.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)
} }
} }

View File

@ -13,12 +13,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::s3::Client;
use crate::s3::builders::BucketCommon; use crate::s3::builders::BucketCommon;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::response::GetBucketPolicyResponse; use crate::s3::response::GetBucketPolicyResponse;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::check_bucket_name; use crate::s3::utils::{check_bucket_name, insert};
use http::Method; use http::Method;
/// Argument builder for [get_bucket_policy()](Client::get_bucket_policy) API /// Argument builder for [get_bucket_policy()](Client::get_bucket_policy) API
@ -32,32 +31,13 @@ impl S3Api for GetBucketPolicy {
} }
impl ToS3Request for GetBucketPolicy { impl ToS3Request for GetBucketPolicy {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
let headers = self Ok(S3Request::new(self.client, Method::GET)
.extra_headers .region(self.region)
.as_ref() .bucket(Some(self.bucket))
.filter(|v| !v.is_empty()) .query_params(insert(self.extra_query_params, "policy"))
.cloned() .headers(self.extra_headers.unwrap_or_default()))
.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("policy"), 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)
} }
} }

View File

@ -13,12 +13,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::s3::Client;
use crate::s3::builders::BucketCommon; use crate::s3::builders::BucketCommon;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::response::GetBucketReplicationResponse; use crate::s3::response::GetBucketReplicationResponse;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::check_bucket_name; use crate::s3::utils::{check_bucket_name, insert};
use http::Method; use http::Method;
/// Argument builder for [get_bucket_replication()](Client::get_bucket_replication) API /// Argument builder for [get_bucket_replication()](Client::get_bucket_replication) API
@ -32,32 +31,13 @@ impl S3Api for GetBucketReplication {
} }
impl ToS3Request for GetBucketReplication { impl ToS3Request for GetBucketReplication {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
let headers = self Ok(S3Request::new(self.client, Method::GET)
.extra_headers .region(self.region)
.as_ref() .bucket(Some(self.bucket))
.filter(|v| !v.is_empty()) .query_params(insert(self.extra_query_params, "replication"))
.cloned() .headers(self.extra_headers.unwrap_or_default()))
.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)
} }
} }

View File

@ -15,38 +15,35 @@
use crate::s3::Client; use crate::s3::Client;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::multimap::Multimap;
use crate::s3::response::GetBucketTagsResponse; use crate::s3::response::GetBucketTagsResponse;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::{Multimap, check_bucket_name}; use crate::s3::utils::{check_bucket_name, insert};
use http::Method; use http::Method;
use std::collections::HashMap; use std::collections::HashMap;
/// Argument builder for [get_bucket_tags()](crate::s3::client::Client::get_bucket_tags) API /// Argument builder for [get_bucket_tags()](crate::s3::client::Client::get_bucket_tags) API
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct GetBucketTags { pub struct GetBucketTags {
pub(crate) client: Option<Client>, client: Client,
pub(crate) extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
pub(crate) extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
pub(crate) region: Option<String>, region: Option<String>,
pub(crate) bucket: String, bucket: String,
pub(crate) tags: HashMap<String, String>, tags: HashMap<String, String>,
} }
impl GetBucketTags { impl GetBucketTags {
pub fn new(bucket: &str) -> Self { pub fn new(client: Client, bucket: String) -> Self {
Self { Self {
bucket: bucket.to_owned(), client,
bucket,
..Default::default() ..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers; self.extra_headers = extra_headers;
self self
@ -73,32 +70,13 @@ impl S3Api for GetBucketTags {
} }
impl ToS3Request for GetBucketTags { impl ToS3Request for GetBucketTags {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
let headers = self Ok(S3Request::new(self.client, Method::GET)
.extra_headers .region(self.region)
.as_ref() .bucket(Some(self.bucket))
.filter(|v| !v.is_empty()) .query_params(insert(self.extra_query_params, "tagging"))
.cloned() .headers(self.extra_headers.unwrap_or_default()))
.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)
} }
} }

View File

@ -17,7 +17,7 @@ use crate::s3::builders::BucketCommon;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::response::GetBucketVersioningResponse; use crate::s3::response::GetBucketVersioningResponse;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::{Multimap, check_bucket_name, merge}; use crate::s3::utils::{check_bucket_name, insert};
use http::Method; use http::Method;
/// Argument builder for [get_bucket_versioning()](crate::s3::client::Client::get_bucket_versioning) API /// Argument builder for [get_bucket_versioning()](crate::s3::client::Client::get_bucket_versioning) API
@ -31,27 +31,13 @@ impl S3Api for GetBucketVersioning {
} }
impl ToS3Request for GetBucketVersioning { impl ToS3Request for GetBucketVersioning {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; 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(); Ok(S3Request::new(self.client, Method::GET)
if let Some(v) = &self.extra_query_params { .region(self.region)
merge(&mut query_params, v); .bucket(Some(self.bucket))
} .query_params(insert(self.extra_query_params, "versioning"))
query_params.insert(String::from("versioning"), String::new()); .headers(self.extra_headers.unwrap_or_default()))
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)
} }
} }

View File

@ -15,19 +15,21 @@
use http::Method; use http::Method;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::utils::check_object_name;
use crate::s3::{ use crate::s3::{
client::Client, client::Client,
error::Error, error::Error,
response::GetObjectResponse, response::GetObjectResponse,
sse::{Sse, SseCustomerKey}, sse::{Sse, SseCustomerKey},
types::{S3Api, S3Request, ToS3Request}, types::{S3Api, S3Request, ToS3Request},
utils::{Multimap, UtcTime, check_bucket_name, merge, to_http_header_value}, utils::{UtcTime, check_bucket_name, to_http_header_value},
}; };
/// Argument builder for [list_objects()](Client::get_object) API. /// Argument builder for [list_objects()](Client::get_object) API.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct GetObject { pub struct GetObject {
client: Option<Client>, client: Client,
extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
@ -46,21 +48,16 @@ pub struct GetObject {
unmodified_since: Option<UtcTime>, unmodified_since: Option<UtcTime>,
} }
// builder interface
impl GetObject { impl GetObject {
pub fn new(bucket: &str, object: &str) -> Self { pub fn new(client: Client, bucket: String, object: String) -> Self {
Self { Self {
bucket: bucket.to_string(), client,
object: object.to_string(), bucket,
object,
..Default::default() ..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers; self.extra_headers = extra_headers;
self self
@ -117,99 +114,69 @@ impl GetObject {
} }
} }
// internal helpers impl S3Api for GetObject {
impl GetObject { type S3Response = GetObjectResponse;
fn get_range_header_value(&self) -> Option<String> { }
let (offset, length) = match self.length {
impl ToS3Request for GetObject {
fn to_s3request(self) -> Result<S3Request, Error> {
{
check_bucket_name(&self.bucket, true)?;
check_object_name(&self.object)?;
if self.ssec.is_some() && !self.client.is_secure() {
return Err(Error::SseTlsRequired(None));
}
}
let mut headers: Multimap = self.extra_headers.unwrap_or_default();
{
{
let (offset, length): (Option<u64>, Option<u64>) = match self.length {
Some(_) => (Some(self.offset.unwrap_or(0_u64)), self.length), Some(_) => (Some(self.offset.unwrap_or(0_u64)), self.length),
None => (self.offset, None), None => (self.offset, None),
}; };
if let Some(o) = offset { if let Some(o) = offset {
let mut range = String::new(); let mut range: String = String::new();
range.push_str("bytes="); range.push_str("bytes=");
range.push_str(&o.to_string()); range.push_str(&o.to_string());
range.push('-'); range.push('-');
if let Some(l) = length { if let Some(l) = length {
range.push_str(&(o + l - 1).to_string()); range.push_str(&(o + l - 1).to_string());
} }
Some(range) headers.add("Range", range);
} else {
None
} }
} }
fn get_headers(&self) -> Multimap { if let Some(v) = self.match_etag {
let mut headers = Multimap::new(); headers.add("if-match", v);
if let Some(val) = self.get_range_header_value() {
headers.insert(String::from("Range"), val);
} }
if let Some(v) = &self.match_etag { if let Some(v) = self.not_match_etag {
headers.insert(String::from("if-match"), v.to_string()); headers.add("if-none-match", v);
}
if let Some(v) = &self.not_match_etag {
headers.insert(String::from("if-none-match"), v.to_string());
} }
if let Some(v) = self.modified_since { if let Some(v) = self.modified_since {
headers.insert(String::from("if-modified-since"), to_http_header_value(v)); headers.add("if-modified-since", to_http_header_value(v));
} }
if let Some(v) = self.unmodified_since { if let Some(v) = self.unmodified_since {
headers.insert(String::from("if-unmodified-since"), to_http_header_value(v)); headers.add("if-unmodified-since", to_http_header_value(v));
} }
if let Some(v) = &self.ssec { if let Some(v) = &self.ssec {
merge(&mut headers, &v.headers()); headers.add_multimap(v.headers());
}
} }
headers let mut query_params: Multimap = self.extra_query_params.unwrap_or_default();
} query_params.add_version(self.version_id);
}
impl ToS3Request for GetObject { Ok(S3Request::new(self.client, Method::GET)
fn to_s3request(&self) -> Result<S3Request, Error> { .region(self.region)
check_bucket_name(&self.bucket, true)?; .bucket(Some(self.bucket))
.object(Some(self.object))
if self.object.is_empty() {
return Err(Error::InvalidObjectName(String::from(
"object name cannot be empty",
)));
}
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?;
if self.ssec.is_some() && !client.is_secure() {
return Err(Error::SseTlsRequired(None));
}
let mut headers = Multimap::new();
if let Some(v) = &self.extra_headers {
merge(&mut headers, v);
}
merge(&mut headers, &self.get_headers());
let mut query_params = Multimap::new();
if let Some(v) = &self.extra_query_params {
merge(&mut query_params, v);
}
if let Some(v) = &self.version_id {
query_params.insert(String::from("versionId"), v.to_string());
}
let req = S3Request::new(client, Method::GET)
.region(self.region.as_deref())
.bucket(Some(&self.bucket))
.object(Some(&self.object))
.query_params(query_params) .query_params(query_params)
.headers(headers); .headers(headers))
Ok(req)
} }
} }
impl S3Api for GetObject {
type S3Response = GetObjectResponse;
}

View File

@ -13,12 +13,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::s3::Client;
use crate::s3::builders::BucketCommon; use crate::s3::builders::BucketCommon;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::response::GetObjectLockConfigResponse; use crate::s3::response::GetObjectLockConfigResponse;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::check_bucket_name; use crate::s3::utils::{check_bucket_name, insert};
use http::Method; use http::Method;
/// Argument builder for [get_object_lock_config()](Client::get_object_lock_config) API /// Argument builder for [get_object_lock_config()](Client::get_object_lock_config) API
@ -32,32 +31,13 @@ impl S3Api for GetObjectLockConfig {
} }
impl ToS3Request for GetObjectLockConfig { impl ToS3Request for GetObjectLockConfig {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
let headers = self Ok(S3Request::new(self.client, Method::GET)
.extra_headers .region(self.region)
.as_ref() .bucket(Some(self.bucket))
.filter(|v| !v.is_empty()) .query_params(insert(self.extra_query_params, "object-lock"))
.cloned() .headers(self.extra_headers.unwrap_or_default()))
.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("object-lock"), 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)
} }
} }

View File

@ -15,36 +15,35 @@
use crate::s3::Client; use crate::s3::Client;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::GetObjectRetentionResponse; use crate::s3::response::GetObjectRetentionResponse;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::{Multimap, check_bucket_name}; use crate::s3::utils::{check_bucket_name, check_object_name, insert};
use http::Method; use http::Method;
/// Argument builder for [get_object_retention()](Client::get_object_retention) API /// Argument builder for [get_object_retention()](Client::get_object_retention) API
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct GetObjectRetention { pub struct GetObjectRetention {
pub client: Option<Client>, client: Client,
pub extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
pub extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
pub region: Option<String>, region: Option<String>,
pub bucket: String, bucket: String,
pub object: String, object: String,
pub version_id: Option<String>, version_id: Option<String>,
} }
impl GetObjectRetention { impl GetObjectRetention {
pub fn new(bucket: &str) -> Self { pub fn new(client: Client, bucket: String, object: String) -> Self {
Self { Self {
bucket: bucket.to_owned(), client,
bucket,
object,
..Default::default() ..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers; self.extra_headers = extra_headers;
@ -61,11 +60,6 @@ impl GetObjectRetention {
self self
} }
pub fn object(mut self, object: String) -> Self {
self.object = object;
self
}
pub fn version_id(mut self, version_id: Option<String>) -> Self { pub fn version_id(mut self, version_id: Option<String>) -> Self {
self.version_id = version_id; self.version_id = version_id;
self self
@ -77,36 +71,18 @@ impl S3Api for GetObjectRetention {
} }
impl ToS3Request for GetObjectRetention { impl ToS3Request for GetObjectRetention {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
check_object_name(&self.object)?;
let headers = self let mut query_params: Multimap = insert(self.extra_query_params, "retention");
.extra_headers query_params.add_version(self.version_id);
.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();
if let Some(v) = &self.version_id { Ok(S3Request::new(self.client, Method::GET)
query_params.insert(String::from("versionId"), v.to_string()); .region(self.region)
} .bucket(Some(self.bucket))
query_params.insert(String::from("retention"), 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) .query_params(query_params)
.headers(headers) .object(Some(self.object))
.object(Some(&self.object)); .headers(self.extra_headers.unwrap_or_default()))
Ok(req)
} }
} }

View File

@ -15,36 +15,35 @@
use crate::s3::Client; use crate::s3::Client;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::GetObjectTagsResponse; use crate::s3::response::GetObjectTagsResponse;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::{Multimap, check_bucket_name}; use crate::s3::utils::{check_bucket_name, check_object_name, insert};
use http::Method; use http::Method;
/// Argument builder for [get_object_tags()](Client::get_object_tags) API /// Argument builder for [get_object_tags()](Client::get_object_tags) API
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct GetObjectTags { pub struct GetObjectTags {
pub client: Option<Client>, client: Client,
pub extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
pub extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
pub region: Option<String>, region: Option<String>,
pub bucket: String, bucket: String,
pub object: String, object: String,
pub version_id: Option<String>, version_id: Option<String>,
} }
impl GetObjectTags { impl GetObjectTags {
pub fn new(bucket: &str) -> Self { pub fn new(client: Client, bucket: String, object: String) -> Self {
Self { Self {
bucket: bucket.to_owned(), client,
bucket,
object,
..Default::default() ..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers; self.extra_headers = extra_headers;
@ -61,11 +60,6 @@ impl GetObjectTags {
self self
} }
pub fn object(mut self, object: String) -> Self {
self.object = object;
self
}
pub fn version_id(mut self, version_id: Option<String>) -> Self { pub fn version_id(mut self, version_id: Option<String>) -> Self {
self.version_id = version_id; self.version_id = version_id;
self self
@ -77,36 +71,18 @@ impl S3Api for GetObjectTags {
} }
impl ToS3Request for GetObjectTags { impl ToS3Request for GetObjectTags {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
check_object_name(&self.object)?;
let headers = self let mut query_params: Multimap = insert(self.extra_query_params, "tagging");
.extra_headers query_params.add_version(self.version_id);
.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();
if let Some(v) = &self.version_id { Ok(S3Request::new(self.client, Method::GET)
query_params.insert(String::from("versionId"), v.to_string()); .region(self.region)
} .bucket(Some(self.bucket))
query_params.insert("tagging".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) .query_params(query_params)
.object(Some(&self.object)) .object(Some(self.object))
.headers(headers); .headers(self.extra_headers.unwrap_or_default()))
Ok(req)
} }
} }

View File

@ -0,0 +1,106 @@
// 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::client::DEFAULT_EXPIRY_SECONDS;
use crate::s3::creds::Credentials;
use crate::s3::error::Error;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::GetPresignedObjectUrlResponse;
use crate::s3::signer::presign_v4;
use crate::s3::utils::{UtcTime, check_bucket_name, check_object_name, utc_now};
use http::Method;
/// Argument for [get_presigned_object_url()](crate::s3::client::Client::get_presigned_object_url) API
#[derive(Clone, Debug, Default)]
pub struct GetPresignedObjectUrl {
client: Client,
extra_query_params: Option<Multimap>,
region: Option<String>,
bucket: String,
object: String,
version_id: Option<String>,
method: Method,
expiry_seconds: Option<u32>,
request_time: Option<UtcTime>,
}
impl GetPresignedObjectUrl {
pub fn new(client: Client, bucket: String, object: String, method: Method) -> Self {
Self {
client,
bucket,
object,
method,
expiry_seconds: Some(DEFAULT_EXPIRY_SECONDS),
..Default::default()
}
}
pub async fn send(self) -> Result<GetPresignedObjectUrlResponse, Error> {
// NOTE: this send function is async to be comparable with other functions...
check_bucket_name(&self.bucket, true)?;
check_object_name(&self.object)?;
let region: String = self.client.get_region_cached(&self.bucket, &self.region)?;
let mut query_params: Multimap = self.extra_query_params.unwrap_or_default();
query_params.add_version(self.version_id.clone());
let mut url = self.client.shared.base_url.build_url(
&self.method,
&region,
&query_params,
Some(&self.bucket),
Some(&self.object),
)?;
if let Some(p) = &self.client.shared.provider {
let creds: Credentials = p.fetch();
if let Some(t) = creds.session_token {
query_params.add("X-Amz-Security-Token", t);
}
let date = match self.request_time {
Some(v) => v,
_ => utc_now(),
};
presign_v4(
&self.method,
&url.host_header_value(),
&url.path,
&region,
&mut query_params,
&creds.access_key,
&creds.secret_key,
date,
self.expiry_seconds.unwrap_or(DEFAULT_EXPIRY_SECONDS),
);
url.query = query_params;
}
Ok(GetPresignedObjectUrlResponse {
region,
bucket: self.bucket,
object: self.object,
version_id: self.version_id,
url: url.to_string(),
})
}
}

View File

@ -0,0 +1,347 @@
// 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::creds::Credentials;
use crate::s3::error::Error;
use crate::s3::signer::post_presign_v4;
use crate::s3::utils::{
UtcTime, b64encode, check_bucket_name, to_amz_date, to_iso8601utc, to_signer_date, utc_now,
};
use serde_json::{Value, json};
use std::collections::HashMap;
/// Argument for [get_presigned_object_url()](crate::s3::client::Client::get_presigned_object_url) API
pub struct GetPresignedPolicyFormData {
client: Client,
policy: PostPolicy,
}
impl GetPresignedPolicyFormData {
pub fn new(client: Client, policy: PostPolicy) -> Self {
Self { client, policy }
}
pub async fn send(self) -> Result<HashMap<String, String>, Error> {
// NOTE: this send function is async to be comparable with other functions...
let region: String = self
.client
.get_region_cached(&self.policy.bucket, &self.policy.region)?;
let creds: Credentials = self.client.shared.provider.as_ref().unwrap().fetch();
self.policy.form_data(
creds.access_key,
creds.secret_key,
creds.session_token,
region,
)
}
}
/// Post policy information for presigned post policy form-data
///
/// Condition elements and respective condition for Post policy is available <a
/// href="https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html#sigv4-PolicyConditions">here</a>.
pub struct PostPolicy {
pub region: Option<String>,
pub bucket: String,
expiration: UtcTime,
eq_conditions: HashMap<String, String>,
starts_with_conditions: HashMap<String, String>,
lower_limit: Option<usize>,
upper_limit: Option<usize>,
}
impl PostPolicy {
const EQ: &'static str = "eq";
const STARTS_WITH: &'static str = "starts-with";
const ALGORITHM: &'static str = "AWS4-HMAC-SHA256";
/// Returns post policy with given bucket name and expiration
///
/// # Examples
///
/// ```
/// use minio::s3::utils::*;
/// use chrono::Duration;
/// use minio::s3::builders::PostPolicy;
/// let expiration = utc_now() + Duration::days(7);
/// let policy = PostPolicy::new("bucket-name", expiration).unwrap();
/// ```
pub fn new(bucket_name: &str, expiration: UtcTime) -> Result<PostPolicy, Error> {
check_bucket_name(bucket_name, true)?;
Ok(PostPolicy {
region: None,
bucket: bucket_name.to_owned(),
expiration,
eq_conditions: HashMap::new(),
starts_with_conditions: HashMap::new(),
lower_limit: None,
upper_limit: None,
})
}
fn trim_dollar(value: &str) -> String {
let mut s = value.to_string();
if s.starts_with('$') {
s.remove(0);
}
s
}
fn is_reserved_element(element: &str) -> bool {
element.eq_ignore_ascii_case("bucket")
|| element.eq_ignore_ascii_case("x-amz-algorithm")
|| element.eq_ignore_ascii_case("x-amz-credential")
|| element.eq_ignore_ascii_case("x-amz-date")
|| element.eq_ignore_ascii_case("policy")
|| element.eq_ignore_ascii_case("x-amz-signature")
}
fn get_credential_string(access_key: &String, date: &UtcTime, region: &String) -> String {
format!(
"{}/{}/{}/s3/aws4_request",
access_key,
to_signer_date(*date),
region
)
}
/// Adds equals condition for given element and value
/// # Examples
///
/// ```
/// use minio::s3::utils::*;
/// use chrono::Duration;
/// use minio::s3::builders::PostPolicy;
/// let expiration = utc_now() + Duration::days(7);
/// let mut policy = PostPolicy::new("my-bucket", expiration).unwrap();
///
/// // Add condition that 'key' (object name) equals to 'bucket-name'
/// policy.add_equals_condition("key", "bucket-name").unwrap();
/// ```
pub fn add_equals_condition(&mut self, element: &str, value: &str) -> Result<(), Error> {
if element.is_empty() {
return Err(Error::PostPolicyError(
"condition element cannot be empty".to_string(),
));
}
let v = PostPolicy::trim_dollar(element);
if v.eq_ignore_ascii_case("success_action_redirect")
|| v.eq_ignore_ascii_case("redirect")
|| v.eq_ignore_ascii_case("content-length-range")
{
return Err(Error::PostPolicyError(format!(
"{} is unsupported for equals condition",
element
)));
}
if PostPolicy::is_reserved_element(v.as_str()) {
return Err(Error::PostPolicyError(format!("{} cannot set", element)));
}
self.eq_conditions.insert(v, value.to_string());
Ok(())
}
/// Removes equals condition for given element
/// # Examples
///
/// ```
/// use minio::s3::utils::*;
/// use chrono::Duration;
/// use minio::s3::builders::PostPolicy;
/// let expiration = utc_now() + Duration::days(7);
/// let mut policy = PostPolicy::new("bucket-name", expiration).unwrap();
/// policy.add_equals_condition("key", "bucket-name");
///
/// policy.remove_equals_condition("key");
/// ```
pub fn remove_equals_condition(&mut self, element: &str) {
self.eq_conditions.remove(element);
}
/// Adds starts-with condition for given element and value
/// # Examples
///
/// ```
/// use minio::s3::utils::*;
/// use chrono::Duration;
/// use minio::s3::builders::PostPolicy;
/// let expiration = utc_now() + Duration::days(7);
/// let mut policy = PostPolicy::new("bucket-name", expiration).unwrap();
///
/// // Add condition that 'Content-Type' starts with 'image/'
/// policy.add_starts_with_condition("Content-Type", "image/").unwrap();
/// ```
pub fn add_starts_with_condition(&mut self, element: &str, value: &str) -> Result<(), Error> {
if element.is_empty() {
return Err(Error::PostPolicyError(
"condition element cannot be empty".to_string(),
));
}
let v = PostPolicy::trim_dollar(element);
if v.eq_ignore_ascii_case("success_action_status")
|| v.eq_ignore_ascii_case("content-length-range")
|| (v.starts_with("x-amz-") && v.starts_with("x-amz-meta-"))
{
return Err(Error::PostPolicyError(format!(
"{} is unsupported for starts-with condition",
element
)));
}
if PostPolicy::is_reserved_element(v.as_str()) {
return Err(Error::PostPolicyError(format!("{} cannot set", element)));
}
self.starts_with_conditions.insert(v, value.to_string());
Ok(())
}
/// Removes starts-with condition for given element
/// # Examples
///
/// ```
/// use minio::s3::utils::*;
/// use chrono::Duration;
/// use minio::s3::builders::PostPolicy;
/// let expiration = utc_now() + Duration::days(7);
/// let mut policy = PostPolicy::new("bucket-name", expiration).unwrap();
/// policy.add_starts_with_condition("Content-Type", "image/").unwrap();
///
/// policy.remove_starts_with_condition("Content-Type");
/// ```
pub fn remove_starts_with_condition(&mut self, element: &str) {
self.starts_with_conditions.remove(element);
}
/// Adds content-length range condition with given lower and upper limits
/// # Examples
///
/// ```
/// use minio::s3::utils::*;
/// use chrono::Duration;
/// use minio::s3::builders::PostPolicy;
///
/// let expiration = utc_now() + Duration::days(7);
/// let mut policy = PostPolicy::new("my-bucket", expiration).unwrap();
///
/// // Add condition that 'content-length-range' is between 64kiB to 10MiB
/// policy.add_content_length_range_condition(64 * 1024, 10 * 1024 * 1024).unwrap();
/// ```
pub fn add_content_length_range_condition(
&mut self,
lower_limit: usize,
upper_limit: usize,
) -> Result<(), Error> {
if lower_limit > upper_limit {
return Err(Error::PostPolicyError(
"lower limit cannot be greater than upper limit".to_string(),
));
}
self.lower_limit = Some(lower_limit);
self.upper_limit = Some(upper_limit);
Ok(())
}
/// Removes content-length range condition
pub fn remove_content_length_range_condition(&mut self) {
self.lower_limit = None;
self.upper_limit = None;
}
/// Generates form data for given access/secret keys, optional session token and region.
/// The returned map contains `x-amz-algorithm`, `x-amz-credential`, `x-amz-security-token`, `x-amz-date`, `policy` and `x-amz-signature` keys and values.
pub fn form_data(
&self,
access_key: String,
secret_key: String,
session_token: Option<String>,
region: String,
) -> Result<HashMap<String, String>, Error> {
if region.is_empty() {
return Err(Error::PostPolicyError("region cannot be empty".to_string()));
}
if !self.eq_conditions.contains_key("key")
&& !self.starts_with_conditions.contains_key("key")
{
return Err(Error::PostPolicyError(
"key condition must be set".to_string(),
));
}
let mut conditions: Vec<Value> = Vec::new();
conditions.push(json!([PostPolicy::EQ, "$bucket", self.bucket]));
for (key, value) in &self.eq_conditions {
conditions.push(json!([PostPolicy::EQ, String::from("$") + key, value]));
}
for (key, value) in &self.starts_with_conditions {
conditions.push(json!([
PostPolicy::STARTS_WITH,
String::from("$") + key,
value
]));
}
if self.lower_limit.is_some() && self.upper_limit.is_some() {
conditions.push(json!([
"content-length-range",
self.lower_limit.unwrap(),
self.upper_limit.unwrap()
]));
}
let date = utc_now();
let credential = PostPolicy::get_credential_string(&access_key, &date, &region);
let amz_date = to_amz_date(date);
conditions.push(json!([
PostPolicy::EQ,
"$x-amz-algorithm",
PostPolicy::ALGORITHM
]));
conditions.push(json!([PostPolicy::EQ, "$x-amz-credential", credential]));
if let Some(v) = &session_token {
conditions.push(json!([PostPolicy::EQ, "$x-amz-security-token", v]));
}
conditions.push(json!([PostPolicy::EQ, "$x-amz-date", amz_date]));
let policy = json!({
"expiration": to_iso8601utc(self.expiration),
"conditions": conditions,
});
let encoded_policy = b64encode(policy.to_string());
let signature = post_presign_v4(&encoded_policy, &secret_key, date, &region);
let mut data: HashMap<String, String> = HashMap::new();
data.insert("x-amz-algorithm".into(), PostPolicy::ALGORITHM.to_string());
data.insert("x-amz-credential".into(), credential);
data.insert("x-amz-date".into(), amz_date);
data.insert("policy".into(), encoded_policy);
data.insert("x-amz-signature".into(), signature);
if let Some(v) = session_token {
data.insert("x-amz-security-token".into(), v);
}
Ok(data)
}
}

View File

@ -0,0 +1,72 @@
// 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::client::DEFAULT_REGION;
use crate::s3::error::Error;
use crate::s3::multimap::Multimap;
use crate::s3::response::GetRegionResponse;
use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::{check_bucket_name, insert};
use http::Method;
/// Argument builder for [get_region()](Client::get_region) API
#[derive(Clone, Debug, Default)]
pub struct GetRegion {
client: Client,
extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>,
bucket: String,
}
impl GetRegion {
pub fn new(client: Client, bucket: String) -> Self {
Self {
client,
bucket,
..Default::default()
}
}
pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers;
self
}
pub fn extra_query_params(mut self, extra_query_params: Option<Multimap>) -> Self {
self.extra_query_params = extra_query_params;
self
}
}
#[derive(Default, Debug)]
pub struct GetRegionPhantomData;
impl S3Api for GetRegion {
type S3Response = GetRegionResponse;
}
impl ToS3Request for GetRegion {
fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?;
Ok(S3Request::new(self.client, Method::GET)
.region(Some(DEFAULT_REGION.to_string()))
.bucket(Some(self.bucket))
.query_params(insert(self.extra_query_params, "location"))
.headers(self.extra_headers.unwrap_or_default()))
}
}

View File

@ -15,38 +15,36 @@
use crate::s3::Client; use crate::s3::Client;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::IsObjectLegalHoldEnabledResponse; use crate::s3::response::IsObjectLegalHoldEnabledResponse;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::{Multimap, check_bucket_name}; use crate::s3::utils::{check_bucket_name, check_object_name, insert};
use http::Method; use http::Method;
/// Argument builder for [is_object_legal_hold_enabled()](Client::is_object_legal_hold_enabled) API /// Argument builder for [is_object_legal_hold_enabled()](Client::is_object_legal_hold_enabled) API
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct IsObjectLegalHoldEnabled { pub struct IsObjectLegalHoldEnabled {
pub(crate) client: Option<Client>, client: Client,
pub(crate) extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
pub(crate) extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
pub(crate) region: Option<String>, region: Option<String>,
pub(crate) bucket: String, bucket: String,
pub(crate) object: String, object: String,
pub(crate) version_id: Option<String>, version_id: Option<String>,
} }
impl IsObjectLegalHoldEnabled { impl IsObjectLegalHoldEnabled {
pub fn new(bucket: &str) -> Self { pub fn new(client: Client, bucket: String, object: String) -> Self {
Self { Self {
bucket: bucket.to_owned(), client,
bucket,
object,
..Default::default() ..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers; self.extra_headers = extra_headers;
self self
@ -57,11 +55,6 @@ impl IsObjectLegalHoldEnabled {
self self
} }
pub fn object(mut self, object: String) -> Self {
self.object = object;
self
}
pub fn version_id(mut self, version_id: Option<String>) -> Self { pub fn version_id(mut self, version_id: Option<String>) -> Self {
self.version_id = version_id; self.version_id = version_id;
self self
@ -73,36 +66,18 @@ impl S3Api for IsObjectLegalHoldEnabled {
} }
impl ToS3Request for IsObjectLegalHoldEnabled { impl ToS3Request for IsObjectLegalHoldEnabled {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
check_object_name(&self.object)?;
let headers = self let mut query_params: Multimap = insert(self.extra_query_params, "legal-hold");
.extra_headers query_params.add_version(self.version_id);
.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();
if let Some(v) = &self.version_id { Ok(S3Request::new(self.client, Method::GET)
query_params.insert(String::from("versionId"), v.to_string()); .region(self.region)
} .bucket(Some(self.bucket))
query_params.insert(String::from("legal-hold"), 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) .query_params(query_params)
.headers(headers) .headers(self.extra_headers.unwrap_or_default())
.object(Some(&self.object)); .object(Some(self.object)))
Ok(req)
} }
} }

View File

@ -15,32 +15,29 @@
use http::Method; use http::Method;
use crate::s3::multimap::Multimap;
use crate::s3::response::ListBucketsResponse; use crate::s3::response::ListBucketsResponse;
use crate::s3::{ use crate::s3::{
Client, Client,
error::Error, error::Error,
types::{S3Api, S3Request, ToS3Request}, types::{S3Api, S3Request, ToS3Request},
utils::Multimap,
}; };
/// Argument builder for [list_buckets()](Client::list_buckets) API. /// Argument builder for [list_buckets()](Client::list_buckets) API.
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct ListBuckets { pub struct ListBuckets {
client: Option<Client>, client: Client,
extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
} }
// builder interface
impl ListBuckets { impl ListBuckets {
pub fn new() -> Self { pub fn new(client: Client) -> Self {
Default::default() Self {
client,
..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
@ -54,27 +51,14 @@ impl ListBuckets {
} }
} }
impl ToS3Request for ListBuckets {
fn to_s3request(&self) -> Result<S3Request, Error> {
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 { impl S3Api for ListBuckets {
type S3Response = ListBucketsResponse; type S3Response = ListBucketsResponse;
} }
impl ToS3Request for ListBuckets {
fn to_s3request(self) -> Result<S3Request, Error> {
Ok(S3Request::new(self.client, Method::GET)
.query_params(self.extra_query_params.unwrap_or_default())
.headers(self.extra_headers.unwrap_or_default()))
}
}

View File

@ -16,6 +16,8 @@ use async_trait::async_trait;
use futures_util::{Stream, StreamExt, stream as futures_stream}; use futures_util::{Stream, StreamExt, stream as futures_stream};
use http::Method; use http::Method;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::utils::insert;
use crate::s3::{ use crate::s3::{
client::Client, client::Client,
error::Error, error::Error,
@ -24,34 +26,41 @@ use crate::s3::{
ListObjectVersionsResponse, ListObjectsV1Response, ListObjectsV2Response, ListObjectVersionsResponse, ListObjectsV1Response, ListObjectsV2Response,
}, },
types::{S3Api, S3Request, ToS3Request, ToStream}, types::{S3Api, S3Request, ToS3Request, ToStream},
utils::{Multimap, check_bucket_name, merge}, utils::check_bucket_name,
}; };
fn add_common_list_objects_query_params( fn add_common_list_objects_query_params(
query_params: &mut Multimap, query_params: &mut Multimap,
delimiter: Option<&str>, delimiter: Option<String>,
disable_url_encoding: bool, disable_url_encoding: bool,
max_keys: Option<u16>, max_keys: Option<u16>,
prefix: Option<&str>, prefix: Option<String>,
) { ) {
query_params.insert( query_params.add("delimiter", delimiter.unwrap_or("".into()));
String::from("delimiter"), query_params.add("max-keys", max_keys.unwrap_or(1000).to_string());
delimiter.unwrap_or("").to_string(), query_params.add("prefix", prefix.unwrap_or("".into()));
);
query_params.insert(
String::from("max-keys"),
max_keys.unwrap_or(1000).to_string(),
);
query_params.insert(String::from("prefix"), prefix.unwrap_or("").to_string());
if !disable_url_encoding { if !disable_url_encoding {
query_params.insert(String::from("encoding-type"), String::from("url")); query_params.add("encoding-type", "url");
} }
} }
/// Helper function delimiter based on recursive flag when delimiter is not provided.
fn delim_helper(delim: Option<String>, recursive: bool) -> Option<String> {
if delim.is_some() {
return delim;
}
match recursive {
true => None,
false => Some(String::from("/")),
}
}
// region: list-objects-v1
/// Argument for ListObjectsV1 S3 API. /// Argument for ListObjectsV1 S3 API.
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
struct ListObjectsV1 { struct ListObjectsV1 {
client: Option<Client>, client: Client,
extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
@ -70,76 +79,61 @@ impl ToStream for ListObjectsV1 {
async fn to_stream(self) -> Box<dyn Stream<Item = Result<Self::Item, Error>> + Unpin + Send> { async fn to_stream(self) -> Box<dyn Stream<Item = Result<Self::Item, Error>> + Unpin + Send> {
Box::new(Box::pin(futures_stream::unfold( Box::new(Box::pin(futures_stream::unfold(
(self.clone(), false), (self, false),
move |(mut args, mut is_done)| async move { move |(args, mut is_done)| async move {
// Stop the stream if no more data is available
if is_done { if is_done {
return None; return None;
} }
let resp = args.send().await; // Prepare a clone of `args` for the next iteration
match resp { let mut args_for_next_request: ListObjectsV1 = args.clone();
// Handle the result of the API call
match args.send().await {
Ok(resp) => { Ok(resp) => {
args.marker.clone_from(&resp.next_marker); // Update the marker for the next request
args_for_next_request.marker.clone_from(&resp.next_marker);
// Determine if there are more results to fetch
is_done = !resp.is_truncated; is_done = !resp.is_truncated;
Some((Ok(resp), (args, is_done)))
// Return the response and prepare for the next iteration
Some((Ok(resp), (args_for_next_request, is_done)))
} }
Err(e) => Some((Err(e), (args, true))), Err(e) => Some((Err(e), (args_for_next_request, true))),
} }
}, },
))) )))
} }
} }
impl ToS3Request for ListObjectsV1 {
fn to_s3request(&self) -> Result<S3Request<'_>, Error> {
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);
}
add_common_list_objects_query_params(
&mut query_params,
self.delimiter.as_deref(),
self.disable_url_encoding,
self.max_keys,
self.prefix.as_deref(),
);
if let Some(v) = &self.marker {
query_params.insert(String::from("marker"), v.to_string());
}
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)
}
}
impl S3Api for ListObjectsV1 { impl S3Api for ListObjectsV1 {
type S3Response = ListObjectsV1Response; type S3Response = ListObjectsV1Response;
} }
// Helper function delimiter based on recursive flag when delimiter is not impl ToS3Request for ListObjectsV1 {
// provided. fn to_s3request(self) -> Result<S3Request, Error> {
fn delim_helper(delim: Option<String>, recursive: bool) -> Option<String> { check_bucket_name(&self.bucket, true)?;
if delim.is_some() {
return delim; let mut query_params: Multimap = self.extra_query_params.unwrap_or_default();
{
add_common_list_objects_query_params(
&mut query_params,
self.delimiter,
self.disable_url_encoding,
self.max_keys,
self.prefix,
);
if let Some(v) = self.marker {
query_params.add("marker", v);
} }
match recursive { }
true => None,
false => Some(String::from("/")), Ok(S3Request::new(self.client, Method::GET)
.region(self.region)
.bucket(Some(self.bucket))
.query_params(query_params)
.headers(self.extra_headers.unwrap_or_default()))
} }
} }
@ -159,11 +153,14 @@ impl From<ListObjects> for ListObjectsV1 {
} }
} }
} }
// endregion: list-objects-v1
// region: list-objects-v2
/// Argument for ListObjectsV2 S3 API. /// Argument for ListObjectsV2 S3 API.
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
struct ListObjectsV2 { struct ListObjectsV2 {
client: Option<Client>, client: Client,
extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
@ -177,6 +174,7 @@ struct ListObjectsV2 {
continuation_token: Option<String>, continuation_token: Option<String>,
fetch_owner: bool, fetch_owner: bool,
include_user_metadata: bool, include_user_metadata: bool,
unsorted: bool,
} }
#[async_trait] #[async_trait]
@ -185,20 +183,28 @@ impl ToStream for ListObjectsV2 {
async fn to_stream(self) -> Box<dyn Stream<Item = Result<Self::Item, Error>> + Unpin + Send> { async fn to_stream(self) -> Box<dyn Stream<Item = Result<Self::Item, Error>> + Unpin + Send> {
Box::new(Box::pin(futures_stream::unfold( Box::new(Box::pin(futures_stream::unfold(
(self.clone(), false), (self, false),
move |(mut args, mut is_done)| async move { move |(args, mut is_done)| async move {
// Stop the stream if no more data is available
if is_done { if is_done {
return None; return None;
} }
let resp = args.send().await; // Prepare a clone of `args` for the next iteration
match resp { let mut args_for_next_request = args.clone();
match args.send().await {
Ok(resp) => { Ok(resp) => {
args.continuation_token // Update the continuation_token for the next request
args_for_next_request
.continuation_token
.clone_from(&resp.next_continuation_token); .clone_from(&resp.next_continuation_token);
// Determine if there are more results to fetch
is_done = !resp.is_truncated; is_done = !resp.is_truncated;
Some((Ok(resp), (args, is_done)))
// Return the response and prepare for the next iteration
Some((Ok(resp), (args_for_next_request, is_done)))
} }
Err(e) => Some((Err(e), (args, true))), Err(e) => Some((Err(e), (args_for_next_request, true))),
} }
}, },
))) )))
@ -210,48 +216,41 @@ impl S3Api for ListObjectsV2 {
} }
impl ToS3Request for ListObjectsV2 { impl ToS3Request for ListObjectsV2 {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
let mut headers = Multimap::new(); let mut query_params: Multimap = self.extra_query_params.unwrap_or_default();
if let Some(v) = &self.extra_headers { {
merge(&mut headers, v); query_params.add("list-type", "2");
}
let mut query_params = Multimap::new();
if let Some(v) = &self.extra_query_params {
merge(&mut query_params, v);
}
query_params.insert(String::from("list-type"), String::from("2"));
add_common_list_objects_query_params( add_common_list_objects_query_params(
&mut query_params, &mut query_params,
self.delimiter.as_deref(), self.delimiter,
self.disable_url_encoding, self.disable_url_encoding,
self.max_keys, self.max_keys,
self.prefix.as_deref(), self.prefix,
); );
if let Some(v) = &self.continuation_token { if let Some(v) = self.continuation_token {
query_params.insert(String::from("continuation-token"), v.to_string()); query_params.add("continuation-token", v);
} }
if self.fetch_owner { if self.fetch_owner {
query_params.insert(String::from("fetch-owner"), String::from("true")); query_params.add("fetch-owner", "true");
} }
if let Some(v) = &self.start_after { if let Some(v) = self.start_after {
query_params.insert(String::from("start-after"), v.to_string()); query_params.add("start-after", v);
} }
if self.include_user_metadata { if self.include_user_metadata {
query_params.insert(String::from("metadata"), String::from("true")); query_params.add("metadata", "true");
}
if self.unsorted {
query_params.add("unsorted", "true");
}
} }
let req = S3Request::new( Ok(S3Request::new(self.client, Method::GET)
self.client.as_ref().ok_or(Error::NoClientProvided)?, .region(self.region)
Method::GET, .bucket(Some(self.bucket))
)
.region(self.region.as_deref())
.bucket(Some(&self.bucket))
.query_params(query_params) .query_params(query_params)
.headers(headers); .headers(self.extra_headers.unwrap_or_default()))
Ok(req)
} }
} }
@ -271,14 +270,18 @@ impl From<ListObjects> for ListObjectsV2 {
continuation_token: value.continuation_token, continuation_token: value.continuation_token,
fetch_owner: value.fetch_owner, fetch_owner: value.fetch_owner,
include_user_metadata: value.include_user_metadata, include_user_metadata: value.include_user_metadata,
unsorted: value.unsorted,
} }
} }
} }
// endregion: list-objects-v2
/// Argument for ListObjectVerions S3 API // region: list-object-versions
/// Argument for ListObjectVersions S3 API
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
struct ListObjectVersions { struct ListObjectVersions {
client: Option<Client>, client: Client,
extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
@ -291,6 +294,7 @@ struct ListObjectVersions {
key_marker: Option<String>, key_marker: Option<String>,
version_id_marker: Option<String>, version_id_marker: Option<String>,
include_user_metadata: bool, include_user_metadata: bool,
unsorted: bool,
} }
#[async_trait] #[async_trait]
@ -299,22 +303,32 @@ impl ToStream for ListObjectVersions {
async fn to_stream(self) -> Box<dyn Stream<Item = Result<Self::Item, Error>> + Unpin + Send> { async fn to_stream(self) -> Box<dyn Stream<Item = Result<Self::Item, Error>> + Unpin + Send> {
Box::new(Box::pin(futures_stream::unfold( Box::new(Box::pin(futures_stream::unfold(
(self.clone(), false), (self, false),
move |(mut args, mut is_done)| async move { move |(args, mut is_done)| async move {
// Stop the stream if no more data is available
if is_done { if is_done {
return None; return None;
} }
let resp = args.send().await; // Prepare a clone of `args` for the next iteration
match resp { let mut args_for_next_request = args.clone();
match args.send().await {
Ok(resp) => { Ok(resp) => {
args.key_marker.clone_from(&resp.next_key_marker); // Update the key_marker for the next request
args.version_id_marker args_for_next_request
.key_marker
.clone_from(&resp.next_key_marker);
// Update the version_id_marker for the next request
args_for_next_request
.version_id_marker
.clone_from(&resp.next_version_id_marker); .clone_from(&resp.next_version_id_marker);
// Determine if there are more results to fetch
is_done = !resp.is_truncated; is_done = !resp.is_truncated;
Some((Ok(resp), (args, is_done)))
// Return the response and prepare for the next iteration
Some((Ok(resp), (args_for_next_request, is_done)))
} }
Err(e) => Some((Err(e), (args, true))), Err(e) => Some((Err(e), (args_for_next_request, true))),
} }
}, },
))) )))
@ -326,51 +340,43 @@ impl S3Api for ListObjectVersions {
} }
impl ToS3Request for ListObjectVersions { impl ToS3Request for ListObjectVersions {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
let mut headers = Multimap::new(); let mut query_params: Multimap = insert(self.extra_query_params, "versions");
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("versions"), String::new());
add_common_list_objects_query_params( add_common_list_objects_query_params(
&mut query_params, &mut query_params,
self.delimiter.as_deref(), self.delimiter,
self.disable_url_encoding, self.disable_url_encoding,
self.max_keys, self.max_keys,
self.prefix.as_deref(), self.prefix,
); );
if let Some(v) = &self.key_marker { if let Some(v) = self.key_marker {
query_params.insert(String::from("key-marker"), v.to_string()); query_params.add("key-marker", v);
} }
if let Some(v) = &self.version_id_marker { if let Some(v) = self.version_id_marker {
query_params.insert(String::from("version-id-marker"), v.to_string()); query_params.add("version-id-marker", v);
} }
if self.include_user_metadata { if self.include_user_metadata {
query_params.insert(String::from("metadata"), String::from("true")); query_params.add("metadata", "true");
}
if self.unsorted {
query_params.add("unsorted", "true");
}
} }
let req = S3Request::new( Ok(S3Request::new(self.client, Method::GET)
self.client.as_ref().ok_or(Error::NoClientProvided)?, .region(self.region)
Method::GET, .bucket(Some(self.bucket))
)
.region(self.region.as_deref())
.bucket(Some(&self.bucket))
.query_params(query_params) .query_params(query_params)
.headers(headers); .headers(self.extra_headers.unwrap_or_default()))
Ok(req)
} }
} }
impl From<ListObjects> for ListObjectVersions { impl From<ListObjects> for ListObjectVersions {
fn from(value: ListObjects) -> Self { fn from(value: ListObjects) -> Self {
ListObjectVersions { Self {
client: value.client, client: value.client,
extra_headers: value.extra_headers, extra_headers: value.extra_headers,
extra_query_params: value.extra_query_params, extra_query_params: value.extra_query_params,
@ -383,10 +389,15 @@ impl From<ListObjects> for ListObjectVersions {
key_marker: value.key_marker, key_marker: value.key_marker,
version_id_marker: value.version_id_marker, version_id_marker: value.version_id_marker,
include_user_metadata: value.include_user_metadata, include_user_metadata: value.include_user_metadata,
unsorted: value.unsorted,
} }
} }
} }
// endregion: list-object-versions
// region: list-objects
/// Argument builder for /// Argument builder for
/// [list_objects()](crate::s3::client::Client::list_objects) API. /// [list_objects()](crate::s3::client::Client::list_objects) API.
/// ///
@ -395,7 +406,7 @@ impl From<ListObjects> for ListObjectVersions {
/// a stream of results. Pagination is automatically performed. /// a stream of results. Pagination is automatically performed.
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct ListObjects { pub struct ListObjects {
client: Option<Client>, client: Client,
// Parameters common to all ListObjects APIs. // Parameters common to all ListObjects APIs.
extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
@ -424,6 +435,7 @@ pub struct ListObjects {
recursive: bool, recursive: bool,
use_api_v1: bool, use_api_v1: bool,
include_versions: bool, include_versions: bool,
unsorted: bool,
} }
#[async_trait] #[async_trait]
@ -445,18 +457,13 @@ impl ToStream for ListObjects {
} }
impl ListObjects { impl ListObjects {
pub fn new(bucket: &str) -> Self { pub fn new(client: Client, bucket: String) -> Self {
Self { Self {
bucket: bucket.to_owned(), client,
bucket,
..Default::default() ..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers; self.extra_headers = extra_headers;
self self
@ -556,4 +563,11 @@ impl ListObjects {
self.include_versions = include_versions; self.include_versions = include_versions;
self self
} }
/// Set this to allow unsorted versions. Defaults to false
pub fn unsorted(mut self, unsorted: bool) -> Self {
self.unsorted = unsorted;
self
}
} }
// endregion: list-objects

View File

@ -17,12 +17,13 @@ use async_trait::async_trait;
use futures_util::Stream; use futures_util::Stream;
use http::Method; use http::Method;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::{ use crate::s3::{
client::Client, client::Client,
error::Error, error::Error,
response::ListenBucketNotificationResponse, response::ListenBucketNotificationResponse,
types::{NotificationRecords, S3Api, S3Request, ToS3Request}, types::{NotificationRecords, S3Api, S3Request, ToS3Request},
utils::{Multimap, check_bucket_name, merge}, utils::check_bucket_name,
}; };
/// Argument builder for /// Argument builder for
@ -30,7 +31,7 @@ use crate::s3::{
/// API. /// API.
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct ListenBucketNotification { pub struct ListenBucketNotification {
client: Option<Client>, client: Client,
extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
@ -41,72 +42,15 @@ pub struct ListenBucketNotification {
events: Option<Vec<String>>, events: Option<Vec<String>>,
} }
#[async_trait]
impl S3Api for ListenBucketNotification {
type S3Response = (
ListenBucketNotificationResponse,
Box<dyn Stream<Item = Result<NotificationRecords, Error>> + Unpin + Send>,
);
}
impl ToS3Request for ListenBucketNotification {
fn to_s3request(&self) -> Result<S3Request, Error> {
let client = self.client.as_ref().ok_or(Error::NoClientProvided)?;
if client.is_aws_host() {
return Err(Error::UnsupportedApi(String::from(
"ListenBucketNotification",
)));
}
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);
}
if let Some(v) = &self.prefix {
query_params.insert(String::from("prefix"), v.to_string());
}
if let Some(v) = &self.suffix {
query_params.insert(String::from("suffix"), v.to_string());
}
if let Some(v) = &self.events {
for e in v.iter() {
query_params.insert(String::from("events"), e.to_string());
}
} else {
query_params.insert(String::from("events"), String::from("s3:ObjectCreated:*"));
query_params.insert(String::from("events"), String::from("s3:ObjectRemoved:*"));
query_params.insert(String::from("events"), String::from("s3:ObjectAccessed:*"));
}
let req = S3Request::new(client, Method::GET)
.region(self.region.as_deref())
.bucket(Some(&self.bucket))
.query_params(query_params)
.headers(headers);
Ok(req)
}
}
impl ListenBucketNotification { impl ListenBucketNotification {
pub fn new(bucket_name: &str) -> ListenBucketNotification { pub fn new(client: Client, bucket: String) -> Self {
ListenBucketNotification { Self {
bucket: bucket_name.to_owned(), client,
bucket,
..Default::default() ..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers; self.extra_headers = extra_headers;
self self
@ -137,3 +81,47 @@ impl ListenBucketNotification {
self self
} }
} }
#[async_trait]
impl S3Api for ListenBucketNotification {
type S3Response = (
ListenBucketNotificationResponse,
Box<dyn Stream<Item = Result<NotificationRecords, Error>> + Unpin + Send>,
);
}
impl ToS3Request for ListenBucketNotification {
fn to_s3request(self) -> Result<S3Request, Error> {
{
check_bucket_name(&self.bucket, true)?;
if self.client.is_aws_host() {
return Err(Error::UnsupportedApi("ListenBucketNotification".into()));
}
}
let mut query_params: Multimap = self.extra_query_params.unwrap_or_default();
{
if let Some(v) = self.prefix {
query_params.add("prefix", v);
}
if let Some(v) = self.suffix {
query_params.add("suffix", v);
}
if let Some(v) = self.events {
for e in v.into_iter() {
query_params.add("events", e);
}
} else {
query_params.add("events", "s3:ObjectCreated:*");
query_params.add("events", "s3:ObjectRemoved:*");
query_params.add("events", "s3:ObjectAccessed:*");
}
}
Ok(S3Request::new(self.client, Method::GET)
.region(self.region)
.bucket(Some(self.bucket))
.query_params(query_params)
.headers(self.extra_headers.unwrap_or_default()))
}
}

View File

@ -14,41 +14,37 @@
// limitations under the License. // limitations under the License.
use crate::s3::Client; use crate::s3::Client;
use crate::s3::builders::SegmentedBytes;
use crate::s3::client::DEFAULT_REGION; use crate::s3::client::DEFAULT_REGION;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::http::BaseUrl; use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::MakeBucketResponse; use crate::s3::response::MakeBucketResponse;
use crate::s3::segmented_bytes::SegmentedBytes;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::{Multimap, check_bucket_name}; use crate::s3::utils::check_bucket_name;
use http::Method; use http::Method;
/// Argument builder for [make_bucket()](Client::make_bucket) API /// Argument builder for [make_bucket()](Client::make_bucket) API
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct MakeBucket { pub struct MakeBucket {
pub client: Option<Client>, client: Client,
pub extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
pub extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
pub region: Option<String>, region: Option<String>,
pub bucket: String, bucket: String,
pub object_lock: bool, object_lock: bool,
} }
impl MakeBucket { impl MakeBucket {
pub fn new(bucket: &str) -> Self { pub fn new(client: Client, bucket: String) -> Self {
Self { Self {
bucket: bucket.to_owned(), client,
bucket,
..Default::default() ..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers; self.extra_headers = extra_headers;
self self
@ -70,64 +66,37 @@ impl MakeBucket {
} }
} }
#[derive(Default, Debug)]
pub struct MakeBucketPhantomData;
impl S3Api for MakeBucket { impl S3Api for MakeBucket {
type S3Response = MakeBucketResponse; type S3Response = MakeBucketResponse;
} }
impl ToS3Request for MakeBucket { impl ToS3Request for MakeBucket {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
let base_url: &BaseUrl = match &self.client {
None => return Err(Error::NoClientProvided),
Some(c) => &c.base_url,
};
let region1: Option<&str> = self.region.as_deref(); let region1: Option<&str> = self.region.as_deref();
let region2: Option<&str> = if base_url.region.is_empty() { let region2: Option<&str> = self.client.get_region_from_url();
None
} else {
Some(base_url.region.as_str())
};
let region: &str = match (region1, region2) { let region_str: String = match (region1, region2) {
(None, None) => DEFAULT_REGION, (None, None) => DEFAULT_REGION.to_string(),
(Some(r), None) | (None, Some(r)) => r, // Take the non-None value (Some(_), None) => self.region.unwrap(),
(Some(r1), Some(r2)) if r1 == r2 => r1, // Both are Some and equal (None, Some(v)) => v.to_string(),
(Some(r1), Some(r2)) if r1 == r2 => self.region.unwrap(), // Both are Some and equal
(Some(r1), Some(r2)) => { (Some(r1), Some(r2)) => {
return Err(Error::RegionMismatch(r1.to_string(), r2.to_string())); return Err(Error::RegionMismatch(r1.to_string(), r2.to_string()));
} }
}; };
let mut headers: Multimap = self let mut headers: Multimap = self.extra_headers.unwrap_or_default();
.extra_headers
.as_ref()
.filter(|v| !v.is_empty())
.cloned()
.unwrap_or_default();
if self.object_lock { if self.object_lock {
headers.insert( headers.add("x-amz-bucket-object-lock-enabled", "true");
String::from("x-amz-bucket-object-lock-enabled"),
String::from("true"),
);
} }
let query_params: Multimap = self let data: String = match region_str.as_str() {
.extra_query_params
.as_ref()
.filter(|v| !v.is_empty())
.cloned()
.unwrap_or_default();
let data: String = match region {
DEFAULT_REGION => String::new(), DEFAULT_REGION => String::new(),
_ => format!( _ => format!(
"<CreateBucketConfiguration><LocationConstraint>{}</LocationConstraint></CreateBucketConfiguration>", "<CreateBucketConfiguration><LocationConstraint>{}</LocationConstraint></CreateBucketConfiguration>",
region region_str
), ),
}; };
@ -136,15 +105,11 @@ impl ToS3Request for MakeBucket {
false => Some(SegmentedBytes::from(data)), false => Some(SegmentedBytes::from(data)),
}; };
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?; Ok(S3Request::new(self.client, Method::PUT)
.region(Some(region_str))
let req = S3Request::new(client, Method::PUT) .bucket(Some(self.bucket))
.region(Some(region)) .query_params(self.extra_query_params.unwrap_or_default())
.bucket(Some(&self.bucket))
.query_params(query_params)
.headers(headers) .headers(headers)
.body(body); .body(body))
Ok(req)
} }
} }

View File

@ -13,9 +13,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::s3::builders::SegmentedBytes; use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::sse::{Sse, SseCustomerKey}; use crate::s3::segmented_bytes::SegmentedBytes;
use crate::s3::utils::{Multimap, check_bucket_name, merge}; use crate::s3::sse::SseCustomerKey;
use crate::s3::utils::{check_bucket_name, check_object_name};
use crate::s3::{ use crate::s3::{
client::Client, client::Client,
error::Error, error::Error,
@ -28,7 +29,7 @@ use serde_json::json;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct ObjectPrompt { pub struct ObjectPrompt {
client: Option<Client>, client: Client,
bucket: String, bucket: String,
object: String, object: String,
prompt: String, prompt: String,
@ -43,21 +44,16 @@ pub struct ObjectPrompt {
// builder interface // builder interface
impl ObjectPrompt { impl ObjectPrompt {
pub fn new(bucket: &str, object: &str, prompt: &str) -> Self { pub fn new(client: Client, bucket: String, object: String, prompt: String) -> Self {
ObjectPrompt { ObjectPrompt {
client: None, client,
bucket: bucket.to_string(), bucket,
object: object.to_string(), object,
prompt: prompt.to_string(), prompt,
..Default::default() ..Default::default()
} }
} }
pub fn client(mut self, client: &Client) -> Self {
self.client = Some(client.clone());
self
}
pub fn lambda_arn(mut self, lambda_arn: &str) -> Self { pub fn lambda_arn(mut self, lambda_arn: &str) -> Self {
self.lambda_arn = Some(lambda_arn.to_string()); self.lambda_arn = Some(lambda_arn.to_string());
self self
@ -89,47 +85,28 @@ impl ObjectPrompt {
} }
} }
// internal helpers impl S3Api for ObjectPrompt {
impl ObjectPrompt { type S3Response = ObjectPromptResponse;
fn get_headers(&self) -> Multimap {
let mut headers = Multimap::new();
if let Some(v) = &self.ssec {
merge(&mut headers, &v.headers());
}
headers
}
} }
impl ToS3Request for ObjectPrompt { impl ToS3Request for ObjectPrompt {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
{
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
check_object_name(&self.object)?;
if self.object.is_empty() { if self.client.is_aws_host() {
return Err(Error::InvalidObjectName(String::from( return Err(Error::UnsupportedApi("ObjectPrompt".into()));
"object name cannot be empty",
)));
} }
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?; if self.ssec.is_some() && !self.client.is_secure() {
if self.ssec.is_some() && !client.is_secure() {
return Err(Error::SseTlsRequired(None)); return Err(Error::SseTlsRequired(None));
} }
}
let mut query_params: Multimap = self.extra_query_params.unwrap_or_default();
query_params.add_version(self.version_id);
let mut headers = Multimap::new(); query_params.add(
if let Some(v) = &self.extra_headers { "lambdaArn",
merge(&mut headers, v);
}
merge(&mut headers, &self.get_headers());
let mut query_params = Multimap::new();
if let Some(v) = &self.extra_query_params {
merge(&mut query_params, v);
}
if let Some(v) = &self.version_id {
query_params.insert(String::from("versionId"), v.to_string());
}
query_params.insert(
String::from("lambdaArn"),
self.lambda_arn self.lambda_arn
.as_ref() .as_ref()
.map(ToString::to_string) .map(ToString::to_string)
@ -139,18 +116,12 @@ impl ToS3Request for ObjectPrompt {
let prompt_body = json!({ "prompt": self.prompt }); let prompt_body = json!({ "prompt": self.prompt });
let body: SegmentedBytes = SegmentedBytes::from(Bytes::from(prompt_body.to_string())); let body: SegmentedBytes = SegmentedBytes::from(Bytes::from(prompt_body.to_string()));
let req = S3Request::new(client, Method::POST) Ok(S3Request::new(self.client, Method::POST)
.region(self.region.as_deref()) .region(self.region)
.bucket(Some(&self.bucket)) .bucket(Some(self.bucket))
.object(Some(&self.object)) .object(Some(self.object))
.query_params(query_params) .query_params(query_params)
.headers(headers) .headers(self.extra_headers.unwrap_or_default())
.body(Some(body)); .body(Some(body)))
Ok(req)
} }
} }
impl S3Api for ObjectPrompt {
type S3Response = ObjectPromptResponse;
}

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,6 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::s3::Client;
use crate::s3::builders::BucketCommon; use crate::s3::builders::BucketCommon;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::response::RemoveBucketResponse; use crate::s3::response::RemoveBucketResponse;
@ -32,30 +31,13 @@ impl S3Api for RemoveBucket {
} }
impl ToS3Request for RemoveBucket { impl ToS3Request for RemoveBucket {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
let headers = self Ok(S3Request::new(self.client, Method::DELETE)
.extra_headers .region(self.region)
.as_ref() .bucket(Some(self.bucket))
.filter(|v| !v.is_empty()) .query_params(self.extra_query_params.unwrap_or_default())
.cloned() .headers(self.extra_headers.unwrap_or_default()))
.unwrap_or_default();
let query_params = self
.extra_query_params
.as_ref()
.filter(|v| !v.is_empty())
.cloned()
.unwrap_or_default();
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)
} }
} }

View File

@ -15,26 +15,30 @@
//! Builders for RemoveObject APIs. //! Builders for RemoveObject APIs.
use std::pin::Pin;
use async_trait::async_trait; use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
use futures_util::{Stream, StreamExt, stream as futures_stream}; use futures_util::{Stream, StreamExt, stream as futures_stream};
use http::Method; use http::Method;
use std::pin::Pin;
use tokio_stream::iter as stream_iter; use tokio_stream::iter as stream_iter;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::DeleteError;
use crate::s3::types::ListEntry;
use crate::s3::utils::{check_object_name, insert};
use crate::s3::{ use crate::s3::{
Client, Client,
client_core::ClientCore,
error::Error, error::Error,
response::{RemoveObjectResponse, RemoveObjectsResponse}, response::{RemoveObjectResponse, RemoveObjectsResponse},
types::{S3Api, S3Request, ToS3Request, ToStream}, types::{S3Api, S3Request, ToS3Request, ToStream},
utils::{Multimap, check_bucket_name, md5sum_hash, merge}, utils::{check_bucket_name, md5sum_hash},
}; };
// region: object-to-delete
/// Specify an object to be deleted. The object can be specified by key or by /// Specify an object to be deleted. The object can be specified by key or by
/// key and version_id via the From trait. /// key and version_id via the From trait.
#[derive(Debug, Clone)] #[derive(Debug, Clone, Default)]
pub struct ObjectToDelete { pub struct ObjectToDelete {
key: String, key: String,
version_id: Option<String>, version_id: Option<String>,
@ -43,8 +47,8 @@ pub struct ObjectToDelete {
/// A key can be converted into a DeleteObject. The version_id is set to None. /// A key can be converted into a DeleteObject. The version_id is set to None.
impl From<&str> for ObjectToDelete { impl From<&str> for ObjectToDelete {
fn from(key: &str) -> Self { fn from(key: &str) -> Self {
ObjectToDelete { Self {
key: key.to_string(), key: key.to_owned(),
version_id: None, version_id: None,
} }
} }
@ -53,7 +57,7 @@ impl From<&str> for ObjectToDelete {
/// A tuple of key and version_id can be converted into a DeleteObject. /// A tuple of key and version_id can be converted into a DeleteObject.
impl From<(&str, &str)> for ObjectToDelete { impl From<(&str, &str)> for ObjectToDelete {
fn from((key, version_id): (&str, &str)) -> Self { fn from((key, version_id): (&str, &str)) -> Self {
ObjectToDelete { Self {
key: key.to_string(), key: key.to_string(),
version_id: Some(version_id.to_string()), version_id: Some(version_id.to_string()),
} }
@ -63,48 +67,58 @@ impl From<(&str, &str)> for ObjectToDelete {
/// A tuple of key and option version_id can be converted into a DeleteObject. /// A tuple of key and option version_id can be converted into a DeleteObject.
impl From<(&str, Option<&str>)> for ObjectToDelete { impl From<(&str, Option<&str>)> for ObjectToDelete {
fn from((key, version_id): (&str, Option<&str>)) -> Self { fn from((key, version_id): (&str, Option<&str>)) -> Self {
ObjectToDelete { Self {
key: key.to_string(), key: key.to_string(),
version_id: version_id.map(|v| v.to_string()), version_id: version_id.map(|v| v.to_string()),
} }
} }
} }
#[derive(Debug, Clone)] impl From<ListEntry> for ObjectToDelete {
fn from(entry: ListEntry) -> Self {
Self {
key: entry.name,
version_id: entry.version_id,
}
}
}
impl From<DeleteError> for ObjectToDelete {
fn from(entry: DeleteError) -> Self {
Self {
key: entry.object_name,
version_id: entry.version_id,
}
}
}
// endregion: object-to-delete
// region: remove-object
#[derive(Debug, Clone, Default)]
pub struct RemoveObject { pub struct RemoveObject {
client: Option<Client>, client: Client,
bucket: String,
object: ObjectToDelete,
bypass_governance_mode: bool,
extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
region: Option<String>, region: Option<String>,
bucket: String,
object: ObjectToDelete,
bypass_governance_mode: bool,
} }
impl RemoveObject { impl RemoveObject {
pub fn new(bucket: &str, object: impl Into<ObjectToDelete>) -> Self { pub fn new(client: Client, bucket: String, object: impl Into<ObjectToDelete>) -> Self {
Self { Self {
client: None, client,
bucket,
bucket: bucket.to_string(),
object: object.into(), object: object.into(),
..Default::default()
bypass_governance_mode: false,
extra_headers: None,
extra_query_params: None,
region: None,
} }
} }
pub fn client(mut self, client: &Client) -> Self {
self.client = Some(client.clone());
self
}
pub fn bypass_governance_mode(mut self, bypass_governance_mode: bool) -> Self { pub fn bypass_governance_mode(mut self, bypass_governance_mode: bool) -> Self {
self.bypass_governance_mode = bypass_governance_mode; self.bypass_governance_mode = bypass_governance_mode;
self self
@ -126,43 +140,33 @@ impl RemoveObject {
} }
} }
impl ToS3Request for RemoveObject {
fn to_s3request(&self) -> Result<S3Request, Error> {
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);
}
if let Some(v) = &self.object.version_id {
query_params.insert(String::from("versionId"), v.to_string());
}
let req = S3Request::new(
self.client.as_ref().ok_or(Error::NoClientProvided)?,
Method::DELETE,
)
.region(self.region.as_deref())
.bucket(Some(&self.bucket))
.object(Some(&self.object.key))
.query_params(query_params)
.headers(headers);
Ok(req)
}
}
impl S3Api for RemoveObject { impl S3Api for RemoveObject {
type S3Response = RemoveObjectResponse; type S3Response = RemoveObjectResponse;
} }
#[derive(Debug, Clone)] impl ToS3Request for RemoveObject {
fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?;
check_object_name(&self.object.key)?;
let mut query_params: Multimap = self.extra_query_params.unwrap_or_default();
query_params.add_version(self.object.version_id);
Ok(S3Request::new(self.client, Method::DELETE)
.region(self.region)
.bucket(Some(self.bucket))
.object(Some(self.object.key))
.query_params(query_params)
.headers(self.extra_headers.unwrap_or_default()))
}
}
// endregion: remove-object
// region: remove-object-api
#[derive(Debug, Clone, Default)]
pub struct RemoveObjectsApi { pub struct RemoveObjectsApi {
client: Option<ClientCore>, client: Client,
bucket: String, bucket: String,
objects: Vec<ObjectToDelete>, objects: Vec<ObjectToDelete>,
@ -176,27 +180,16 @@ pub struct RemoveObjectsApi {
} }
impl RemoveObjectsApi { impl RemoveObjectsApi {
pub fn new(bucket: &str, objects: Vec<ObjectToDelete>) -> Self { #[inline]
pub fn new(client: Client, bucket: String, objects: Vec<ObjectToDelete>) -> Self {
RemoveObjectsApi { RemoveObjectsApi {
client: None, client,
bucket,
bucket: bucket.to_string(),
objects, objects,
..Default::default()
bypass_governance_mode: false,
verbose_mode: false,
extra_headers: None,
extra_query_params: None,
region: None,
} }
} }
pub fn client(mut self, client: &ClientCore) -> Self {
self.client = Some(client.clone());
self
}
pub fn bypass_governance_mode(mut self, bypass_governance_mode: bool) -> Self { pub fn bypass_governance_mode(mut self, bypass_governance_mode: bool) -> Self {
self.bypass_governance_mode = bypass_governance_mode; self.bypass_governance_mode = bypass_governance_mode;
self self
@ -226,8 +219,12 @@ impl RemoveObjectsApi {
} }
} }
impl S3Api for RemoveObjectsApi {
type S3Response = RemoveObjectsResponse;
}
impl ToS3Request for RemoveObjectsApi { impl ToS3Request for RemoveObjectsApi {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
let mut data = String::from("<Delete>"); let mut data = String::from("<Delete>");
@ -249,56 +246,40 @@ impl ToS3Request for RemoveObjectsApi {
data.push_str("</Delete>"); data.push_str("</Delete>");
let data: Bytes = data.into(); let data: Bytes = data.into();
let mut headers = Multimap::new(); let mut headers: Multimap = self.extra_headers.unwrap_or_default();
if let Some(v) = &self.extra_headers { {
merge(&mut headers, v);
}
if self.bypass_governance_mode { if self.bypass_governance_mode {
headers.insert( headers.add("x-amz-bypass-governance-retention", "true");
String::from("x-amz-bypass-governance-retention"),
String::from("true"),
);
} }
headers.insert( headers.add("Content-Type", "application/xml");
String::from("Content-Type"), headers.add("Content-MD5", md5sum_hash(data.as_ref()));
String::from("application/xml"),
);
headers.insert(String::from("Content-MD5"), md5sum_hash(data.as_ref()));
let mut query_params = Multimap::new();
if let Some(v) = &self.extra_query_params {
merge(&mut query_params, v);
} }
query_params.insert(String::from("delete"), String::new());
let client = self.client.as_ref().ok_or(Error::NoClientProvided)?.inner(); Ok(S3Request::new(self.client, Method::POST)
let req = S3Request::new(client, Method::POST) .region(self.region)
.region(self.region.as_deref()) .bucket(Some(self.bucket))
.bucket(Some(&self.bucket)) .query_params(insert(self.extra_query_params, "delete"))
.query_params(query_params)
.headers(headers) .headers(headers)
.body(Some(data.into())); .body(Some(data.into())))
Ok(req)
} }
} }
impl S3Api for RemoveObjectsApi { // endregion: remove-object-api
type S3Response = RemoveObjectsResponse;
}
// region: delete-object
pub struct DeleteObjects { pub struct DeleteObjects {
items: Pin<Box<dyn Stream<Item = ObjectToDelete> + Send + Sync>>, items: Pin<Box<dyn Stream<Item = ObjectToDelete> + Send + Sync>>,
} }
impl DeleteObjects { impl DeleteObjects {
pub fn from_stream(s: impl Stream<Item = ObjectToDelete> + Send + Sync + 'static) -> Self { pub fn from_stream(s: impl Stream<Item = ObjectToDelete> + Send + Sync + 'static) -> Self {
DeleteObjects { items: Box::pin(s) } Self { items: Box::pin(s) }
} }
} }
impl From<ObjectToDelete> for DeleteObjects { impl From<ObjectToDelete> for DeleteObjects {
fn from(delete_object: ObjectToDelete) -> Self { fn from(delete_object: ObjectToDelete) -> Self {
DeleteObjects::from_stream(stream_iter(std::iter::once(delete_object))) Self::from_stream(stream_iter(std::iter::once(delete_object)))
} }
} }
@ -307,12 +288,16 @@ where
I: Iterator<Item = ObjectToDelete> + Send + Sync + 'static, I: Iterator<Item = ObjectToDelete> + Send + Sync + 'static,
{ {
fn from(keys: I) -> Self { fn from(keys: I) -> Self {
DeleteObjects::from_stream(stream_iter(keys)) Self::from_stream(stream_iter(keys))
} }
} }
// endregion: delete-object
// region: remove-objects
pub struct RemoveObjects { pub struct RemoveObjects {
client: Option<Client>, client: Client,
bucket: String, bucket: String,
objects: DeleteObjects, objects: DeleteObjects,
@ -326,11 +311,10 @@ pub struct RemoveObjects {
} }
impl RemoveObjects { impl RemoveObjects {
pub fn new(bucket: &str, objects: impl Into<DeleteObjects>) -> Self { pub fn new(client: Client, bucket: String, objects: impl Into<DeleteObjects>) -> Self {
RemoveObjects { Self {
client: None, client,
bucket,
bucket: bucket.to_string(),
objects: objects.into(), objects: objects.into(),
bypass_governance_mode: false, bypass_governance_mode: false,
@ -342,11 +326,6 @@ impl RemoveObjects {
} }
} }
pub fn client(mut self, client: &Client) -> Self {
self.client = Some(client.clone());
self
}
pub fn bypass_governance_mode(mut self, bypass_governance_mode: bool) -> Self { pub fn bypass_governance_mode(mut self, bypass_governance_mode: bool) -> Self {
self.bypass_governance_mode = bypass_governance_mode; self.bypass_governance_mode = bypass_governance_mode;
self self
@ -386,15 +365,15 @@ impl RemoveObjects {
if objects.is_empty() { if objects.is_empty() {
return Ok(None); return Ok(None);
} }
let client_core = ClientCore::new(self.client.as_ref().ok_or(Error::NoClientProvided)?);
let request = RemoveObjectsApi::new(&self.bucket, objects) Ok(Some(
.client(&client_core) RemoveObjectsApi::new(self.client.clone(), self.bucket.clone(), objects)
.bypass_governance_mode(self.bypass_governance_mode) .bypass_governance_mode(self.bypass_governance_mode)
.verbose_mode(self.verbose_mode) .verbose_mode(self.verbose_mode)
.extra_headers(self.extra_headers.clone()) .extra_headers(self.extra_headers.clone())
.extra_query_params(self.extra_query_params.clone()) .extra_query_params(self.extra_query_params.clone())
.region(self.region.clone()); .region(self.region.clone()),
Ok(Some(request)) ))
} }
} }
@ -420,3 +399,5 @@ impl ToStream for RemoveObjects {
))) )))
} }
} }
// endregion: remove-objects

View File

@ -0,0 +1,120 @@
// 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::multimap::{Multimap, MultimapExt};
use crate::s3::response::SelectObjectContentResponse;
use crate::s3::segmented_bytes::SegmentedBytes;
use crate::s3::sse::SseCustomerKey;
use crate::s3::types::{S3Api, S3Request, SelectRequest, ToS3Request};
use crate::s3::utils::{check_bucket_name, check_object_name, insert, md5sum_hash};
use async_trait::async_trait;
use bytes::Bytes;
use http::Method;
/// Argument builder for [bucket_exists()](Client::bucket_exists) API
#[derive(Default)]
pub struct SelectObjectContent {
client: Client,
extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>,
region: Option<String>,
bucket: String,
object: String,
version_id: Option<String>,
ssec: Option<SseCustomerKey>,
request: SelectRequest,
}
impl SelectObjectContent {
pub fn new(client: Client, bucket: String, object: String) -> Self {
Self {
client,
bucket,
object,
..Default::default()
}
}
pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers;
self
}
pub fn extra_query_params(mut self, extra_query_params: Option<Multimap>) -> Self {
self.extra_query_params = extra_query_params;
self
}
pub fn region(mut self, region: Option<String>) -> Self {
self.region = region;
self
}
pub fn version_id(mut self, version_id: Option<String>) -> Self {
self.version_id = version_id;
self
}
pub fn ssec(mut self, ssec: Option<SseCustomerKey>) -> Self {
self.ssec = ssec;
self
}
pub fn request(mut self, request: SelectRequest) -> Self {
self.request = request;
self
}
}
impl S3Api for SelectObjectContent {
type S3Response = SelectObjectContentResponse;
}
#[async_trait]
impl ToS3Request for SelectObjectContent {
fn to_s3request(self) -> Result<S3Request, Error> {
{
check_bucket_name(&self.bucket, true)?;
check_object_name(&self.object)?;
if self.ssec.is_some() && !self.client.is_secure() {
return Err(Error::SseTlsRequired(None));
}
}
let region: String = self.client.get_region_cached(&self.bucket, &self.region)?;
let data = self.request.to_xml();
let bytes: Bytes = data.into();
let mut headers: Multimap = self.extra_headers.unwrap_or_default();
headers.add("Content-MD5", md5sum_hash(bytes.as_ref()));
let mut query_params: Multimap = insert(self.extra_query_params, "select");
query_params.add("select-type", "2");
let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(bytes));
Ok(S3Request::new(self.client, Method::POST)
.region(Some(region))
.bucket(Some(self.bucket))
.query_params(query_params)
.object(Some(self.object))
.headers(headers)
.body(body))
}
}

View File

@ -14,18 +14,19 @@
// limitations under the License. // limitations under the License.
use crate::s3::Client; use crate::s3::Client;
use crate::s3::builders::SegmentedBytes;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::multimap::Multimap;
use crate::s3::response::SetBucketEncryptionResponse; use crate::s3::response::SetBucketEncryptionResponse;
use crate::s3::segmented_bytes::SegmentedBytes;
use crate::s3::types::{S3Api, S3Request, SseConfig, ToS3Request}; use crate::s3::types::{S3Api, S3Request, SseConfig, ToS3Request};
use crate::s3::utils::{Multimap, check_bucket_name}; use crate::s3::utils::{check_bucket_name, insert};
use bytes::Bytes; use bytes::Bytes;
use http::Method; use http::Method;
/// Argument builder for [set_bucket_encryption()](Client::set_bucket_encryption) API /// Argument builder for [set_bucket_encryption()](Client::set_bucket_encryption) API
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct SetBucketEncryption { pub struct SetBucketEncryption {
client: Option<Client>, client: Client,
extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
@ -36,18 +37,14 @@ pub struct SetBucketEncryption {
} }
impl SetBucketEncryption { impl SetBucketEncryption {
pub fn new(bucket: &str) -> Self { pub fn new(client: Client, bucket: String) -> Self {
Self { Self {
bucket: bucket.to_owned(), client,
bucket,
..Default::default() ..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers; self.extra_headers = extra_headers;
self self
@ -74,35 +71,17 @@ impl S3Api for SetBucketEncryption {
} }
impl ToS3Request for SetBucketEncryption { impl ToS3Request for SetBucketEncryption {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; 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("encryption".into(), String::new());
let bytes: Bytes = self.config.to_xml().into(); let bytes: Bytes = self.config.to_xml().into();
let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(bytes)); let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(bytes));
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?;
let req = S3Request::new(client, Method::GET) Ok(S3Request::new(self.client, Method::GET)
.region(self.region.as_deref()) .region(self.region)
.bucket(Some(&self.bucket)) .bucket(Some(self.bucket))
.query_params(query_params) .query_params(insert(self.extra_query_params, "encryption"))
.headers(headers) .headers(self.extra_headers.unwrap_or_default())
.body(body); .body(body))
Ok(req)
} }
} }

View File

@ -14,38 +14,36 @@
// limitations under the License. // limitations under the License.
use crate::s3::Client; use crate::s3::Client;
use crate::s3::builders::SegmentedBytes;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::SetBucketLifecycleResponse; use crate::s3::response::SetBucketLifecycleResponse;
use crate::s3::segmented_bytes::SegmentedBytes;
use crate::s3::types::{LifecycleConfig, S3Api, S3Request, ToS3Request}; use crate::s3::types::{LifecycleConfig, S3Api, S3Request, ToS3Request};
use crate::s3::utils::{Multimap, check_bucket_name, md5sum_hash}; use crate::s3::utils::{check_bucket_name, insert, md5sum_hash};
use bytes::Bytes; use bytes::Bytes;
use http::Method; use http::Method;
/// Argument builder for [set_bucket_lifecycle()](crate::s3::client::Client::set_bucket_lifecycle) API /// Argument builder for [set_bucket_lifecycle()](crate::s3::client::Client::set_bucket_lifecycle) API
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct SetBucketLifecycle { pub struct SetBucketLifecycle {
pub(crate) client: Option<Client>, client: Client,
pub(crate) extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
pub(crate) extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
pub(crate) region: Option<String>, region: Option<String>,
pub(crate) bucket: String, bucket: String,
pub(crate) config: LifecycleConfig, config: LifecycleConfig,
} }
impl SetBucketLifecycle { impl SetBucketLifecycle {
pub fn new(bucket: &str) -> Self { pub fn new(client: Client, bucket: String) -> Self {
Self { Self {
bucket: bucket.to_owned(), client,
bucket,
..Default::default() ..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers; self.extra_headers = extra_headers;
@ -73,37 +71,20 @@ impl S3Api for SetBucketLifecycle {
} }
impl ToS3Request for SetBucketLifecycle { impl ToS3Request for SetBucketLifecycle {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
let mut headers = self let mut headers: Multimap = self.extra_headers.unwrap_or_default();
.extra_headers
.as_ref()
.filter(|v| !v.is_empty())
.cloned()
.unwrap_or_default();
let mut query_params = self
.extra_query_params
.as_ref()
.filter(|v| !v.is_empty())
.cloned()
.unwrap_or_default();
query_params.insert("lifecycle".into(), String::new());
let bytes: Bytes = self.config.to_xml().into(); let bytes: Bytes = self.config.to_xml().into();
headers.insert(String::from("Content-MD5"), md5sum_hash(&bytes)); headers.add("Content-MD5", md5sum_hash(&bytes));
let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(bytes)); let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(bytes));
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?; Ok(S3Request::new(self.client, Method::PUT)
.region(self.region)
let req = S3Request::new(client, Method::PUT) .bucket(Some(self.bucket))
.region(self.region.as_deref()) .query_params(insert(self.extra_query_params, "lifecycle"))
.bucket(Some(&self.bucket))
.query_params(query_params)
.headers(headers) .headers(headers)
.body(body); .body(body))
Ok(req)
} }
} }

View File

@ -14,38 +14,36 @@
// limitations under the License. // limitations under the License.
use crate::s3::Client; use crate::s3::Client;
use crate::s3::builders::SegmentedBytes;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::multimap::Multimap;
use crate::s3::response::SetBucketNotificationResponse; use crate::s3::response::SetBucketNotificationResponse;
use crate::s3::segmented_bytes::SegmentedBytes;
use crate::s3::types::{NotificationConfig, S3Api, S3Request, ToS3Request}; use crate::s3::types::{NotificationConfig, S3Api, S3Request, ToS3Request};
use crate::s3::utils::{Multimap, check_bucket_name}; use crate::s3::utils::{check_bucket_name, insert};
use bytes::Bytes; use bytes::Bytes;
use http::Method; use http::Method;
/// Argument builder for [set_bucket_notification()](crate::s3::client::Client::set_bucket_notification) API /// Argument builder for [set_bucket_notification()](crate::s3::client::Client::set_bucket_notification) API
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct SetBucketNotification { pub struct SetBucketNotification {
pub(crate) client: Option<Client>, client: Client,
pub(crate) extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
pub(crate) extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
pub(crate) region: Option<String>, region: Option<String>,
pub(crate) bucket: String, bucket: String,
pub(crate) config: NotificationConfig, config: NotificationConfig,
} }
impl SetBucketNotification { impl SetBucketNotification {
pub fn new(bucket: &str) -> Self { pub fn new(client: Client, bucket: String) -> Self {
Self { Self {
bucket: bucket.to_owned(), client,
bucket,
..Default::default() ..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers; self.extra_headers = extra_headers;
@ -73,36 +71,17 @@ impl S3Api for SetBucketNotification {
} }
impl ToS3Request for SetBucketNotification { impl ToS3Request for SetBucketNotification {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; 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 bytes: Bytes = self.config.to_xml().into();
let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(bytes)); let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(bytes));
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?; Ok(S3Request::new(self.client, Method::PUT)
.region(self.region)
let req = S3Request::new(client, Method::PUT) .bucket(Some(self.bucket))
.region(self.region.as_deref()) .query_params(insert(self.extra_query_params, "notification"))
.bucket(Some(&self.bucket)) .headers(self.extra_headers.unwrap_or_default())
.query_params(query_params) .body(body))
.headers(headers)
.body(body);
Ok(req)
} }
} }

View File

@ -14,38 +14,36 @@
// limitations under the License. // limitations under the License.
use crate::s3::Client; use crate::s3::Client;
use crate::s3::builders::SegmentedBytes;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::multimap::Multimap;
use crate::s3::response::SetBucketPolicyResponse; use crate::s3::response::SetBucketPolicyResponse;
use crate::s3::segmented_bytes::SegmentedBytes;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::{Multimap, check_bucket_name}; use crate::s3::utils::{check_bucket_name, insert};
use bytes::Bytes; use bytes::Bytes;
use http::Method; use http::Method;
/// Argument builder for [set_bucket_policy()](crate::s3::client::Client::set_bucket_policy) API /// Argument builder for [set_bucket_policy()](crate::s3::client::Client::set_bucket_policy) API
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct SetBucketPolicy { pub struct SetBucketPolicy {
pub(crate) client: Option<Client>, client: Client,
pub(crate) extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
pub(crate) extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
pub(crate) region: Option<String>, region: Option<String>,
pub(crate) bucket: String, bucket: String,
pub(crate) config: String, //TODO consider PolicyConfig struct config: String, //TODO consider PolicyConfig struct
} }
impl SetBucketPolicy { impl SetBucketPolicy {
pub fn new(bucket: &str) -> Self { pub fn new(client: Client, bucket: String) -> Self {
Self { Self {
bucket: bucket.to_owned(), client,
bucket,
..Default::default() ..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers; self.extra_headers = extra_headers;
@ -73,36 +71,17 @@ impl S3Api for SetBucketPolicy {
} }
impl ToS3Request for SetBucketPolicy { impl ToS3Request for SetBucketPolicy {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
let headers = self let bytes: Bytes = self.config.into();
.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("policy"), String::new());
let bytes: Bytes = self.config.to_string().into();
let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(bytes)); let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(bytes));
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?; Ok(S3Request::new(self.client, Method::PUT)
.region(self.region)
let req = S3Request::new(client, Method::PUT) .bucket(Some(self.bucket))
.region(self.region.as_deref()) .query_params(insert(self.extra_query_params, "policy"))
.bucket(Some(&self.bucket)) .headers(self.extra_headers.unwrap_or_default())
.query_params(query_params) .body(body))
.headers(headers)
.body(body);
Ok(req)
} }
} }

View File

@ -14,38 +14,36 @@
// limitations under the License. // limitations under the License.
use crate::s3::Client; use crate::s3::Client;
use crate::s3::builders::SegmentedBytes;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::multimap::Multimap;
use crate::s3::response::SetBucketReplicationResponse; use crate::s3::response::SetBucketReplicationResponse;
use crate::s3::segmented_bytes::SegmentedBytes;
use crate::s3::types::{ReplicationConfig, S3Api, S3Request, ToS3Request}; use crate::s3::types::{ReplicationConfig, S3Api, S3Request, ToS3Request};
use crate::s3::utils::{Multimap, check_bucket_name}; use crate::s3::utils::{check_bucket_name, insert};
use bytes::Bytes; use bytes::Bytes;
use http::Method; use http::Method;
/// Argument builder for [set_bucket_replication()](crate::s3::client::Client::set_bucket_replication) API /// Argument builder for [set_bucket_replication()](crate::s3::client::Client::set_bucket_replication) API
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct SetBucketReplication { pub struct SetBucketReplication {
pub(crate) client: Option<Client>, client: Client,
pub(crate) extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
pub(crate) extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
pub(crate) region: Option<String>, region: Option<String>,
pub(crate) bucket: String, bucket: String,
pub(crate) config: ReplicationConfig, config: ReplicationConfig,
} }
impl SetBucketReplication { impl SetBucketReplication {
pub fn new(bucket: &str) -> Self { pub fn new(client: Client, bucket: String) -> Self {
Self { Self {
bucket: bucket.to_owned(), client,
bucket,
..Default::default() ..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers; self.extra_headers = extra_headers;
@ -73,36 +71,17 @@ impl S3Api for SetBucketReplication {
} }
impl ToS3Request for SetBucketReplication { impl ToS3Request for SetBucketReplication {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; 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 bytes: Bytes = self.config.to_xml().into();
let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(bytes)); let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(bytes));
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?; Ok(S3Request::new(self.client, Method::PUT)
.region(self.region)
let req = S3Request::new(client, Method::PUT) .bucket(Some(self.bucket))
.region(self.region.as_deref()) .query_params(insert(self.extra_query_params, "replication"))
.bucket(Some(&self.bucket)) .headers(self.extra_headers.unwrap_or_default())
.query_params(query_params) .body(body))
.headers(headers)
.body(body);
Ok(req)
} }
} }

View File

@ -14,11 +14,12 @@
// limitations under the License. // limitations under the License.
use crate::s3::Client; use crate::s3::Client;
use crate::s3::builders::SegmentedBytes;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::multimap::Multimap;
use crate::s3::response::SetBucketTagsResponse; use crate::s3::response::SetBucketTagsResponse;
use crate::s3::segmented_bytes::SegmentedBytes;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::{Multimap, check_bucket_name}; use crate::s3::utils::{check_bucket_name, insert};
use bytes::Bytes; use bytes::Bytes;
use http::Method; use http::Method;
use std::collections::HashMap; use std::collections::HashMap;
@ -26,29 +27,25 @@ use std::collections::HashMap;
/// Argument builder for [set_bucket_tags()](crate::s3::client::Client::set_bucket_tags) API /// Argument builder for [set_bucket_tags()](crate::s3::client::Client::set_bucket_tags) API
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct SetBucketTags { pub struct SetBucketTags {
pub(crate) client: Option<Client>, client: Client,
pub(crate) extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
pub(crate) extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
pub(crate) region: Option<String>, region: Option<String>,
pub(crate) bucket: String, bucket: String,
pub(crate) tags: HashMap<String, String>, tags: HashMap<String, String>,
} }
impl SetBucketTags { impl SetBucketTags {
pub fn new(bucket: &str) -> Self { pub fn new(client: Client, bucket: String) -> Self {
Self { Self {
bucket: bucket.to_owned(), client,
bucket,
..Default::default() ..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers; self.extra_headers = extra_headers;
self self
@ -75,24 +72,10 @@ impl S3Api for SetBucketTags {
} }
impl ToS3Request for SetBucketTags { impl ToS3Request for SetBucketTags {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
let headers = self let data: String = {
.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("<Tagging>"); let mut data = String::from("<Tagging>");
if !self.tags.is_empty() { if !self.tags.is_empty() {
data.push_str("<TagSet>"); data.push_str("<TagSet>");
@ -109,17 +92,15 @@ impl ToS3Request for SetBucketTags {
data.push_str("</TagSet>"); data.push_str("</TagSet>");
} }
data.push_str("</Tagging>"); data.push_str("</Tagging>");
data
};
let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(Bytes::from(data))); let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(Bytes::from(data)));
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?;
let req = S3Request::new(client, Method::PUT) Ok(S3Request::new(self.client, Method::PUT)
.region(self.region.as_deref()) .region(self.region)
.bucket(Some(&self.bucket)) .bucket(Some(self.bucket))
.query_params(query_params) .query_params(insert(self.extra_query_params, "tagging"))
.headers(headers) .headers(self.extra_headers.unwrap_or_default())
.body(body); .body(body))
Ok(req)
} }
} }

View File

@ -14,11 +14,12 @@
// limitations under the License. // limitations under the License.
use crate::s3::Client; use crate::s3::Client;
use crate::s3::builders::SegmentedBytes;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::multimap::Multimap;
use crate::s3::response::SetBucketVersioningResponse; use crate::s3::response::SetBucketVersioningResponse;
use crate::s3::segmented_bytes::SegmentedBytes;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::{Multimap, check_bucket_name}; use crate::s3::utils::{check_bucket_name, insert};
use bytes::Bytes; use bytes::Bytes;
use http::Method; use http::Method;
use std::fmt; use std::fmt;
@ -43,30 +44,26 @@ impl fmt::Display for VersioningStatus {
/// Argument builder for [set_bucket_encryption()](crate::s3::client::Client::set_bucket_encryption) API /// Argument builder for [set_bucket_encryption()](crate::s3::client::Client::set_bucket_encryption) API
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct SetBucketVersioning { pub struct SetBucketVersioning {
pub(crate) client: Option<Client>, client: Client,
pub(crate) extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
pub(crate) extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
pub(crate) region: Option<String>, region: Option<String>,
pub(crate) bucket: String, bucket: String,
pub(crate) status: Option<VersioningStatus>, status: Option<VersioningStatus>,
pub(crate) mfa_delete: Option<bool>, mfa_delete: Option<bool>,
} }
impl SetBucketVersioning { impl SetBucketVersioning {
pub fn new(bucket: &str) -> Self { pub fn new(client: Client, bucket: String) -> Self {
Self { Self {
bucket: bucket.to_owned(), client,
bucket,
..Default::default() ..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers; self.extra_headers = extra_headers;
self self
@ -98,24 +95,10 @@ impl S3Api for SetBucketVersioning {
} }
impl ToS3Request for SetBucketVersioning { impl ToS3Request for SetBucketVersioning {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
let headers = self let data: String = {
.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("versioning".into(), String::new());
let mut data = "<VersioningConfiguration>".to_string(); let mut data = "<VersioningConfiguration>".to_string();
if let Some(v) = self.mfa_delete { if let Some(v) = self.mfa_delete {
@ -135,17 +118,15 @@ impl ToS3Request for SetBucketVersioning {
}; };
data.push_str("</VersioningConfiguration>"); data.push_str("</VersioningConfiguration>");
data
};
let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(Bytes::from(data))); let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(Bytes::from(data)));
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?;
let req = S3Request::new(client, Method::PUT) Ok(S3Request::new(self.client, Method::PUT)
.region(self.region.as_deref()) .region(self.region)
.bucket(Some(&self.bucket)) .bucket(Some(self.bucket))
.query_params(query_params) .query_params(insert(self.extra_query_params, "versioning"))
.headers(headers) .headers(self.extra_headers.unwrap_or_default())
.body(body); .body(body))
Ok(req)
} }
} }

View File

@ -14,11 +14,12 @@
// limitations under the License. // limitations under the License.
use crate::s3::Client; use crate::s3::Client;
use crate::s3::builders::SegmentedBytes;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::multimap::Multimap;
use crate::s3::response::SetObjectLockConfigResponse; use crate::s3::response::SetObjectLockConfigResponse;
use crate::s3::segmented_bytes::SegmentedBytes;
use crate::s3::types::{ObjectLockConfig, S3Api, S3Request, ToS3Request}; use crate::s3::types::{ObjectLockConfig, S3Api, S3Request, ToS3Request};
use crate::s3::utils::{Multimap, check_bucket_name}; use crate::s3::utils::{check_bucket_name, insert};
use bytes::Bytes; use bytes::Bytes;
use http::Method; use http::Method;
@ -26,29 +27,25 @@ use http::Method;
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct SetObjectLockConfig { pub struct SetObjectLockConfig {
pub(crate) client: Option<Client>, client: Client,
pub(crate) extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
pub(crate) extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
pub(crate) region: Option<String>, region: Option<String>,
pub(crate) bucket: String, bucket: String,
pub(crate) config: ObjectLockConfig, config: ObjectLockConfig,
} }
impl SetObjectLockConfig { impl SetObjectLockConfig {
pub fn new(bucket: &str) -> Self { pub fn new(client: Client, bucket: String) -> Self {
Self { Self {
bucket: bucket.to_owned(), client,
bucket,
..Default::default() ..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers; self.extra_headers = extra_headers;
self self
@ -75,36 +72,17 @@ impl S3Api for SetObjectLockConfig {
} }
impl ToS3Request for SetObjectLockConfig { impl ToS3Request for SetObjectLockConfig {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; 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("object-lock"), String::new());
let bytes: Bytes = self.config.to_xml().into(); let bytes: Bytes = self.config.to_xml().into();
let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(bytes)); let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(bytes));
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?; Ok(S3Request::new(self.client, Method::PUT)
.region(self.region)
let req = S3Request::new(client, Method::PUT) .bucket(Some(self.bucket))
.region(self.region.as_deref()) .query_params(insert(self.extra_query_params, "object-lock"))
.bucket(Some(&self.bucket)) .headers(self.extra_headers.unwrap_or_default())
.query_params(query_params) .body(body))
.headers(headers)
.body(body);
Ok(req)
} }
} }

View File

@ -14,45 +14,44 @@
// limitations under the License. // limitations under the License.
use crate::s3::Client; use crate::s3::Client;
use crate::s3::builders::SegmentedBytes;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::SetObjectRetentionResponse; use crate::s3::response::SetObjectRetentionResponse;
use crate::s3::segmented_bytes::SegmentedBytes;
use crate::s3::types::{RetentionMode, S3Api, S3Request, ToS3Request}; use crate::s3::types::{RetentionMode, S3Api, S3Request, ToS3Request};
use crate::s3::utils::{Multimap, UtcTime, check_bucket_name, md5sum_hash, to_iso8601utc}; use crate::s3::utils::{
UtcTime, check_bucket_name, check_object_name, insert, md5sum_hash, to_iso8601utc,
};
use bytes::Bytes; use bytes::Bytes;
use http::Method; use http::Method;
/// Argument builder for [set_object_retention()](Client::set_object_retention) API /// Argument builder for [set_object_retention()](Client::set_object_retention) API
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct SetObjectRetention { pub struct SetObjectRetention {
pub client: Option<Client>, client: Client,
pub extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
pub extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
pub region: Option<String>, region: Option<String>,
pub bucket: String, bucket: String,
pub object: String, object: String,
pub version_id: Option<String>, version_id: Option<String>,
pub bypass_governance_mode: bool, bypass_governance_mode: bool,
pub retention_mode: Option<RetentionMode>, retention_mode: Option<RetentionMode>,
pub retain_until_date: Option<UtcTime>, retain_until_date: Option<UtcTime>,
} }
impl SetObjectRetention { impl SetObjectRetention {
pub fn new(bucket: &str) -> Self { pub fn new(client: Client, bucket: String, object: String) -> Self {
Self { Self {
bucket: bucket.to_owned(), client,
bypass_governance_mode: false, bucket,
object,
..Default::default() ..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers; self.extra_headers = extra_headers;
self self
@ -68,11 +67,6 @@ impl SetObjectRetention {
self self
} }
pub fn object(mut self, object: String) -> Self {
self.object = object;
self
}
pub fn version_id(mut self, version_id: Option<String>) -> Self { pub fn version_id(mut self, version_id: Option<String>) -> Self {
self.version_id = version_id; self.version_id = version_id;
self self
@ -99,49 +93,20 @@ impl S3Api for SetObjectRetention {
} }
impl ToS3Request for SetObjectRetention { impl ToS3Request for SetObjectRetention {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
//TODO move the following checks to a validate fn {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
check_object_name(&self.object)?;
if self.object.is_empty() {
return Err(Error::InvalidObjectName(String::from(
"object name cannot be empty",
)));
}
if self.retention_mode.is_some() ^ self.retain_until_date.is_some() { if self.retention_mode.is_some() ^ self.retain_until_date.is_some() {
return Err(Error::InvalidRetentionConfig(String::from( return Err(Error::InvalidRetentionConfig(String::from(
"both mode and retain_until_date must be set or unset", "both mode and retain_until_date must be set or unset",
))); )));
} }
let mut headers = self
.extra_headers
.as_ref()
.filter(|v| !v.is_empty())
.cloned()
.unwrap_or_default();
if self.bypass_governance_mode {
headers.insert(
String::from("x-amz-bypass-governance-retention"),
String::from("true"),
);
} }
let mut query_params = self let data: String = {
.extra_query_params let mut data: String = "<Retention>".into();
.as_ref()
.filter(|v| !v.is_empty())
.cloned()
.unwrap_or_default();
if let Some(v) = &self.version_id {
query_params.insert(String::from("versionId"), v.to_string());
}
query_params.insert(String::from("retention"), String::new());
let mut data: String = String::from("<Retention>");
if let Some(v) = &self.retention_mode { if let Some(v) = &self.retention_mode {
data.push_str("<Mode>"); data.push_str("<Mode>");
data.push_str(&v.to_string()); data.push_str(&v.to_string());
@ -153,20 +118,24 @@ impl ToS3Request for SetObjectRetention {
data.push_str("</RetainUntilDate>"); data.push_str("</RetainUntilDate>");
} }
data.push_str("</Retention>"); data.push_str("</Retention>");
data
};
headers.insert(String::from("Content-MD5"), md5sum_hash(data.as_ref())); let mut headers: Multimap = self.extra_headers.unwrap_or_default();
if self.bypass_governance_mode {
headers.add("x-amz-bypass-governance-retention", "true");
}
headers.add("Content-MD5", md5sum_hash(data.as_ref()));
let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(Bytes::from(data))); let mut query_params: Multimap = insert(self.extra_query_params, "retention");
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?; query_params.add_version(self.version_id);
let req = S3Request::new(client, Method::PUT) Ok(S3Request::new(self.client, Method::PUT)
.region(self.region.as_deref()) .region(self.region)
.bucket(Some(&self.bucket)) .bucket(Some(self.bucket))
.query_params(query_params) .query_params(query_params)
.headers(headers) .headers(headers)
.object(Some(&self.object)) .object(Some(self.object))
.body(body); .body(Some(SegmentedBytes::from(Bytes::from(data)))))
Ok(req)
} }
} }

View File

@ -14,11 +14,12 @@
// limitations under the License. // limitations under the License.
use crate::s3::Client; use crate::s3::Client;
use crate::s3::builders::SegmentedBytes;
use crate::s3::error::Error; use crate::s3::error::Error;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::SetObjectTagsResponse; use crate::s3::response::SetObjectTagsResponse;
use crate::s3::segmented_bytes::SegmentedBytes;
use crate::s3::types::{S3Api, S3Request, ToS3Request}; use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::{Multimap, check_bucket_name}; use crate::s3::utils::{check_bucket_name, check_object_name, insert};
use bytes::Bytes; use bytes::Bytes;
use http::Method; use http::Method;
use std::collections::HashMap; use std::collections::HashMap;
@ -26,29 +27,27 @@ use std::collections::HashMap;
/// Argument builder for [set_object_tags()](Client::set_object_tags) API /// Argument builder for [set_object_tags()](Client::set_object_tags) API
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct SetObjectTags { pub struct SetObjectTags {
pub client: Option<Client>, client: Client,
pub extra_headers: Option<Multimap>, extra_headers: Option<Multimap>,
pub extra_query_params: Option<Multimap>, extra_query_params: Option<Multimap>,
pub region: Option<String>, region: Option<String>,
pub bucket: String, bucket: String,
pub object: String, object: String,
pub version_id: Option<String>, version_id: Option<String>,
pub tags: HashMap<String, String>, tags: HashMap<String, String>,
} }
impl SetObjectTags { impl SetObjectTags {
pub fn new(bucket: &str) -> Self { pub fn new(client: Client, bucket: String, object: String) -> Self {
Self { Self {
bucket: bucket.to_owned(), client,
bucket,
object,
..Default::default() ..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<Multimap>) -> Self { pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers; self.extra_headers = extra_headers;
@ -65,11 +64,6 @@ impl SetObjectTags {
self self
} }
pub fn object(mut self, object: String) -> Self {
self.object = object;
self
}
pub fn version_id(mut self, version_id: Option<String>) -> Self { pub fn version_id(mut self, version_id: Option<String>) -> Self {
self.version_id = version_id; self.version_id = version_id;
self self
@ -86,35 +80,14 @@ impl S3Api for SetObjectTags {
} }
impl ToS3Request for SetObjectTags { impl ToS3Request for SetObjectTags {
fn to_s3request(&self) -> Result<S3Request, Error> { fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?; check_bucket_name(&self.bucket, true)?;
check_object_name(&self.object)?;
// TODO add to all other function (that use object) the following test let mut query_params: Multimap = insert(self.extra_query_params, "tagging");
// TODO should it be moved to the object setter function? or use validate as in put_object query_params.add_version(self.version_id);
if self.object.is_empty() {
return Err(Error::InvalidObjectName(String::from(
"object name cannot be empty",
)));
}
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();
if let Some(v) = &self.version_id {
query_params.insert(String::from("versionId"), v.to_string());
}
query_params.insert("tagging".into(), String::new());
let data: String = {
let mut data = String::from("<Tagging>"); let mut data = String::from("<Tagging>");
if !self.tags.is_empty() { if !self.tags.is_empty() {
data.push_str("<TagSet>"); data.push_str("<TagSet>");
@ -131,18 +104,16 @@ impl ToS3Request for SetObjectTags {
data.push_str("</TagSet>"); data.push_str("</TagSet>");
} }
data.push_str("</Tagging>"); data.push_str("</Tagging>");
data
};
let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(Bytes::from(data))); let body: Option<SegmentedBytes> = Some(SegmentedBytes::from(Bytes::from(data)));
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?;
let req = S3Request::new(client, Method::PUT) Ok(S3Request::new(self.client, Method::PUT)
.region(self.region.as_deref()) .region(self.region)
.bucket(Some(&self.bucket)) .bucket(Some(self.bucket))
.query_params(query_params) .query_params(query_params)
.object(Some(&self.object)) .object(Some(self.object))
.headers(headers) .headers(self.extra_headers.unwrap_or_default())
.body(body); .body(body))
Ok(req)
} }
} }

View File

@ -0,0 +1,162 @@
// MinIO Rust Library for Amazon S3 Compatible Cloud Storage
// Copyright 2023 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 async_trait::async_trait;
use http::Method;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::StatObjectResponse;
use crate::s3::utils::check_object_name;
use crate::s3::{
client::Client,
error::Error,
sse::{Sse, SseCustomerKey},
types::{S3Api, S3Request, ToS3Request},
utils::{UtcTime, check_bucket_name, to_http_header_value},
};
/// Argument builder for [list_objects()](Client::get_object) API.
#[derive(Debug, Clone, Default)]
pub struct StatObject {
client: Client,
extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>,
bucket: String,
object: String,
version_id: Option<String>,
offset: Option<u64>,
length: Option<u64>,
region: Option<String>,
ssec: Option<SseCustomerKey>,
// Conditionals
match_etag: Option<String>,
not_match_etag: Option<String>,
modified_since: Option<UtcTime>,
unmodified_since: Option<UtcTime>,
}
impl StatObject {
pub fn new(client: Client, bucket: String, object: String) -> Self {
Self {
client,
bucket,
object,
..Default::default()
}
}
pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
self.extra_headers = extra_headers;
self
}
pub fn extra_query_params(mut self, extra_query_params: Option<Multimap>) -> Self {
self.extra_query_params = extra_query_params;
self
}
pub fn version_id(mut self, version_id: Option<String>) -> Self {
self.version_id = version_id;
self
}
pub fn offset(mut self, offset: Option<u64>) -> Self {
self.offset = offset;
self
}
pub fn length(mut self, length: Option<u64>) -> Self {
self.length = length;
self
}
pub fn region(mut self, region: Option<String>) -> Self {
self.region = region;
self
}
pub fn ssec(mut self, ssec: Option<SseCustomerKey>) -> Self {
self.ssec = ssec;
self
}
pub fn match_etag(mut self, etag: Option<String>) -> Self {
self.match_etag = etag;
self
}
pub fn not_match_etag(mut self, etag: Option<String>) -> Self {
self.not_match_etag = etag;
self
}
pub fn modified_since(mut self, time: Option<UtcTime>) -> Self {
self.modified_since = time;
self
}
pub fn unmodified_since(mut self, time: Option<UtcTime>) -> Self {
self.unmodified_since = time;
self
}
}
impl S3Api for StatObject {
type S3Response = StatObjectResponse;
}
#[async_trait]
impl ToS3Request for StatObject {
fn to_s3request(self) -> Result<S3Request, Error> {
{
check_bucket_name(&self.bucket, true)?;
check_object_name(&self.object)?;
if self.ssec.is_some() && !self.client.is_secure() {
return Err(Error::SseTlsRequired(None));
}
}
let mut headers: Multimap = self.extra_headers.unwrap_or_default();
{
if let Some(v) = self.match_etag {
headers.add("if-match", v);
}
if let Some(v) = self.not_match_etag {
headers.add("if-none-match", v);
}
if let Some(v) = self.modified_since {
headers.add("if-modified-since", to_http_header_value(v));
}
if let Some(v) = self.unmodified_since {
headers.add("if-unmodified-since", to_http_header_value(v));
}
if let Some(v) = self.ssec {
headers.add_multimap(v.headers());
}
}
let mut query_params: Multimap = self.extra_query_params.unwrap_or_default();
query_params.add_version(self.version_id);
Ok(S3Request::new(self.client, Method::GET)
.region(self.region)
.bucket(Some(self.bucket))
.object(Some(self.object))
.query_params(query_params)
.headers(headers))
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,55 @@
// 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 appending objects.
use super::Client;
use crate::s3::builders::ObjectContent;
use crate::s3::builders::{AppendObject, AppendObjectContent};
use crate::s3::segmented_bytes::SegmentedBytes;
impl Client {
/// Creates an AppendObject request builder to append data to the end of an (existing) object.
/// This is a lower-level API that performs a non-multipart object upload.
///
/// 🛈 This operation is not supported for regular non-express buckets.
pub fn append_object<S: Into<String>>(
&self,
bucket: S,
object: S,
data: SegmentedBytes,
offset_bytes: u64,
) -> AppendObject {
AppendObject::new(
self.clone(),
bucket.into(),
object.into(),
data,
offset_bytes,
)
}
/// Creates an AppendObjectContent request builder to append data to the end of an existing
/// object. The content is streamed and appended to MinIO/S3. This is a higher-level API that
/// handles multipart appends transparently.
pub fn append_object_content<S: Into<String>, C: Into<ObjectContent>>(
&self,
bucket: S,
object: S,
content: C,
) -> AppendObjectContent {
AppendObjectContent::new(self.clone(), bucket.into(), object.into(), content)
}
}

View File

@ -19,8 +19,28 @@ use super::Client;
use crate::s3::builders::BucketExists; use crate::s3::builders::BucketExists;
impl Client { impl Client {
/// Create a BucketExists request builder. /// Creates a [`BucketExists`] request builder.
pub fn bucket_exists(&self, bucket: &str) -> BucketExists { ///
BucketExists::new(bucket).client(self) /// To execute the request, call [`BucketExists::send()`](crate::s3::types::S3Api::send),
/// which returns a [`Result`] containing a [`BucketExistsResponse`](crate::s3::response::BucketExistsResponse).
///
/// # Example
///
/// ```no_run
/// use minio::s3::Client;
/// use minio::s3::response::BucketExistsResponse;
/// use minio::s3::types::S3Api;
///
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
/// let resp: BucketExistsResponse =
/// client.bucket_exists("bucket-name").send().await.unwrap();
/// println!("bucket '{}' exists: {}", resp.bucket, resp.exists);
/// }
/// ```
pub fn bucket_exists<S: Into<String>>(&self, bucket: S) -> BucketExists {
BucketExists::new(self.clone(), bucket.into())
} }
} }

View File

@ -0,0 +1,69 @@
// 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::{
ComposeObject, ComposeObjectInternal, ComposeSource, CopyObject, CopyObjectInternal,
UploadPartCopy,
};
impl Client {
/// Executes [UploadPartCopy](https://docs.aws.amazon.com/AmazonS3/latest/API/API_UploadPartCopy.html) S3 API
pub fn upload_part_copy<S1: Into<String>, S2: Into<String>, S3: Into<String>>(
&self,
bucket: S1,
object: S2,
upload_id: S3,
) -> UploadPartCopy {
UploadPartCopy::new(self.clone(), bucket.into(), object.into(), upload_id.into())
}
/// Create a CopyObject request builder. This is a lower-level API that
/// performs a non-multipart object copy.
pub fn copy_object_internal<S1: Into<String>, S2: Into<String>>(
&self,
bucket: S1,
object: S2,
) -> CopyObjectInternal {
CopyObjectInternal::new(self.clone(), bucket.into(), object.into())
}
/// copy object is a high-order API that calls [`stat_object`] and based on the results calls
/// either [`compose_object`] or [`copy_object_internal`] to copy the object.
pub fn copy_object<S: Into<String>>(&self, bucket: S, object: S) -> CopyObject {
CopyObject::new(self.clone(), bucket.into(), object.into())
}
pub(crate) fn compose_object_internal<S1: Into<String>, S2: Into<String>>(
&self,
bucket: S1,
object: S2,
) -> ComposeObjectInternal {
ComposeObjectInternal::new(self.clone(), bucket.into(), object.into())
}
/// compose object is high-order API that calls [`compose_object_internal`] and if that call fails,
/// it calls ['abort_multipart_upload`].
pub fn compose_object<S1: Into<String>, S2: Into<String>>(
&self,
bucket: S1,
object: S2,
sources: Vec<ComposeSource>,
) -> ComposeObject {
ComposeObject::new(self.clone(), bucket.into(), object.into()).sources(sources)
}
}

View File

@ -19,8 +19,28 @@ use super::Client;
use crate::s3::builders::DeleteBucketEncryption; use crate::s3::builders::DeleteBucketEncryption;
impl Client { impl Client {
/// Create a DeleteBucketEncryption request builder. /// Creates a [`DeleteBucketEncryption`] request builder.
pub fn delete_bucket_encryption(&self, bucket: &str) -> DeleteBucketEncryption { ///
DeleteBucketEncryption::new(bucket).client(self) /// To execute the request, call [`DeleteBucketEncryption::send()`](crate::s3::types::S3Api::send),
/// which returns a [`Result`] containing a [`BucketExistsResponse`](crate::s3::response::BucketExistsResponse).
///
/// # Example
///
/// ```no_run
/// use minio::s3::Client;
/// use minio::s3::response::DeleteBucketEncryptionResponse;
/// use minio::s3::types::S3Api;
///
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
/// let resp: DeleteBucketEncryptionResponse =
/// client.delete_bucket_encryption("bucket-name").send().await.unwrap();
/// println!("bucket '{}' is deleted", resp.bucket);
/// }
/// ```
pub fn delete_bucket_encryption<S: Into<String>>(&self, bucket: S) -> DeleteBucketEncryption {
DeleteBucketEncryption::new(self.clone(), bucket.into())
} }
} }

View File

@ -19,8 +19,28 @@ use super::Client;
use crate::s3::builders::DeleteBucketLifecycle; use crate::s3::builders::DeleteBucketLifecycle;
impl Client { impl Client {
/// Create a DeleteBucketLifecycle request builder. /// Creates a [`DeleteBucketLifecycle`] request builder.
pub fn delete_bucket_lifecycle(&self, bucket: &str) -> DeleteBucketLifecycle { ///
DeleteBucketLifecycle::new(bucket).client(self) /// To execute the request, call [`DeleteBucketLifecycle::send()`](crate::s3::types::S3Api::send),
/// which returns a [`Result`] containing a [`DeleteBucketLifecycleResponse`](crate::s3::response::DeleteBucketLifecycleResponse).
///
/// # Example
///
/// ```no_run
/// use minio::s3::Client;
/// use minio::s3::response::DeleteBucketLifecycleResponse;
/// use minio::s3::types::S3Api;
///
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
/// let resp: DeleteBucketLifecycleResponse =
/// client.delete_bucket_lifecycle("bucket-name").send().await.unwrap();
/// println!("lifecycle of bucket '{}' is deleted", resp.bucket);
/// }
/// ```
pub fn delete_bucket_lifecycle<S: Into<String>>(&self, bucket: S) -> DeleteBucketLifecycle {
DeleteBucketLifecycle::new(self.clone(), bucket.into())
} }
} }

View File

@ -19,8 +19,31 @@ use super::Client;
use crate::s3::builders::DeleteBucketNotification; use crate::s3::builders::DeleteBucketNotification;
impl Client { impl Client {
/// Create a DeleteBucketNotification request builder. /// Creates a [`DeleteBucketNotification`] request builder.
pub fn delete_bucket_notification(&self, bucket: &str) -> DeleteBucketNotification { ///
DeleteBucketNotification::new(bucket).client(self) /// To execute the request, call [`DeleteBucketNotification::send()`](crate::s3::types::S3Api::send),
/// which returns a [`Result`] containing a [`DeleteBucketNotificationResponse`](crate::s3::response::DeleteBucketNotificationResponse).
///
/// # Example
///
/// ```no_run
/// use minio::s3::Client;
/// use minio::s3::response::DeleteBucketNotificationResponse;
/// use minio::s3::types::S3Api;
///
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
/// let resp: DeleteBucketNotificationResponse =
/// client.delete_bucket_notification("bucket-name").send().await.unwrap();
/// println!("notification of bucket '{}' is deleted", resp.bucket);
/// }
/// ```
pub fn delete_bucket_notification<S: Into<String>>(
&self,
bucket: S,
) -> DeleteBucketNotification {
DeleteBucketNotification::new(self.clone(), bucket.into())
} }
} }

View File

@ -20,7 +20,28 @@ use crate::s3::builders::DeleteBucketPolicy;
impl Client { impl Client {
/// Create a DeleteBucketPolicy request builder. /// Create a DeleteBucketPolicy request builder.
pub fn delete_bucket_policy(&self, bucket: &str) -> DeleteBucketPolicy { /// Creates a [`DeleteBucketPolicy`] request builder.
DeleteBucketPolicy::new(bucket).client(self) ///
/// To execute the request, call [`DeleteBucketPolicy::send()`](crate::s3::types::S3Api::send),
/// which returns a [`Result`] containing a [`DeleteBucketPolicyResponse`](crate::s3::response::DeleteBucketPolicyResponse).
///
/// # Example
///
/// ```no_run
/// use minio::s3::Client;
/// use minio::s3::response::DeleteBucketPolicyResponse;
/// use minio::s3::types::S3Api;
///
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
/// let resp: DeleteBucketPolicyResponse =
/// client.delete_bucket_policy("bucket-name").send().await.unwrap();
/// println!("policy of bucket '{}' is deleted", resp.bucket);
/// }
/// ```
pub fn delete_bucket_policy<S: Into<String>>(&self, bucket: S) -> DeleteBucketPolicy {
DeleteBucketPolicy::new(self.clone(), bucket.into())
} }
} }

View File

@ -19,8 +19,30 @@ use super::Client;
use crate::s3::builders::DeleteBucketReplication; use crate::s3::builders::DeleteBucketReplication;
impl Client { impl Client {
/// Create a DeleteBucketReplication request builder. /// Creates a [`DeleteBucketReplication`] request builder.
pub fn delete_bucket_replication(&self, bucket: &str) -> DeleteBucketReplication { ///
DeleteBucketReplication::new(bucket).client(self) /// To execute the request, call [`DeleteBucketReplication::send()`](crate::s3::types::S3Api::send),
/// which returns a [`Result`] containing a [`DeleteBucketReplicationResponse`](crate::s3::response::DeleteBucketReplicationResponse).
///
/// 🛈 This operation is not supported for express buckets.
///
/// # Example
///
/// ```no_run
/// use minio::s3::Client;
/// use minio::s3::response::DeleteBucketReplicationResponse;
/// use minio::s3::types::S3Api;
///
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
/// let resp: DeleteBucketReplicationResponse =
/// client.delete_bucket_replication("bucket-name").send().await.unwrap();
/// println!("replication of bucket '{}' is deleted", resp.bucket);
/// }
/// ```
pub fn delete_bucket_replication<S: Into<String>>(&self, bucket: S) -> DeleteBucketReplication {
DeleteBucketReplication::new(self.clone(), bucket.into())
} }
} }

View File

@ -19,8 +19,30 @@ use super::Client;
use crate::s3::builders::DeleteBucketTags; use crate::s3::builders::DeleteBucketTags;
impl Client { impl Client {
/// Create a DeleteBucketTags request builder. /// Creates a [`DeleteBucketTags`] request builder.
pub fn delete_bucket_tags(&self, bucket: &str) -> DeleteBucketTags { ///
DeleteBucketTags::new(bucket).client(self) /// To execute the request, call [`DeleteBucketTagsResponse::send()`](crate::s3::types::S3Api::send),
/// which returns a [`Result`] containing a [`DeleteBucketTagsResponse`](crate::s3::response::DeleteBucketTagsResponse).
///
/// 🛈 This operation is not supported for express buckets.
///
/// # Example
///
/// ```no_run
/// use minio::s3::Client;
/// use minio::s3::response::DeleteBucketTagsResponse;
/// use minio::s3::types::S3Api;
///
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
/// let resp: DeleteBucketTagsResponse =
/// client.delete_bucket_tags("bucket-name").send().await.unwrap();
/// println!("tags of bucket '{}' are deleted", resp.bucket);
/// }
/// ```
pub fn delete_bucket_tags<S: Into<String>>(&self, bucket: S) -> DeleteBucketTags {
DeleteBucketTags::new(self.clone(), bucket.into())
} }
} }

View File

@ -19,8 +19,43 @@ use super::Client;
use crate::s3::builders::DeleteObjectLockConfig; use crate::s3::builders::DeleteObjectLockConfig;
impl Client { impl Client {
/// Create a DeleteObjectLockConfig request builder. /// Creates a [`DeleteObjectLockConfig`] request builder.
pub fn delete_object_lock_config(&self, bucket: &str) -> DeleteObjectLockConfig { ///
DeleteObjectLockConfig::new(bucket).client(self) /// To execute the request, call [`DeleteObjectLockConfig::send()`](crate::s3::types::S3Api::send),
/// which returns a [`Result`] containing a [`DeleteObjectLockConfigResponse`](crate::s3::response::DeleteObjectLockConfigResponse).
///
/// 🛈 This operation is not supported for express buckets.
///
/// # Example
///
/// ```no_run
/// use minio::s3::Client;
/// use minio::s3::response::{DeleteObjectLockConfigResponse, MakeBucketResponse, SetObjectLockConfigResponse};
/// use minio::s3::types::{S3Api, ObjectLockConfig, RetentionMode};
///
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
/// let bucket_name = "bucket-name";
///
/// let resp: MakeBucketResponse =
/// client.make_bucket(bucket_name).object_lock(true).send().await.unwrap();
/// println!("created bucket '{}' with object locking enabled", resp.bucket);
///
/// const DURATION_DAYS: i32 = 7;
/// let config = ObjectLockConfig::new(RetentionMode::GOVERNANCE, Some(DURATION_DAYS), None).unwrap();
///
/// let resp: SetObjectLockConfigResponse =
/// client.set_object_lock_config(bucket_name).config(config).send().await.unwrap();
/// println!("configured object locking for bucket '{}'", resp.bucket);
///
/// let resp: DeleteObjectLockConfigResponse =
/// client.delete_object_lock_config(bucket_name).send().await.unwrap();
/// println!("object locking of bucket '{}' is deleted", resp.bucket);
/// }
/// ```
pub fn delete_object_lock_config<S: Into<String>>(&self, bucket: S) -> DeleteObjectLockConfig {
DeleteObjectLockConfig::new(self.clone(), bucket.into())
} }
} }

View File

@ -19,8 +19,34 @@ use super::Client;
use crate::s3::builders::DeleteObjectTags; use crate::s3::builders::DeleteObjectTags;
impl Client { impl Client {
/// Create a DeleteObjectTags request builder. /// Creates a [`DeleteObjectTags`] request builder.
pub fn delete_object_tags(&self, bucket: &str) -> DeleteObjectTags { ///
DeleteObjectTags::new(bucket).client(self) /// To execute the request, call [`DeleteObjectTags::send()`](crate::s3::types::S3Api::send),
/// which returns a [`Result`] containing a [`DeleteObjectTagsResponse`](crate::s3::response::DeleteObjectTagsResponse).
///
/// 🛈 This operation is not supported for express buckets.
///
/// # Example
///
/// ```no_run
/// use minio::s3::Client;
/// use minio::s3::response::DeleteObjectTagsResponse;
/// use minio::s3::types::S3Api;
///
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
/// let resp: DeleteObjectTagsResponse =
/// client.delete_object_tags("bucket-name", "object_name").send().await.unwrap();
/// println!("legal hold of object '{}' in bucket '{}' is deleted", resp.object, resp.bucket);
/// }
/// ```
pub fn delete_object_tags<S1: Into<String>, S2: Into<String>>(
&self,
bucket: S1,
object: S2,
) -> DeleteObjectTags {
DeleteObjectTags::new(self.clone(), bucket.into(), object.into())
} }
} }

View File

@ -19,8 +19,34 @@ use super::Client;
use crate::s3::builders::DisableObjectLegalHold; use crate::s3::builders::DisableObjectLegalHold;
impl Client { impl Client {
/// Create a DisableObjectLegalHold request builder. /// Creates a [`DisableObjectLegalHold`] request builder.
pub fn disable_object_legal_hold(&self, bucket: &str) -> DisableObjectLegalHold { ///
DisableObjectLegalHold::new(bucket).client(self) /// To execute the request, call [`DisableObjectLegalHold::send()`](crate::s3::types::S3Api::send),
/// which returns a [`Result`] containing a [`DisableObjectLegalHoldResponse`](crate::s3::response::DisableObjectLegalHoldResponse).
///
/// 🛈 This operation is not supported for express buckets.
///
/// # Example
///
/// ```no_run
/// use minio::s3::Client;
/// use minio::s3::response::DisableObjectLegalHoldResponse;
/// use minio::s3::types::S3Api;
///
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
/// let resp: DisableObjectLegalHoldResponse =
/// client.disable_object_legal_hold("bucket-name", "object-name").send().await.unwrap();
/// println!("legal hold of bucket '{}' is deleted", resp.bucket);
/// }
/// ```
pub fn disable_object_legal_hold<S1: Into<String>, S2: Into<String>>(
&self,
bucket: S1,
object: S2,
) -> DisableObjectLegalHold {
DisableObjectLegalHold::new(self.clone(), bucket.into(), object.into())
} }
} }

View File

@ -19,8 +19,34 @@ use super::Client;
use crate::s3::builders::EnableObjectLegalHold; use crate::s3::builders::EnableObjectLegalHold;
impl Client { impl Client {
/// Create a EnableObjectLegalHold request builder. /// Creates a [`EnableObjectLegalHold`] request builder.
pub fn enable_object_legal_hold(&self, bucket: &str) -> EnableObjectLegalHold { ///
EnableObjectLegalHold::new(bucket).client(self) /// To execute the request, call [`EnableObjectLegalHold::send()`](crate::s3::types::S3Api::send),
/// which returns a [`Result`] containing a [`EnableObjectLegalHoldResponse`](crate::s3::response::EnableObjectLegalHoldResponse).
///
/// 🛈 This operation is not supported for express buckets.
///
/// # Example
///
/// ```no_run
/// use minio::s3::Client;
/// use minio::s3::response::EnableObjectLegalHoldResponse;
/// use minio::s3::types::S3Api;
///
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
/// let resp: EnableObjectLegalHoldResponse =
/// client.enable_object_legal_hold("bucket-name", "object-name").send().await.unwrap();
/// println!("legal hold of object '{}' in bucket '{}' is enabled", resp.object, resp.bucket);
/// }
/// ```
pub fn enable_object_legal_hold<S1: Into<String>, S2: Into<String>>(
&self,
bucket: S1,
object: S2,
) -> EnableObjectLegalHold {
EnableObjectLegalHold::new(self.clone(), bucket.into(), object.into())
} }
} }

View File

@ -19,8 +19,28 @@ use super::Client;
use crate::s3::builders::GetBucketEncryption; use crate::s3::builders::GetBucketEncryption;
impl Client { impl Client {
/// Create a GetBucketEncryption request builder. /// Creates a [`GetBucketEncryption`] request builder.
pub fn get_bucket_encryption(&self, bucket: &str) -> GetBucketEncryption { ///
GetBucketEncryption::new(bucket).client(self) /// To execute the request, call [`GetBucketEncryption::send()`](crate::s3::types::S3Api::send),
/// which returns a [`Result`] containing a [`GetBucketEncryptionResponse`](crate::s3::response::GetBucketEncryptionResponse).
///
/// # Example
///
/// ```no_run
/// use minio::s3::Client;
/// use minio::s3::response::GetBucketEncryptionResponse;
/// use minio::s3::types::S3Api;
///
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
/// let resp: GetBucketEncryptionResponse =
/// client.get_bucket_encryption("bucket-name").send().await.unwrap();
/// println!("retrieved SseConfig '{:?}' from bucket '{}' is enabled", resp.config, resp.bucket);
/// }
/// ```
pub fn get_bucket_encryption<S: Into<String>>(&self, bucket: S) -> GetBucketEncryption {
GetBucketEncryption::new(self.clone(), bucket.into())
} }
} }

View File

@ -20,7 +20,29 @@ use crate::s3::builders::GetBucketLifecycle;
impl Client { impl Client {
/// Create a GetBucketLifecycle request builder. /// Create a GetBucketLifecycle request builder.
pub fn get_bucket_lifecycle(&self, bucket: &str) -> GetBucketLifecycle { ///
GetBucketLifecycle::new(bucket).client(self) /// Creates a [`GetBucketLifecycle`] request builder.
///
/// To execute the request, call [`GetBucketLifecycle::send()`](crate::s3::types::S3Api::send),
/// which returns a [`Result`] containing a [`GetBucketLifecycleResponse`](crate::s3::response::GetBucketLifecycleResponse).
///
/// # Example
///
/// ```no_run
/// use minio::s3::Client;
/// use minio::s3::response::GetBucketLifecycleResponse;
/// use minio::s3::types::S3Api;
///
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
/// let resp: GetBucketLifecycleResponse =
/// client.get_bucket_lifecycle("bucket-name").send().await.unwrap();
/// println!("retrieved bucket lifecycle config '{:?}' from bucket '{}' is enabled", resp.config, resp.bucket);
/// }
/// ```
pub fn get_bucket_lifecycle<S: Into<String>>(&self, bucket: S) -> GetBucketLifecycle {
GetBucketLifecycle::new(self.clone(), bucket.into())
} }
} }

View File

@ -19,8 +19,28 @@ use super::Client;
use crate::s3::builders::GetBucketNotification; use crate::s3::builders::GetBucketNotification;
impl Client { impl Client {
/// Create a GetBucketNotification request builder. /// Creates a [`GetBucketNotification`] request builder.
pub fn get_bucket_notification(&self, bucket: &str) -> GetBucketNotification { ///
GetBucketNotification::new(bucket).client(self) /// To execute the request, call [`GetBucketNotification::send()`](crate::s3::types::S3Api::send),
/// which returns a [`Result`] containing a [`GetBucketNotificationResponse`](crate::s3::response::GetBucketNotificationResponse).
///
/// # Example
///
/// ```no_run
/// use minio::s3::Client;
/// use minio::s3::response::GetBucketNotificationResponse;
/// use minio::s3::types::S3Api;
///
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
/// let resp: GetBucketNotificationResponse =
/// client.get_bucket_notification("bucket-name").send().await.unwrap();
/// println!("retrieved bucket notification config '{:?}' from bucket '{}' is enabled", resp.config, resp.bucket);
/// }
/// ```
pub fn get_bucket_notification<S: Into<String>>(&self, bucket: S) -> GetBucketNotification {
GetBucketNotification::new(self.clone(), bucket.into())
} }
} }

View File

@ -19,8 +19,28 @@ use super::Client;
use crate::s3::builders::GetBucketPolicy; use crate::s3::builders::GetBucketPolicy;
impl Client { impl Client {
/// Create a GetBucketPolicy request builder. /// Creates a [`GetBucketPolicy`] request builder.
pub fn get_bucket_policy(&self, bucket: &str) -> GetBucketPolicy { ///
GetBucketPolicy::new(bucket).client(self) /// To execute the request, call [`GetBucketPolicy::send()`](crate::s3::types::S3Api::send),
/// which returns a [`Result`] containing a [`GetBucketPolicyResponse`](crate::s3::response::GetBucketPolicyResponse).
///
/// # Example
///
/// ```no_run
/// use minio::s3::Client;
/// use minio::s3::response::GetBucketPolicyResponse;
/// use minio::s3::types::S3Api;
///
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
/// let resp: GetBucketPolicyResponse =
/// client.get_bucket_policy("bucket-name").send().await.unwrap();
/// println!("retrieved bucket policy config '{:?}' from bucket '{}' is enabled", resp.config, resp.bucket);
/// }
/// ```
pub fn get_bucket_policy<S: Into<String>>(&self, bucket: S) -> GetBucketPolicy {
GetBucketPolicy::new(self.clone(), bucket.into())
} }
} }

View File

@ -19,8 +19,30 @@ use super::Client;
use crate::s3::builders::GetBucketReplication; use crate::s3::builders::GetBucketReplication;
impl Client { impl Client {
/// Create a GetBucketReplication request builder. /// Creates a [`GetBucketReplication`] request builder.
pub fn get_bucket_replication(&self, bucket: &str) -> GetBucketReplication { ///
GetBucketReplication::new(bucket).client(self) /// To execute the request, call [`GetBucketReplication::send()`](crate::s3::types::S3Api::send),
/// which returns a [`Result`] containing a [`GetBucketReplicationResponse`](crate::s3::response::GetBucketReplicationResponse).
///
/// 🛈 This operation is not supported for express buckets.
///
/// # Example
///
/// ```no_run
/// use minio::s3::Client;
/// use minio::s3::response::GetBucketReplicationResponse;
/// use minio::s3::types::S3Api;
///
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
/// let resp: GetBucketReplicationResponse =
/// client.get_bucket_replication("bucket-name").send().await.unwrap();
/// println!("retrieved bucket replication config '{:?}' from bucket '{}' is enabled", resp.config, resp.bucket);
/// }
/// ```
pub fn get_bucket_replication<S: Into<String>>(&self, bucket: S) -> GetBucketReplication {
GetBucketReplication::new(self.clone(), bucket.into())
} }
} }

View File

@ -19,8 +19,30 @@ use super::Client;
use crate::s3::builders::GetBucketTags; use crate::s3::builders::GetBucketTags;
impl Client { impl Client {
/// Create a GetBucketTags request builder. /// Creates a [`GetBucketTags`] request builder.
pub fn get_bucket_tags(&self, bucket: &str) -> GetBucketTags { ///
GetBucketTags::new(bucket).client(self) /// To execute the request, call [`GetBucketTags::send()`](crate::s3::types::S3Api::send),
/// which returns a [`Result`] containing a [`GetBucketTagsResponse`](crate::s3::response::GetBucketTagsResponse).
///
/// 🛈 This operation is not supported for express buckets.
///
/// # Example
///
/// ```no_run
/// use minio::s3::Client;
/// use minio::s3::response::GetBucketTagsResponse;
/// use minio::s3::types::S3Api;
///
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
/// let resp: GetBucketTagsResponse =
/// client.get_bucket_tags("bucket-name").send().await.unwrap();
/// println!("retrieved bucket tags '{:?}' from bucket '{}' is enabled", resp.tags, resp.bucket);
/// }
/// ```
pub fn get_bucket_tags<S: Into<String>>(&self, bucket: S) -> GetBucketTags {
GetBucketTags::new(self.clone(), bucket.into())
} }
} }

View File

@ -19,8 +19,30 @@ use super::Client;
use crate::s3::builders::GetBucketVersioning; use crate::s3::builders::GetBucketVersioning;
impl Client { impl Client {
/// Create a GetBucketVersioning request builder. /// Creates a [`GetBucketVersioning`] request builder.
pub fn get_bucket_versioning(&self, bucket: &str) -> GetBucketVersioning { ///
GetBucketVersioning::new(bucket).client(self) /// To execute the request, call [`GetBucketVersioning::send()`](crate::s3::types::S3Api::send),
/// which returns a [`Result`] containing a [`GetBucketVersioningResponse`](crate::s3::response::GetBucketVersioningResponse).
///
/// 🛈 This operation is not supported for express buckets.
///
/// # Example
///
/// ```no_run
/// use minio::s3::Client;
/// use minio::s3::response::GetBucketVersioningResponse;
/// use minio::s3::types::S3Api;
///
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
/// let resp: GetBucketVersioningResponse =
/// client.get_bucket_versioning("bucket-name").send().await.unwrap();
/// println!("retrieved versioning status '{:?}' from bucket '{}' is enabled", resp.status, resp.bucket);
/// }
/// ```
pub fn get_bucket_versioning<S: Into<String>>(&self, bucket: S) -> GetBucketVersioning {
GetBucketVersioning::new(self.clone(), bucket.into())
} }
} }

View File

@ -15,13 +15,38 @@
//! S3 APIs for downloading objects. //! S3 APIs for downloading objects.
use super::Client;
use crate::s3::builders::GetObject; use crate::s3::builders::GetObject;
use super::Client;
impl Client { impl Client {
/// Create a GetObject request builder. /// Creates a [`GetObject`] request builder.
pub fn get_object(&self, bucket: &str, object: &str) -> GetObject { ///
GetObject::new(bucket, object).client(self) /// To execute the request, call [`GetObject::send()`](crate::s3::types::S3Api::send),
/// which returns a [`Result`] containing a [`GetObjectResponse`](crate::s3::response::GetObjectResponse).
///
/// # Example
///
/// ```no_run
/// use minio::s3::Client;
/// use minio::s3::response::GetObjectResponse;
/// use minio::s3::types::S3Api;
///
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
/// let resp: GetObjectResponse =
/// client.get_object("bucket-name", "object-name").send().await.unwrap();
/// let content_bytes = resp.content.to_segmented_bytes().await.unwrap().to_bytes();
/// let content_str = String::from_utf8(content_bytes.to_vec()).unwrap();
/// println!("retrieved content '{}'", content_str);
/// }
/// ```
pub fn get_object<S1: Into<String>, S2: Into<String>>(
&self,
bucket: S1,
object: S2,
) -> GetObject {
GetObject::new(self.clone(), bucket.into(), object.into())
} }
} }

Some files were not shown because too many files have changed in this diff Show More