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 build --bins --examples --tests --benches --verbose
- name: Run tests
- name: Run tests S3
run: |
./tests/start-server.sh
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_json = "1.0.140"
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-util = { version = "0.7.14", features = ["io"] }
urlencoding = "2.1.3"
@ -59,7 +59,7 @@ http = "1.3.1"
[dev-dependencies]
minio_common = { path = "./common" }
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"
criterion = "0.5.1"
@ -76,6 +76,9 @@ name = "file_downloader"
[[example]]
name = "object_prompt"
[[example]]
name = "append_object"
[[bench]]
name = "s3-api"
path = "benches/s3/api_benchmarks.rs"

View File

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

View File

@ -23,6 +23,6 @@ pub(crate) fn bench_bucket_exists(criterion: &mut Criterion) {
"bucket_exists",
criterion,
|| 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 },
|ctx| {
let config = create_bucket_lifecycle_config_examples();
SetBucketLifecycle::new(&ctx.bucket)
.client(&ctx.client)
SetBucketLifecycle::new(ctx.client.clone(), ctx.bucket.clone())
.life_cycle_config(config)
},
)
@ -40,15 +39,15 @@ pub(crate) fn bench_get_bucket_lifecycle(criterion: &mut Criterion) {
|| async {
let ctx = Ctx2::new().await;
let config = create_bucket_lifecycle_config_examples();
SetBucketLifecycle::new(&ctx.bucket)
.client(&ctx.client)
ctx.client
.set_bucket_lifecycle(&ctx.bucket)
.life_cycle_config(config)
.send()
.await
.unwrap();
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) {
@ -56,6 +55,6 @@ pub(crate) fn bench_delete_bucket_lifecycle(criterion: &mut Criterion) {
"delete_bucket_lifecycle",
criterion,
|| 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 },
|ctx| {
let config = create_bucket_notification_config_example();
SetBucketNotification::new(&ctx.bucket)
.client(&ctx.client)
SetBucketNotification::new(ctx.client.clone(), ctx.bucket.clone())
.notification_config(config)
},
)
@ -42,15 +41,15 @@ pub(crate) fn bench_get_bucket_notification(criterion: &mut Criterion) {
|| async {
let ctx = Ctx2::new().await;
let config = create_bucket_notification_config_example();
SetBucketNotification::new(&ctx.bucket)
.client(&ctx.client)
ctx.client
.set_bucket_notification(&ctx.bucket)
.notification_config(config)
.send()
.await
.unwrap();
ctx
},
|ctx| GetBucketNotification::new(&ctx.bucket).client(&ctx.client),
|ctx| GetBucketNotification::new(ctx.client.clone(), ctx.bucket.clone()),
)
}
#[allow(dead_code)]
@ -59,6 +58,6 @@ pub(crate) fn bench_delete_bucket_notification(criterion: &mut Criterion) {
"delete_bucket_notification",
criterion,
|| 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 },
|ctx| {
let config = create_bucket_policy_config_example(&ctx.bucket);
SetBucketPolicy::new(&ctx.bucket)
.client(&ctx.client)
.config(config)
SetBucketPolicy::new(ctx.client.clone(), ctx.bucket.clone()).config(config)
},
)
}
@ -40,15 +38,14 @@ pub(crate) fn bench_get_bucket_policy(criterion: &mut Criterion) {
|| async {
let ctx = Ctx2::new().await;
let config = create_bucket_policy_config_example(&ctx.bucket);
SetBucketPolicy::new(&ctx.bucket)
.client(&ctx.client)
SetBucketPolicy::new(ctx.client.clone(), ctx.bucket.clone())
.config(config)
.send()
.await
.unwrap();
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) {
@ -56,6 +53,6 @@ pub(crate) fn bench_delete_bucket_policy(criterion: &mut Criterion) {
"delete_bucket_policy",
criterion,
|| 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| {
let config =
create_bucket_replication_config_example(ctx.aux_bucket.clone().unwrap().as_str());
SetBucketReplication::new(&ctx.bucket)
.client(&ctx.client)
SetBucketReplication::new(ctx.client.clone(), ctx.bucket.clone())
.replication_config(config)
},
)
@ -86,7 +85,7 @@ pub(crate) fn bench_get_bucket_replication(criterion: &mut Criterion) {
ctx
},
|ctx| GetBucketReplication::new(&ctx.bucket).client(&ctx.client),
|ctx| GetBucketReplication::new(ctx.client.clone(), ctx.bucket.clone()),
)
}
#[allow(dead_code)]
@ -116,6 +115,6 @@ pub(crate) fn bench_delete_bucket_replication(criterion: &mut Criterion) {
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
// 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 minio::s3::builders::{DeleteBucketTags, GetBucketTags, SetBucketTags};
use minio::s3::response::SetBucketTagsResponse;
use minio::s3::types::S3Api;
use minio_common::example::create_tags_example;
pub(crate) fn bench_set_bucket_tags(criterion: &mut Criterion) {
if skip_express_mode("bench_set_bucket_tags") {
return;
}
benchmark_s3_api(
"set_bucket_tags",
criterion,
|| async { Ctx2::new().await },
|ctx| {
SetBucketTags::new(&ctx.bucket)
.client(&ctx.client)
.tags(create_tags_example())
SetBucketTags::new(ctx.client.clone(), ctx.bucket.clone()).tags(create_tags_example())
},
)
}
pub(crate) fn bench_get_bucket_tags(criterion: &mut Criterion) {
if skip_express_mode("bench_get_bucket_tags") {
return;
}
benchmark_s3_api(
"get_bucket_tags",
criterion,
|| async {
let ctx = Ctx2::new().await;
let _resp: SetBucketTagsResponse = ctx
.client
ctx.client
.set_bucket_tags(&ctx.bucket)
.tags(create_tags_example())
.send()
@ -48,14 +50,17 @@ pub(crate) fn bench_get_bucket_tags(criterion: &mut Criterion) {
.unwrap();
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) {
if skip_express_mode("bench_delete_bucket_tags") {
return;
}
benchmark_s3_api(
"delete_bucket_tags",
criterion,
|| 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
// 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 minio::s3::builders::{GetBucketVersioning, SetBucketVersioning, VersioningStatus};
pub(crate) fn bench_get_bucket_versioning(criterion: &mut Criterion) {
if skip_express_mode("bench_get_bucket_versioning") {
return;
}
benchmark_s3_api(
"get_bucket_versioning",
criterion,
|| 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) {
if skip_express_mode("bench_set_bucket_versioning") {
return;
}
benchmark_s3_api(
"set_bucket_versioning",
criterion,
|| async { Ctx2::new().await },
|ctx| {
let status = VersioningStatus::Enabled;
SetBucketVersioning::new(&ctx.bucket)
.client(&ctx.client)
.versioning_status(status)
SetBucketVersioning::new(ctx.client.clone(), ctx.bucket.clone())
.versioning_status(VersioningStatus::Enabled)
},
)
}

View File

@ -23,6 +23,6 @@ pub(crate) fn bench_list_buckets(criterion: &mut Criterion) {
"list_buckets",
criterion,
|| 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
// limitations under the License.
#[allow(unused_imports)]
use crate::common_benches::{Ctx2, benchmark_s3_api};
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(_criterion: &mut Criterion) {
/*
pub(crate) fn bench_object_copy_internal(criterion: &mut Criterion) {
benchmark_s3_api(
"object_copy",
"object_copy_internal",
criterion,
|| async { Ctx2::new_with_object(false).await },
|ctx| {
let _object_name_dst = rand_object_name();
//TODO refactor copy object for this to be possible
todo!()
let object_name_src = &ctx.object;
let object_name_dst = rand_object_name();
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
// 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 minio::s3::builders::{
DisableObjectLegalHold, EnableObjectLegalHold, IsObjectLegalHoldEnabled,
};
use minio::s3::types::S3Api;
pub(crate) fn bench_enable_object_legal_hold(criterion: &mut Criterion) {
if skip_express_mode("bench_enable_object_legal_hold") {
return;
}
benchmark_s3_api(
"enable_object_legal_hold",
criterion,
|| async { Ctx2::new_with_object(true).await },
|ctx| {
EnableObjectLegalHold::new(&ctx.bucket)
.client(&ctx.client)
.object(ctx.object.clone())
EnableObjectLegalHold::new(ctx.client.clone(), ctx.bucket.clone(), ctx.object.clone())
},
)
}
pub(crate) fn bench_disable_object_legal_hold(criterion: &mut Criterion) {
if skip_express_mode("bench_disable_object_legal_hold") {
return;
}
benchmark_s3_api(
"disable_object_legal_hold",
criterion,
|| async { Ctx2::new_with_object(true).await },
|ctx| {
DisableObjectLegalHold::new(&ctx.bucket)
.client(&ctx.client)
.object(ctx.object.clone())
DisableObjectLegalHold::new(ctx.client.clone(), ctx.bucket.clone(), ctx.object.clone())
},
)
}
pub(crate) fn bench_is_object_legal_hold(criterion: &mut Criterion) {
if skip_express_mode("bench_is_object_legal_hold") {
return;
}
benchmark_s3_api(
"is_object_legal_hold",
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| {
IsObjectLegalHoldEnabled::new(&ctx.bucket)
.client(&ctx.client)
.object(ctx.object.clone())
IsObjectLegalHoldEnabled::new(
ctx.client.clone(),
ctx.bucket.clone(),
ctx.object.clone(),
)
},
)
}

View File

@ -13,37 +13,44 @@
// See the License for the specific language governing permissions and
// 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 minio::s3::builders::{DeleteObjectLockConfig, GetObjectLockConfig, SetObjectLockConfig};
use minio_common::example::create_object_lock_config_example;
pub(crate) fn bench_set_object_lock_config(criterion: &mut Criterion) {
if skip_express_mode("bench_set_object_lock_config") {
return;
}
benchmark_s3_api(
"set_object_lock_config",
criterion,
|| async { Ctx2::new_with_object(true).await },
|ctx| {
let config = create_object_lock_config_example();
SetObjectLockConfig::new(&ctx.bucket)
.client(&ctx.client)
.config(config)
SetObjectLockConfig::new(ctx.client.clone(), ctx.bucket.clone()).config(config)
},
)
}
pub(crate) fn bench_get_object_lock_config(criterion: &mut Criterion) {
if skip_express_mode("bench_get_object_lock_config") {
return;
}
benchmark_s3_api(
"get_object_lock_config",
criterion,
|| 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) {
if skip_express_mode("bench_delete_object_lock_config") {
return;
}
benchmark_s3_api(
"delete_object_lock_config",
criterion,
|| 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
// 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 minio::s3::builders::{GetObjectRetention, SetObjectRetention};
@ -22,39 +22,38 @@ use minio::s3::types::{RetentionMode, S3Api};
use minio::s3::utils::utc_now;
pub(crate) fn bench_set_object_retention(criterion: &mut Criterion) {
if skip_express_mode("bench_set_object_retention") {
return;
}
benchmark_s3_api(
"set_object_retention",
criterion,
|| async { Ctx2::new_with_object(true).await },
|ctx| {
SetObjectRetention::new(&ctx.bucket)
.client(&ctx.client)
.object(ctx.object.clone())
SetObjectRetention::new(ctx.client.clone(), ctx.bucket.clone(), ctx.object.clone())
.retention_mode(Some(RetentionMode::GOVERNANCE))
.retain_until_date(Some(utc_now() + chrono::Duration::days(1)))
},
)
}
pub(crate) fn bench_get_object_retention(criterion: &mut Criterion) {
if skip_express_mode("bench_get_object_retention") {
return;
}
benchmark_s3_api(
"get_object_retention",
criterion,
|| async {
let ctx = Ctx2::new_with_object(true).await;
let _resp: SetObjectRetentionResponse = SetObjectRetention::new(&ctx.bucket)
.client(&ctx.client)
.object(ctx.object.clone())
.retention_mode(Some(RetentionMode::GOVERNANCE))
.retain_until_date(Some(utc_now() + chrono::Duration::days(1)))
.send()
.await
.unwrap();
let _resp: SetObjectRetentionResponse =
SetObjectRetention::new(ctx.client.clone(), ctx.bucket.clone(), ctx.object.clone())
.retention_mode(Some(RetentionMode::GOVERNANCE))
.retain_until_date(Some(utc_now() + chrono::Duration::days(1)))
.send()
.await
.unwrap();
ctx
},
|ctx| {
GetObjectRetention::new(&ctx.bucket)
.client(&ctx.client)
.object(ctx.object.clone())
},
|ctx| GetObjectRetention::new(ctx.client.clone(), ctx.bucket.clone(), ctx.object.clone()),
)
}

View File

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

View File

@ -17,13 +17,14 @@ use criterion::Criterion;
use minio::s3::Client;
use minio::s3::error::Error;
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::test_context::TestContext;
use minio_common::utils::{
get_bytes_from_response, get_response_from_bytes, rand_bucket_name, rand_object_name,
};
use std::env;
use tokio::runtime::Runtime;
pub(crate) struct Ctx2 {
@ -67,7 +68,7 @@ impl Ctx2 {
.send()
.await
.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 data = bytes::Bytes::from("hello, world".to_string().into_bytes());
let _resp: PutObjectContentResponse = ctx
@ -90,7 +91,7 @@ impl Ctx2 {
pub async fn new_aux(&mut self) -> String {
let bucket_name: String = rand_bucket_name();
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
.client
.make_bucket(&bucket_name)
@ -140,7 +141,7 @@ pub(crate) fn benchmark_s3_api<ApiType, GlobalSetupFuture>(
// Per-iteration setup for initial request
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
let bytes: bytes::Bytes = rt.block_on(async {
@ -165,3 +166,11 @@ pub(crate) fn benchmark_s3_api<ApiType, GlobalSetupFuture>(
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 minio::s3::Client;
use std::thread;
/// Cleanup guard that removes the bucket when it is dropped
@ -25,10 +26,10 @@ pub struct CleanupGuard {
impl CleanupGuard {
#[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 {
client: client.clone(),
bucket_name: bucket_name.to_string(),
client,
bucket_name: bucket_name.into(),
}
}
}
@ -57,7 +58,9 @@ impl Drop for CleanupGuard {
Ok(_) => {
//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),
}

View File

@ -13,11 +13,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use minio::s3::args::PostPolicy;
use chrono::{DateTime, Utc};
use minio::s3::builders::PostPolicy;
use minio::s3::types::{
AndOperator, Destination, Filter, LifecycleConfig, LifecycleRule, NotificationConfig,
ObjectLockConfig, PrefixFilterRule, QueueConfig, ReplicationConfig, ReplicationRule,
RetentionMode, SuffixFilterRule,
AndOperator, CsvInputSerialization, CsvOutputSerialization, Destination, FileHeaderInfo,
Filter, LifecycleConfig, LifecycleRule, NotificationConfig, ObjectLockConfig, PrefixFilterRule,
QueueConfig, QuoteFields, ReplicationConfig, ReplicationRule, RetentionMode, SelectRequest,
SuffixFilterRule,
};
use minio::s3::utils::utc_now;
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()
}
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();
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();
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)]
pub struct TestContext {
pub client: Client,
pub base_url: BaseUrl,
pub access_key: String,
pub secret_key: String,
pub ignore_cert_check: Option<bool>,
pub ssl_cert_file: Option<PathBuf>,
pub client: Client,
}
impl TestContext {
@ -66,12 +66,12 @@ impl TestContext {
.unwrap();
Self {
client,
base_url,
access_key,
secret_key,
ignore_cert_check: Some(ignore_cert_check),
ssl_cert_file: ssl_cert_file.map(PathBuf::from),
client,
}
} else {
const DEFAULT_SERVER_ENDPOINT: &str = "https://play.min.io/";
@ -123,12 +123,12 @@ impl TestContext {
.unwrap();
Self {
client,
base_url,
access_key,
secret_key,
ignore_cert_check: Some(ignore_cert_check),
ssl_cert_file: Some(ssl_cert_file),
client,
}
}
}
@ -144,7 +144,7 @@ impl TestContext {
/// - `CleanupGuard` - A guard that automatically deletes the bucket when dropped.
///
/// # Example
/// ```rust
/// ```ignore
/// let (bucket_name, guard) = client.create_bucket_helper().await;
/// println!("Created temporary bucket: {}", bucket_name);
/// // The bucket will be removed when `guard` is dropped.
@ -152,7 +152,7 @@ impl TestContext {
pub async fn create_bucket_helper(&self) -> (String, CleanupGuard) {
let bucket_name = rand_bucket_name();
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)
}
}

View File

@ -24,7 +24,7 @@ pub fn rand_bucket_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 {

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)
}
#[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(
bucket_name: &str,
client: &Client,

View File

@ -16,10 +16,11 @@
mod common;
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::creds::StaticProvider;
use minio::s3::http::BaseUrl;
use minio::s3::response::ObjectPromptResponse;
use minio::s3::types::S3Api;
use std::path::Path;
@ -65,12 +66,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
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
.client(&client);
.send()
.await?;
let res = op.send().await?;
log::info!("Object prompt result: '{}'", res.prompt_response);
log::info!("Object prompt result: '{}'", resp.prompt_response);
Ok(())
}

View File

@ -17,7 +17,7 @@ use clap::Parser;
use log::info;
use minio::s3::response::BucketExistsResponse;
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;
/// 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,
);
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)))
.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
mod append_object;
mod bucket_common;
mod bucket_exists;
mod copy_object;
mod delete_bucket_encryption;
mod delete_bucket_lifecycle;
mod delete_bucket_notification;
@ -38,16 +40,19 @@ mod get_object;
mod get_object_lock_config;
mod get_object_retention;
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 list_buckets;
mod list_objects;
mod listen_bucket_notification;
mod make_bucket;
mod object_content;
mod object_prompt;
mod put_object;
mod remove_bucket;
mod remove_objects;
mod select_object_content;
mod set_bucket_encryption;
mod set_bucket_lifecycle;
mod set_bucket_notification;
@ -58,9 +63,13 @@ mod set_bucket_versioning;
mod set_object_lock_config;
mod set_object_retention;
mod set_object_tags;
mod stat_object;
pub use crate::s3::object_content::*;
pub use append_object::*;
pub use bucket_common::*;
pub use bucket_exists::*;
pub use copy_object::*;
pub use delete_bucket_encryption::*;
pub use delete_bucket_lifecycle::*;
pub use delete_bucket_notification::*;
@ -82,16 +91,19 @@ pub use get_object::*;
pub use get_object_lock_config::*;
pub use get_object_retention::*;
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 list_buckets::*;
pub use list_objects::*;
pub use listen_bucket_notification::*;
pub use make_bucket::*;
pub use object_content::*;
pub use object_prompt::*;
pub use put_object::*;
pub use remove_bucket::*;
pub use remove_objects::*;
pub use select_object_content::*;
pub use set_bucket_encryption::*;
pub use set_bucket_lifecycle::*;
pub use set_bucket_notification::*;
@ -102,3 +114,4 @@ pub use set_bucket_versioning::*;
pub use set_object_lock_config::*;
pub use set_object_retention::*;
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 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(crate) client: Option<Client>,
pub(crate) client: Client,
pub(crate) extra_headers: Option<Multimap>,
pub(crate) extra_query_params: Option<Multimap>,
@ -30,18 +31,14 @@ pub struct BucketCommon<A> {
}
impl<A: Default> BucketCommon<A> {
pub fn new(bucket: &str) -> BucketCommon<A> {
pub fn new(client: Client, bucket: String) -> BucketCommon<A> {
BucketCommon {
bucket: bucket.to_owned(),
client,
bucket,
..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 {
self.extra_headers = extra_headers;
self

View File

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

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
// limitations under the License.
use crate::s3::Client;
use crate::s3::builders::BucketCommon;
use crate::s3::error::Error;
use crate::s3::response::DeleteBucketEncryptionResponse;
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;
/// Argument builder for [delete_bucket_encryption()](Client::delete_bucket_encryption) API
@ -32,32 +31,13 @@ impl S3Api 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)?;
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 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)
Ok(S3Request::new(self.client, Method::DELETE)
.region(self.region)
.bucket(Some(self.bucket))
.query_params(insert(self.extra_query_params, "encryption"))
.headers(self.extra_headers.unwrap_or_default()))
}
}

View File

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

View File

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

View File

@ -13,12 +13,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::s3::Client;
use crate::s3::builders::BucketCommon;
use crate::s3::error::Error;
use crate::s3::response::DeleteBucketPolicyResponse;
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;
/// Argument builder for [delete_bucket_policy()](Client::delete_bucket_policy) API
@ -32,32 +31,13 @@ impl S3Api 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)?;
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("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)
Ok(S3Request::new(self.client, Method::DELETE)
.region(self.region)
.bucket(Some(self.bucket))
.query_params(insert(self.extra_query_params, "policy"))
.headers(self.extra_headers.unwrap_or_default()))
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -14,41 +14,39 @@
// limitations under the License.
use crate::s3::Client;
use crate::s3::builders::SegmentedBytes;
use crate::s3::error::Error;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::DisableObjectLegalHoldResponse;
use crate::s3::segmented_bytes::SegmentedBytes;
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 http::Method;
/// Argument builder for [disable_object_legal_hold()](Client::disable_object_legal_hold) API
#[derive(Clone, Debug, Default)]
pub struct DisableObjectLegalHold {
pub(crate) client: Option<Client>,
client: Client,
pub(crate) extra_headers: Option<Multimap>,
pub(crate) extra_query_params: Option<Multimap>,
pub(crate) region: Option<String>,
pub(crate) bucket: String,
extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>,
region: Option<String>,
bucket: String,
pub(crate) object: String,
pub(crate) version_id: Option<String>,
object: String,
version_id: Option<String>,
}
impl DisableObjectLegalHold {
pub fn new(bucket: &str) -> Self {
pub fn new(client: Client, bucket: String, object: String) -> Self {
Self {
bucket: bucket.to_owned(),
client,
bucket,
object,
..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 {
self.extra_headers = extra_headers;
self
@ -59,11 +57,6 @@ impl DisableObjectLegalHold {
self
}
pub fn object(mut self, object: String) -> Self {
self.object = object;
self
}
pub fn version_id(mut self, version_id: Option<String>) -> Self {
self.version_id = version_id;
self
@ -75,42 +68,25 @@ impl S3Api 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_object_name(&self.object)?;
let mut headers = self
.extra_headers
.as_ref()
.filter(|v| !v.is_empty())
.cloned()
.unwrap_or_default();
let mut query_params = self
.extra_query_params
.as_ref()
.filter(|v| !v.is_empty())
.cloned()
.unwrap_or_default();
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());
let mut headers: Multimap = self.extra_headers.unwrap_or_default();
let mut query_params: Multimap = insert(self.extra_query_params, "legal-hold");
query_params.add_version(self.version_id);
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)));
//TODO consider const body
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?;
let req = S3Request::new(client, Method::PUT)
.region(self.region.as_deref())
.bucket(Some(&self.bucket))
Ok(S3Request::new(self.client, Method::PUT)
.region(self.region)
.bucket(Some(self.bucket))
.query_params(query_params)
.headers(headers)
.object(Some(&self.object))
.body(body);
Ok(req)
.object(Some(self.object))
.body(body))
}
}

View File

@ -14,41 +14,39 @@
// limitations under the License.
use crate::s3::Client;
use crate::s3::builders::SegmentedBytes;
use crate::s3::error::Error;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::EnableObjectLegalHoldResponse;
use crate::s3::segmented_bytes::SegmentedBytes;
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 http::Method;
/// Argument builder for [enable_object_legal_hold()](Client::enable_object_legal_hold) API
#[derive(Clone, Debug, Default)]
pub struct EnableObjectLegalHold {
pub(crate) client: Option<Client>,
client: Client,
pub(crate) extra_headers: Option<Multimap>,
pub(crate) extra_query_params: Option<Multimap>,
pub(crate) region: Option<String>,
pub(crate) bucket: String,
extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>,
region: Option<String>,
bucket: String,
pub(crate) object: String,
pub(crate) version_id: Option<String>,
object: String,
version_id: Option<String>,
}
impl EnableObjectLegalHold {
pub fn new(bucket: &str) -> Self {
pub fn new(client: Client, bucket: String, object: String) -> Self {
Self {
bucket: bucket.to_owned(),
client,
bucket,
object,
..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 {
self.extra_headers = extra_headers;
self
@ -59,11 +57,6 @@ impl EnableObjectLegalHold {
self
}
pub fn object(mut self, object: String) -> Self {
self.object = object;
self
}
pub fn version_id(mut self, version_id: Option<String>) -> Self {
self.version_id = version_id;
self
@ -75,42 +68,25 @@ impl S3Api 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_object_name(&self.object)?;
let mut headers = self
.extra_headers
.as_ref()
.filter(|v| !v.is_empty())
.cloned()
.unwrap_or_default();
let mut query_params = self
.extra_query_params
.as_ref()
.filter(|v| !v.is_empty())
.cloned()
.unwrap_or_default();
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());
let mut headers: Multimap = self.extra_headers.unwrap_or_default();
let mut query_params: Multimap = insert(self.extra_query_params, "legal-hold");
query_params.add_version(self.version_id);
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)));
//TODO consider const body
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?;
let req = S3Request::new(client, Method::PUT)
.region(self.region.as_deref())
.bucket(Some(&self.bucket))
Ok(S3Request::new(self.client, Method::PUT)
.region(self.region)
.bucket(Some(self.bucket))
.query_params(query_params)
.headers(headers)
.object(Some(&self.object))
.body(body);
Ok(req)
.object(Some(self.object))
.body(body))
}
}

View File

@ -17,7 +17,7 @@ use crate::s3::builders::BucketCommon;
use crate::s3::error::Error;
use crate::s3::response::GetBucketEncryptionResponse;
use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::{Multimap, check_bucket_name, merge};
use crate::s3::utils::{check_bucket_name, insert};
use http::Method;
/// 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 {
fn to_s3request(&self) -> Result<S3Request, Error> {
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);
}
query_params.insert(String::from("encryption"), String::new());
let req = S3Request::new(
self.client.as_ref().ok_or(Error::NoClientProvided)?,
Method::GET,
)
.region(self.region.as_deref())
.bucket(Some(&self.bucket))
.query_params(query_params)
.headers(headers);
Ok(req)
Ok(S3Request::new(self.client, Method::GET)
.region(self.region)
.bucket(Some(self.bucket))
.query_params(insert(self.extra_query_params, "encryption"))
.headers(self.extra_headers.unwrap_or_default()))
}
}

View File

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

View File

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

View File

@ -13,12 +13,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::s3::Client;
use crate::s3::builders::BucketCommon;
use crate::s3::error::Error;
use crate::s3::response::GetBucketPolicyResponse;
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;
/// Argument builder for [get_bucket_policy()](Client::get_bucket_policy) API
@ -32,32 +31,13 @@ impl S3Api 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)?;
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("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)
Ok(S3Request::new(self.client, Method::GET)
.region(self.region)
.bucket(Some(self.bucket))
.query_params(insert(self.extra_query_params, "policy"))
.headers(self.extra_headers.unwrap_or_default()))
}
}

View File

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

View File

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

View File

@ -17,7 +17,7 @@ use crate::s3::builders::BucketCommon;
use crate::s3::error::Error;
use crate::s3::response::GetBucketVersioningResponse;
use crate::s3::types::{S3Api, S3Request, ToS3Request};
use crate::s3::utils::{Multimap, check_bucket_name, merge};
use crate::s3::utils::{check_bucket_name, insert};
use http::Method;
/// 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 {
fn to_s3request(&self) -> Result<S3Request, Error> {
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);
}
query_params.insert(String::from("versioning"), String::new());
let req = S3Request::new(
self.client.as_ref().ok_or(Error::NoClientProvided)?,
Method::GET,
)
.region(self.region.as_deref())
.bucket(Some(&self.bucket))
.query_params(query_params)
.headers(headers);
Ok(req)
Ok(S3Request::new(self.client, Method::GET)
.region(self.region)
.bucket(Some(self.bucket))
.query_params(insert(self.extra_query_params, "versioning"))
.headers(self.extra_headers.unwrap_or_default()))
}
}

View File

@ -15,19 +15,21 @@
use http::Method;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::utils::check_object_name;
use crate::s3::{
client::Client,
error::Error,
response::GetObjectResponse,
sse::{Sse, SseCustomerKey},
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.
#[derive(Debug, Clone, Default)]
pub struct GetObject {
client: Option<Client>,
client: Client,
extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>,
@ -46,21 +48,16 @@ pub struct GetObject {
unmodified_since: Option<UtcTime>,
}
// builder interface
impl GetObject {
pub fn new(bucket: &str, object: &str) -> Self {
pub fn new(client: Client, bucket: String, object: String) -> Self {
Self {
bucket: bucket.to_string(),
object: object.to_string(),
client,
bucket,
object,
..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 {
self.extra_headers = extra_headers;
self
@ -117,99 +114,69 @@ impl GetObject {
}
}
// internal helpers
impl GetObject {
fn get_range_header_value(&self) -> Option<String> {
let (offset, length) = match self.length {
Some(_) => (Some(self.offset.unwrap_or(0_u64)), self.length),
None => (self.offset, None),
};
if let Some(o) = offset {
let mut range = String::new();
range.push_str("bytes=");
range.push_str(&o.to_string());
range.push('-');
if let Some(l) = length {
range.push_str(&(o + l - 1).to_string());
}
Some(range)
} else {
None
}
}
fn get_headers(&self) -> Multimap {
let mut headers = Multimap::new();
if let Some(val) = self.get_range_header_value() {
headers.insert(String::from("Range"), val);
}
if let Some(v) = &self.match_etag {
headers.insert(String::from("if-match"), v.to_string());
}
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 {
headers.insert(String::from("if-modified-since"), to_http_header_value(v));
}
if let Some(v) = self.unmodified_since {
headers.insert(String::from("if-unmodified-since"), to_http_header_value(v));
}
if let Some(v) = &self.ssec {
merge(&mut headers, &v.headers());
}
headers
}
}
impl ToS3Request for GetObject {
fn to_s3request(&self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?;
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)
.headers(headers);
Ok(req)
}
}
impl S3Api for GetObject {
type S3Response = GetObjectResponse;
}
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),
None => (self.offset, None),
};
if let Some(o) = offset {
let mut range: String = String::new();
range.push_str("bytes=");
range.push_str(&o.to_string());
range.push('-');
if let Some(l) = length {
range.push_str(&(o + l - 1).to_string());
}
headers.add("Range", range);
}
}
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))
}
}

View File

@ -13,12 +13,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::s3::Client;
use crate::s3::builders::BucketCommon;
use crate::s3::error::Error;
use crate::s3::response::GetObjectLockConfigResponse;
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;
/// 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 {
fn to_s3request(&self) -> Result<S3Request, Error> {
fn to_s3request(self) -> Result<S3Request, Error> {
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 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)
Ok(S3Request::new(self.client, Method::GET)
.region(self.region)
.bucket(Some(self.bucket))
.query_params(insert(self.extra_query_params, "object-lock"))
.headers(self.extra_headers.unwrap_or_default()))
}
}

View File

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

View File

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

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

View File

@ -15,32 +15,29 @@
use http::Method;
use crate::s3::multimap::Multimap;
use crate::s3::response::ListBucketsResponse;
use crate::s3::{
Client,
error::Error,
types::{S3Api, S3Request, ToS3Request},
utils::Multimap,
};
/// Argument builder for [list_buckets()](Client::list_buckets) API.
#[derive(Clone, Debug, Default)]
pub struct ListBuckets {
client: Option<Client>,
client: Client,
extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>,
}
// builder interface
impl ListBuckets {
pub fn new() -> Self {
Default::default()
}
pub fn client(mut self, client: &Client) -> Self {
self.client = Some(client.clone());
self
pub fn new(client: Client) -> Self {
Self {
client,
..Default::default()
}
}
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 {
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 http::Method;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::utils::insert;
use crate::s3::{
client::Client,
error::Error,
@ -24,34 +26,41 @@ use crate::s3::{
ListObjectVersionsResponse, ListObjectsV1Response, ListObjectsV2Response,
},
types::{S3Api, S3Request, ToS3Request, ToStream},
utils::{Multimap, check_bucket_name, merge},
utils::check_bucket_name,
};
fn add_common_list_objects_query_params(
query_params: &mut Multimap,
delimiter: Option<&str>,
delimiter: Option<String>,
disable_url_encoding: bool,
max_keys: Option<u16>,
prefix: Option<&str>,
prefix: Option<String>,
) {
query_params.insert(
String::from("delimiter"),
delimiter.unwrap_or("").to_string(),
);
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());
query_params.add("delimiter", delimiter.unwrap_or("".into()));
query_params.add("max-keys", max_keys.unwrap_or(1000).to_string());
query_params.add("prefix", prefix.unwrap_or("".into()));
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.
#[derive(Clone, Debug, Default)]
struct ListObjectsV1 {
client: Option<Client>,
client: Client,
extra_headers: 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> {
Box::new(Box::pin(futures_stream::unfold(
(self.clone(), false),
move |(mut args, mut is_done)| async move {
(self, false),
move |(args, mut is_done)| async move {
// Stop the stream if no more data is available
if is_done {
return None;
}
let resp = args.send().await;
match resp {
// Prepare a clone of `args` for the next iteration
let mut args_for_next_request: ListObjectsV1 = args.clone();
// Handle the result of the API call
match args.send().await {
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;
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 {
type S3Response = ListObjectsV1Response;
}
// 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("/")),
impl ToS3Request for ListObjectsV1 {
fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?;
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);
}
}
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.
#[derive(Clone, Debug, Default)]
struct ListObjectsV2 {
client: Option<Client>,
client: Client,
extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>,
@ -177,6 +174,7 @@ struct ListObjectsV2 {
continuation_token: Option<String>,
fetch_owner: bool,
include_user_metadata: bool,
unsorted: bool,
}
#[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> {
Box::new(Box::pin(futures_stream::unfold(
(self.clone(), false),
move |(mut args, mut is_done)| async move {
(self, false),
move |(args, mut is_done)| async move {
// Stop the stream if no more data is available
if is_done {
return None;
}
let resp = args.send().await;
match resp {
// Prepare a clone of `args` for the next iteration
let mut args_for_next_request = args.clone();
match args.send().await {
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);
// Determine if there are more results to fetch
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 {
fn to_s3request(&self) -> Result<S3Request, Error> {
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 = self.extra_query_params.unwrap_or_default();
{
query_params.add("list-type", "2");
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.continuation_token {
query_params.add("continuation-token", v);
}
if self.fetch_owner {
query_params.add("fetch-owner", "true");
}
if let Some(v) = self.start_after {
query_params.add("start-after", v);
}
if self.include_user_metadata {
query_params.add("metadata", "true");
}
if self.unsorted {
query_params.add("unsorted", "true");
}
}
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(
&mut query_params,
self.delimiter.as_deref(),
self.disable_url_encoding,
self.max_keys,
self.prefix.as_deref(),
);
if let Some(v) = &self.continuation_token {
query_params.insert(String::from("continuation-token"), v.to_string());
}
if self.fetch_owner {
query_params.insert(String::from("fetch-owner"), String::from("true"));
}
if let Some(v) = &self.start_after {
query_params.insert(String::from("start-after"), v.to_string());
}
if self.include_user_metadata {
query_params.insert(String::from("metadata"), String::from("true"));
}
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)
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()))
}
}
@ -271,14 +270,18 @@ impl From<ListObjects> for ListObjectsV2 {
continuation_token: value.continuation_token,
fetch_owner: value.fetch_owner,
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)]
struct ListObjectVersions {
client: Option<Client>,
client: Client,
extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>,
@ -291,6 +294,7 @@ struct ListObjectVersions {
key_marker: Option<String>,
version_id_marker: Option<String>,
include_user_metadata: bool,
unsorted: bool,
}
#[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> {
Box::new(Box::pin(futures_stream::unfold(
(self.clone(), false),
move |(mut args, mut is_done)| async move {
(self, false),
move |(args, mut is_done)| async move {
// Stop the stream if no more data is available
if is_done {
return None;
}
let resp = args.send().await;
match resp {
// Prepare a clone of `args` for the next iteration
let mut args_for_next_request = args.clone();
match args.send().await {
Ok(resp) => {
args.key_marker.clone_from(&resp.next_key_marker);
args.version_id_marker
// Update the key_marker for the next request
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);
// Determine if there are more results to fetch
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 {
fn to_s3request(&self) -> Result<S3Request, Error> {
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 = insert(self.extra_query_params, "versions");
{
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.key_marker {
query_params.add("key-marker", v);
}
if let Some(v) = self.version_id_marker {
query_params.add("version-id-marker", v);
}
if self.include_user_metadata {
query_params.add("metadata", "true");
}
if self.unsorted {
query_params.add("unsorted", "true");
}
}
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(
&mut query_params,
self.delimiter.as_deref(),
self.disable_url_encoding,
self.max_keys,
self.prefix.as_deref(),
);
if let Some(v) = &self.key_marker {
query_params.insert(String::from("key-marker"), v.to_string());
}
if let Some(v) = &self.version_id_marker {
query_params.insert(String::from("version-id-marker"), v.to_string());
}
if self.include_user_metadata {
query_params.insert(String::from("metadata"), String::from("true"));
}
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)
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()))
}
}
impl From<ListObjects> for ListObjectVersions {
fn from(value: ListObjects) -> Self {
ListObjectVersions {
Self {
client: value.client,
extra_headers: value.extra_headers,
extra_query_params: value.extra_query_params,
@ -383,10 +389,15 @@ impl From<ListObjects> for ListObjectVersions {
key_marker: value.key_marker,
version_id_marker: value.version_id_marker,
include_user_metadata: value.include_user_metadata,
unsorted: value.unsorted,
}
}
}
// endregion: list-object-versions
// region: list-objects
/// Argument builder for
/// [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.
#[derive(Clone, Debug, Default)]
pub struct ListObjects {
client: Option<Client>,
client: Client,
// Parameters common to all ListObjects APIs.
extra_headers: Option<Multimap>,
@ -424,6 +435,7 @@ pub struct ListObjects {
recursive: bool,
use_api_v1: bool,
include_versions: bool,
unsorted: bool,
}
#[async_trait]
@ -445,18 +457,13 @@ impl ToStream for ListObjects {
}
impl ListObjects {
pub fn new(bucket: &str) -> Self {
pub fn new(client: Client, bucket: String) -> Self {
Self {
bucket: bucket.to_owned(),
client,
bucket,
..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 {
self.extra_headers = extra_headers;
self
@ -556,4 +563,11 @@ impl ListObjects {
self.include_versions = include_versions;
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 http::Method;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::{
client::Client,
error::Error,
response::ListenBucketNotificationResponse,
types::{NotificationRecords, S3Api, S3Request, ToS3Request},
utils::{Multimap, check_bucket_name, merge},
utils::check_bucket_name,
};
/// Argument builder for
@ -30,7 +31,7 @@ use crate::s3::{
/// API.
#[derive(Clone, Debug, Default)]
pub struct ListenBucketNotification {
client: Option<Client>,
client: Client,
extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>,
@ -41,72 +42,15 @@ pub struct ListenBucketNotification {
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 {
pub fn new(bucket_name: &str) -> ListenBucketNotification {
ListenBucketNotification {
bucket: bucket_name.to_owned(),
pub fn new(client: Client, bucket: String) -> Self {
Self {
client,
bucket,
..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 {
self.extra_headers = extra_headers;
self
@ -137,3 +81,47 @@ impl ListenBucketNotification {
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.
use crate::s3::Client;
use crate::s3::builders::SegmentedBytes;
use crate::s3::client::DEFAULT_REGION;
use crate::s3::error::Error;
use crate::s3::http::BaseUrl;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::MakeBucketResponse;
use crate::s3::segmented_bytes::SegmentedBytes;
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;
/// Argument builder for [make_bucket()](Client::make_bucket) API
#[derive(Clone, Debug, Default)]
pub struct MakeBucket {
pub client: Option<Client>,
client: Client,
pub extra_headers: Option<Multimap>,
pub extra_query_params: Option<Multimap>,
pub region: Option<String>,
pub bucket: String,
extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>,
region: Option<String>,
bucket: String,
pub object_lock: bool,
object_lock: bool,
}
impl MakeBucket {
pub fn new(bucket: &str) -> Self {
pub fn new(client: Client, bucket: String) -> Self {
Self {
bucket: bucket.to_owned(),
client,
bucket,
..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 {
self.extra_headers = extra_headers;
self
@ -70,64 +66,37 @@ impl MakeBucket {
}
}
#[derive(Default, Debug)]
pub struct MakeBucketPhantomData;
impl S3Api for MakeBucket {
type S3Response = MakeBucketResponse;
}
impl ToS3Request for MakeBucket {
fn to_s3request(&self) -> Result<S3Request, Error> {
fn to_s3request(self) -> Result<S3Request, Error> {
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 region2: Option<&str> = if base_url.region.is_empty() {
None
} else {
Some(base_url.region.as_str())
};
let region2: Option<&str> = self.client.get_region_from_url();
let region: &str = match (region1, region2) {
(None, None) => DEFAULT_REGION,
(Some(r), None) | (None, Some(r)) => r, // Take the non-None value
(Some(r1), Some(r2)) if r1 == r2 => r1, // Both are Some and equal
let region_str: String = match (region1, region2) {
(None, None) => DEFAULT_REGION.to_string(),
(Some(_), None) => self.region.unwrap(),
(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)) => {
return Err(Error::RegionMismatch(r1.to_string(), r2.to_string()));
}
};
let mut headers: Multimap = self
.extra_headers
.as_ref()
.filter(|v| !v.is_empty())
.cloned()
.unwrap_or_default();
let mut headers: Multimap = self.extra_headers.unwrap_or_default();
if self.object_lock {
headers.insert(
String::from("x-amz-bucket-object-lock-enabled"),
String::from("true"),
);
headers.add("x-amz-bucket-object-lock-enabled", "true");
}
let query_params: Multimap = self
.extra_query_params
.as_ref()
.filter(|v| !v.is_empty())
.cloned()
.unwrap_or_default();
let data: String = match region {
let data: String = match region_str.as_str() {
DEFAULT_REGION => String::new(),
_ => format!(
"<CreateBucketConfiguration><LocationConstraint>{}</LocationConstraint></CreateBucketConfiguration>",
region
region_str
),
};
@ -136,15 +105,11 @@ impl ToS3Request for MakeBucket {
false => Some(SegmentedBytes::from(data)),
};
let client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?;
let req = S3Request::new(client, Method::PUT)
.region(Some(region))
.bucket(Some(&self.bucket))
.query_params(query_params)
Ok(S3Request::new(self.client, Method::PUT)
.region(Some(region_str))
.bucket(Some(self.bucket))
.query_params(self.extra_query_params.unwrap_or_default())
.headers(headers)
.body(body);
Ok(req)
.body(body))
}
}

View File

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

View File

@ -15,26 +15,30 @@
//! Builders for RemoveObject APIs.
use std::pin::Pin;
use async_trait::async_trait;
use bytes::Bytes;
use futures_util::{Stream, StreamExt, stream as futures_stream};
use http::Method;
use std::pin::Pin;
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::{
Client,
client_core::ClientCore,
error::Error,
response::{RemoveObjectResponse, RemoveObjectsResponse},
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
/// key and version_id via the From trait.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Default)]
pub struct ObjectToDelete {
key: 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.
impl From<&str> for ObjectToDelete {
fn from(key: &str) -> Self {
ObjectToDelete {
key: key.to_string(),
Self {
key: key.to_owned(),
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.
impl From<(&str, &str)> for ObjectToDelete {
fn from((key, version_id): (&str, &str)) -> Self {
ObjectToDelete {
Self {
key: key.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.
impl From<(&str, Option<&str>)> for ObjectToDelete {
fn from((key, version_id): (&str, Option<&str>)) -> Self {
ObjectToDelete {
Self {
key: key.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 {
client: Option<Client>,
bucket: String,
object: ObjectToDelete,
bypass_governance_mode: bool,
client: Client,
extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>,
region: Option<String>,
bucket: String,
object: ObjectToDelete,
bypass_governance_mode: bool,
}
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 {
client: None,
bucket: bucket.to_string(),
client,
bucket,
object: object.into(),
bypass_governance_mode: false,
extra_headers: None,
extra_query_params: None,
region: None,
..Default::default()
}
}
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 {
self.bypass_governance_mode = bypass_governance_mode;
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 {
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 {
client: Option<ClientCore>,
client: Client,
bucket: String,
objects: Vec<ObjectToDelete>,
@ -176,27 +180,16 @@ pub struct 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 {
client: None,
bucket: bucket.to_string(),
client,
bucket,
objects,
bypass_governance_mode: false,
verbose_mode: false,
extra_headers: None,
extra_query_params: None,
region: None,
..Default::default()
}
}
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 {
self.bypass_governance_mode = bypass_governance_mode;
self
@ -226,8 +219,12 @@ impl RemoveObjectsApi {
}
}
impl S3Api for RemoveObjectsApi {
type S3Response = RemoveObjectsResponse;
}
impl ToS3Request for RemoveObjectsApi {
fn to_s3request(&self) -> Result<S3Request, Error> {
fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?;
let mut data = String::from("<Delete>");
@ -249,56 +246,40 @@ impl ToS3Request for RemoveObjectsApi {
data.push_str("</Delete>");
let data: Bytes = data.into();
let mut headers = Multimap::new();
if let Some(v) = &self.extra_headers {
merge(&mut headers, v);
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-Type", "application/xml");
headers.add("Content-MD5", md5sum_hash(data.as_ref()));
}
if self.bypass_governance_mode {
headers.insert(
String::from("x-amz-bypass-governance-retention"),
String::from("true"),
);
}
headers.insert(
String::from("Content-Type"),
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();
let req = S3Request::new(client, Method::POST)
.region(self.region.as_deref())
.bucket(Some(&self.bucket))
.query_params(query_params)
Ok(S3Request::new(self.client, Method::POST)
.region(self.region)
.bucket(Some(self.bucket))
.query_params(insert(self.extra_query_params, "delete"))
.headers(headers)
.body(Some(data.into()));
Ok(req)
.body(Some(data.into())))
}
}
impl S3Api for RemoveObjectsApi {
type S3Response = RemoveObjectsResponse;
}
// endregion: remove-object-api
// region: delete-object
pub struct DeleteObjects {
items: Pin<Box<dyn Stream<Item = ObjectToDelete> + Send + Sync>>,
}
impl DeleteObjects {
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 {
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,
{
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 {
client: Option<Client>,
client: Client,
bucket: String,
objects: DeleteObjects,
@ -326,11 +311,10 @@ pub struct RemoveObjects {
}
impl RemoveObjects {
pub fn new(bucket: &str, objects: impl Into<DeleteObjects>) -> Self {
RemoveObjects {
client: None,
bucket: bucket.to_string(),
pub fn new(client: Client, bucket: String, objects: impl Into<DeleteObjects>) -> Self {
Self {
client,
bucket,
objects: objects.into(),
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 {
self.bypass_governance_mode = bypass_governance_mode;
self
@ -386,15 +365,15 @@ impl RemoveObjects {
if objects.is_empty() {
return Ok(None);
}
let client_core = ClientCore::new(self.client.as_ref().ok_or(Error::NoClientProvided)?);
let request = RemoveObjectsApi::new(&self.bucket, objects)
.client(&client_core)
.bypass_governance_mode(self.bypass_governance_mode)
.verbose_mode(self.verbose_mode)
.extra_headers(self.extra_headers.clone())
.extra_query_params(self.extra_query_params.clone())
.region(self.region.clone());
Ok(Some(request))
Ok(Some(
RemoveObjectsApi::new(self.client.clone(), self.bucket.clone(), objects)
.bypass_governance_mode(self.bypass_governance_mode)
.verbose_mode(self.verbose_mode)
.extra_headers(self.extra_headers.clone())
.extra_query_params(self.extra_query_params.clone())
.region(self.region.clone()),
))
}
}
@ -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.
use crate::s3::Client;
use crate::s3::builders::SegmentedBytes;
use crate::s3::error::Error;
use crate::s3::multimap::Multimap;
use crate::s3::response::SetBucketEncryptionResponse;
use crate::s3::segmented_bytes::SegmentedBytes;
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 http::Method;
/// Argument builder for [set_bucket_encryption()](Client::set_bucket_encryption) API
#[derive(Clone, Debug, Default)]
pub struct SetBucketEncryption {
client: Option<Client>,
client: Client,
extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>,
@ -36,18 +37,14 @@ pub struct SetBucketEncryption {
}
impl SetBucketEncryption {
pub fn new(bucket: &str) -> Self {
pub fn new(client: Client, bucket: String) -> Self {
Self {
bucket: bucket.to_owned(),
client,
bucket,
..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 {
self.extra_headers = extra_headers;
self
@ -74,35 +71,17 @@ impl S3Api 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)?;
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 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)
.region(self.region.as_deref())
.bucket(Some(&self.bucket))
.query_params(query_params)
.headers(headers)
.body(body);
Ok(req)
Ok(S3Request::new(self.client, Method::GET)
.region(self.region)
.bucket(Some(self.bucket))
.query_params(insert(self.extra_query_params, "encryption"))
.headers(self.extra_headers.unwrap_or_default())
.body(body))
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -14,11 +14,12 @@
// limitations under the License.
use crate::s3::Client;
use crate::s3::builders::SegmentedBytes;
use crate::s3::error::Error;
use crate::s3::multimap::Multimap;
use crate::s3::response::SetBucketTagsResponse;
use crate::s3::segmented_bytes::SegmentedBytes;
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 http::Method;
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
#[derive(Clone, Debug, Default)]
pub struct SetBucketTags {
pub(crate) client: Option<Client>,
client: Client,
pub(crate) extra_headers: Option<Multimap>,
pub(crate) extra_query_params: Option<Multimap>,
pub(crate) region: Option<String>,
pub(crate) bucket: String,
extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>,
region: Option<String>,
bucket: String,
pub(crate) tags: HashMap<String, String>,
tags: HashMap<String, String>,
}
impl SetBucketTags {
pub fn new(bucket: &str) -> Self {
pub fn new(client: Client, bucket: String) -> Self {
Self {
bucket: bucket.to_owned(),
client,
bucket,
..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 {
self.extra_headers = extra_headers;
self
@ -75,51 +72,35 @@ impl S3Api 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)?;
let headers = self
.extra_headers
.as_ref()
.filter(|v| !v.is_empty())
.cloned()
.unwrap_or_default();
let mut query_params = self
.extra_query_params
.as_ref()
.filter(|v| !v.is_empty())
.cloned()
.unwrap_or_default();
query_params.insert(String::from("tagging"), String::new());
let mut data = String::from("<Tagging>");
if !self.tags.is_empty() {
data.push_str("<TagSet>");
for (key, value) in self.tags.iter() {
data.push_str("<Tag>");
data.push_str("<Key>");
data.push_str(key);
data.push_str("</Key>");
data.push_str("<Value>");
data.push_str(value);
data.push_str("</Value>");
data.push_str("</Tag>");
let data: String = {
let mut data = String::from("<Tagging>");
if !self.tags.is_empty() {
data.push_str("<TagSet>");
for (key, value) in self.tags.iter() {
data.push_str("<Tag>");
data.push_str("<Key>");
data.push_str(key);
data.push_str("</Key>");
data.push_str("<Value>");
data.push_str(value);
data.push_str("</Value>");
data.push_str("</Tag>");
}
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 client: &Client = self.client.as_ref().ok_or(Error::NoClientProvided)?;
let req = S3Request::new(client, Method::PUT)
.region(self.region.as_deref())
.bucket(Some(&self.bucket))
.query_params(query_params)
.headers(headers)
.body(body);
Ok(req)
Ok(S3Request::new(self.client, Method::PUT)
.region(self.region)
.bucket(Some(self.bucket))
.query_params(insert(self.extra_query_params, "tagging"))
.headers(self.extra_headers.unwrap_or_default())
.body(body))
}
}

View File

@ -14,11 +14,12 @@
// limitations under the License.
use crate::s3::Client;
use crate::s3::builders::SegmentedBytes;
use crate::s3::error::Error;
use crate::s3::multimap::Multimap;
use crate::s3::response::SetBucketVersioningResponse;
use crate::s3::segmented_bytes::SegmentedBytes;
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 http::Method;
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
#[derive(Clone, Debug, Default)]
pub struct SetBucketVersioning {
pub(crate) client: Option<Client>,
client: Client,
pub(crate) extra_headers: Option<Multimap>,
pub(crate) extra_query_params: Option<Multimap>,
pub(crate) region: Option<String>,
pub(crate) bucket: String,
extra_headers: Option<Multimap>,
extra_query_params: Option<Multimap>,
region: Option<String>,
bucket: String,
pub(crate) status: Option<VersioningStatus>,
pub(crate) mfa_delete: Option<bool>,
status: Option<VersioningStatus>,
mfa_delete: Option<bool>,
}
impl SetBucketVersioning {
pub fn new(bucket: &str) -> Self {
pub fn new(client: Client, bucket: String) -> Self {
Self {
bucket: bucket.to_owned(),
client,
bucket,
..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 {
self.extra_headers = extra_headers;
self
@ -98,54 +95,38 @@ impl S3Api 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)?;
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();
let data: String = {
let mut data = "<VersioningConfiguration>".to_string();
query_params.insert("versioning".into(), String::new());
let mut data = "<VersioningConfiguration>".to_string();
if let Some(v) = self.mfa_delete {
data.push_str("<MFADelete>");
data.push_str(if v { "Enabled" } else { "Disabled" });
data.push_str("</MFADelete>");
}
match self.status {
Some(VersioningStatus::Enabled) => data.push_str("<Status>Enabled</Status>"),
Some(VersioningStatus::Suspended) => data.push_str("<Status>Suspended</Status>"),
None => {
return Err(Error::InvalidVersioningStatus(
"Missing VersioningStatus".into(),
));
if let Some(v) = self.mfa_delete {
data.push_str("<MFADelete>");
data.push_str(if v { "Enabled" } else { "Disabled" });
data.push_str("</MFADelete>");
}
match self.status {
Some(VersioningStatus::Enabled) => data.push_str("<Status>Enabled</Status>"),
Some(VersioningStatus::Suspended) => data.push_str("<Status>Suspended</Status>"),
None => {
return Err(Error::InvalidVersioningStatus(
"Missing VersioningStatus".into(),
));
}
};
data.push_str("</VersioningConfiguration>");
data
};
data.push_str("</VersioningConfiguration>");
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)
.region(self.region.as_deref())
.bucket(Some(&self.bucket))
.query_params(query_params)
.headers(headers)
.body(body);
Ok(req)
Ok(S3Request::new(self.client, Method::PUT)
.region(self.region)
.bucket(Some(self.bucket))
.query_params(insert(self.extra_query_params, "versioning"))
.headers(self.extra_headers.unwrap_or_default())
.body(body))
}
}

View File

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

View File

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

View File

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

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;
impl Client {
/// Create a BucketExists request builder.
pub fn bucket_exists(&self, bucket: &str) -> BucketExists {
BucketExists::new(bucket).client(self)
/// Creates a [`BucketExists`] request builder.
///
/// 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;
impl Client {
/// Create a DeleteBucketEncryption request builder.
pub fn delete_bucket_encryption(&self, bucket: &str) -> DeleteBucketEncryption {
DeleteBucketEncryption::new(bucket).client(self)
/// Creates a [`DeleteBucketEncryption`] request builder.
///
/// 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;
impl Client {
/// Create a DeleteBucketLifecycle request builder.
pub fn delete_bucket_lifecycle(&self, bucket: &str) -> DeleteBucketLifecycle {
DeleteBucketLifecycle::new(bucket).client(self)
/// Creates a [`DeleteBucketLifecycle`] request builder.
///
/// 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;
impl Client {
/// Create a DeleteBucketNotification request builder.
pub fn delete_bucket_notification(&self, bucket: &str) -> DeleteBucketNotification {
DeleteBucketNotification::new(bucket).client(self)
/// Creates a [`DeleteBucketNotification`] request builder.
///
/// 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 {
/// Create a DeleteBucketPolicy request builder.
pub fn delete_bucket_policy(&self, bucket: &str) -> DeleteBucketPolicy {
DeleteBucketPolicy::new(bucket).client(self)
/// Creates a [`DeleteBucketPolicy`] request builder.
///
/// 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;
impl Client {
/// Create a DeleteBucketReplication request builder.
pub fn delete_bucket_replication(&self, bucket: &str) -> DeleteBucketReplication {
DeleteBucketReplication::new(bucket).client(self)
/// Creates a [`DeleteBucketReplication`] request builder.
///
/// 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;
impl Client {
/// Create a DeleteBucketTags request builder.
pub fn delete_bucket_tags(&self, bucket: &str) -> DeleteBucketTags {
DeleteBucketTags::new(bucket).client(self)
/// Creates a [`DeleteBucketTags`] request builder.
///
/// 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;
impl Client {
/// Create a DeleteObjectLockConfig request builder.
pub fn delete_object_lock_config(&self, bucket: &str) -> DeleteObjectLockConfig {
DeleteObjectLockConfig::new(bucket).client(self)
/// Creates a [`DeleteObjectLockConfig`] request builder.
///
/// 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;
impl Client {
/// Create a DeleteObjectTags request builder.
pub fn delete_object_tags(&self, bucket: &str) -> DeleteObjectTags {
DeleteObjectTags::new(bucket).client(self)
/// Creates a [`DeleteObjectTags`] request builder.
///
/// 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;
impl Client {
/// Create a DisableObjectLegalHold request builder.
pub fn disable_object_legal_hold(&self, bucket: &str) -> DisableObjectLegalHold {
DisableObjectLegalHold::new(bucket).client(self)
/// Creates a [`DisableObjectLegalHold`] request builder.
///
/// 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;
impl Client {
/// Create a EnableObjectLegalHold request builder.
pub fn enable_object_legal_hold(&self, bucket: &str) -> EnableObjectLegalHold {
EnableObjectLegalHold::new(bucket).client(self)
/// Creates a [`EnableObjectLegalHold`] request builder.
///
/// 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;
impl Client {
/// Create a GetBucketEncryption request builder.
pub fn get_bucket_encryption(&self, bucket: &str) -> GetBucketEncryption {
GetBucketEncryption::new(bucket).client(self)
/// Creates a [`GetBucketEncryption`] request builder.
///
/// 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 {
/// 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;
impl Client {
/// Create a GetBucketNotification request builder.
pub fn get_bucket_notification(&self, bucket: &str) -> GetBucketNotification {
GetBucketNotification::new(bucket).client(self)
/// Creates a [`GetBucketNotification`] request builder.
///
/// 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;
impl Client {
/// Create a GetBucketPolicy request builder.
pub fn get_bucket_policy(&self, bucket: &str) -> GetBucketPolicy {
GetBucketPolicy::new(bucket).client(self)
/// Creates a [`GetBucketPolicy`] request builder.
///
/// 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;
impl Client {
/// Create a GetBucketReplication request builder.
pub fn get_bucket_replication(&self, bucket: &str) -> GetBucketReplication {
GetBucketReplication::new(bucket).client(self)
/// Creates a [`GetBucketReplication`] request builder.
///
/// 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;
impl Client {
/// Create a GetBucketTags request builder.
pub fn get_bucket_tags(&self, bucket: &str) -> GetBucketTags {
GetBucketTags::new(bucket).client(self)
/// Creates a [`GetBucketTags`] request builder.
///
/// 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;
impl Client {
/// Create a GetBucketVersioning request builder.
pub fn get_bucket_versioning(&self, bucket: &str) -> GetBucketVersioning {
GetBucketVersioning::new(bucket).client(self)
/// Creates a [`GetBucketVersioning`] request builder.
///
/// 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.
use super::Client;
use crate::s3::builders::GetObject;
use super::Client;
impl Client {
/// Create a GetObject request builder.
pub fn get_object(&self, bucket: &str, object: &str) -> GetObject {
GetObject::new(bucket, object).client(self)
/// Creates a [`GetObject`] request builder.
///
/// 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