Duplicated code removed, and lazy response evaluation (#162)

* Duplicated code removed, and lazy response evaluation

* moved Tokio runtime from general dependency to dev dependency
This commit is contained in:
Henk-Jan Lebbink 2025-06-18 18:55:53 +02:00 committed by GitHub
parent 6f904b452a
commit 720943b4bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
132 changed files with 1937 additions and 2579 deletions

View File

@ -7,6 +7,7 @@ on:
branches: [ "master" ]
env:
RUST_LOG: debug
CARGO_TERM_COLOR: always
jobs:

View File

@ -43,7 +43,7 @@ pub(crate) async fn bench_object_append(criterion: &mut Criterion) {
})
.unwrap();
let offset_bytes: u64 = resp.size;
let offset_bytes: u64 = resp.size().unwrap();
AppendObject::new(
ctx.client.clone(),
ctx.bucket.clone(),

View File

@ -17,6 +17,7 @@ mod common;
use crate::common::{create_bucket_if_not_exists, create_client_on_localhost};
use minio::s3::Client;
use minio::s3::response::a_response_traits::HasObjectSize;
use minio::s3::response::{AppendObjectResponse, StatObjectResponse};
use minio::s3::segmented_bytes::SegmentedBytes;
use minio::s3::types::S3Api;
@ -54,19 +55,21 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
.await?;
offset_bytes += data_size;
if resp.object_size != offset_bytes {
if resp.object_size() != offset_bytes {
panic!(
"from the append_object: size mismatch: expected {}, got {}",
resp.object_size, offset_bytes
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 {
if resp.size()? != offset_bytes {
panic!(
"from the stat_Object: size mismatch: expected {}, got {}",
resp.size, offset_bytes
resp.size()?,
offset_bytes
)
}
println!("{}/{}", i, n_segments);

View File

@ -30,7 +30,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let resp: GetBucketEncryptionResponse =
client.get_bucket_encryption(bucket_name).send().await?;
log::info!("encryption before: config={:?}", resp.config);
log::info!("encryption before: config={:?}", resp.config());
let config = SseConfig::default();
log::info!("going to set encryption config={:?}", config);
@ -43,7 +43,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let resp: GetBucketEncryptionResponse =
client.get_bucket_encryption(bucket_name).send().await?;
log::info!("encryption after: config={:?}", resp.config);
log::info!("encryption after: config={:?}", resp.config());
Ok(())
}

View File

@ -33,8 +33,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
client.get_bucket_versioning(bucket_name).send().await?;
log::info!(
"versioning before: status={:?}, mfa_delete={:?}",
resp.status,
resp.mfa_delete
resp.status(),
resp.mfa_delete()
);
let _resp: PutBucketVersioningResponse = client
@ -48,8 +48,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
log::info!(
"versioning after setting to Enabled: status={:?}, mfa_delete={:?}",
resp.status,
resp.mfa_delete
resp.status(),
resp.mfa_delete()
);
let _resp: PutBucketVersioningResponse = client
@ -63,8 +63,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
log::info!(
"versioning after setting to Suspended: status={:?}, mfa_delete={:?}",
resp.status,
resp.mfa_delete
resp.status(),
resp.mfa_delete()
);
let _resp: PutBucketVersioningResponse = client
@ -78,8 +78,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
log::info!(
"versioning after setting to None: status={:?}, mfa_delete={:?}",
resp.status,
resp.mfa_delete
resp.status(),
resp.mfa_delete()
);
Ok(())

View File

@ -42,7 +42,7 @@ pub async fn create_bucket_if_not_exists(
let resp: BucketExistsResponse = client.bucket_exists(bucket_name).send().await?;
// Make 'bucket_name' bucket if not exist.
if !resp.exists {
if !resp.exists() {
client.create_bucket(bucket_name).send().await.unwrap();
};
Ok(())

View File

@ -55,7 +55,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let get_object = client.get_object(bucket_name, object_name).send().await?;
get_object.content.to_file(Path::new(download_path)).await?;
get_object
.content()?
.to_file(Path::new(download_path))
.await?;
log::info!("Object '{object_name}' is successfully downloaded to file '{download_path}'.");

View File

@ -72,7 +72,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
.send()
.await?;
log::info!("Object prompt result: '{}'", resp.prompt_response);
log::info!("Object prompt result: '{}'", resp.prompt_response()?);
Ok(())
}

View File

@ -47,7 +47,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let resp: BucketExistsResponse = client.bucket_exists(&args.bucket).send().await.unwrap();
if !resp.exists {
if !resp.exists() {
client.create_bucket(&args.bucket).send().await.unwrap();
}

View File

@ -40,7 +40,7 @@
//! .await
//! .expect("request failed");
//!
//! println!("Bucket exists: {}", exists.exists);
//! println!("Bucket exists: {}", exists.exists());
//! }
//! ```
//!

View File

@ -19,6 +19,7 @@ use crate::s3::builders::{
};
use crate::s3::error::Error;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::a_response_traits::HasObjectSize;
use crate::s3::response::{AppendObjectResponse, StatObjectResponse};
use crate::s3::segmented_bytes::SegmentedBytes;
use crate::s3::sse::Sse;
@ -26,7 +27,6 @@ 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
/// Argument builder for the [`AppendObject`](https://docs.aws.amazon.com/AmazonS3/latest/userguide/directory-buckets-objects-append.html) S3 API operation.
@ -234,7 +234,7 @@ impl AppendObjectContent {
.await?;
//println!("statObjectResponse={:#?}", resp);
let current_file_size = resp.size;
let current_file_size = resp.size()?;
// In the first part read, if:
//
@ -322,7 +322,7 @@ impl AppendObjectContent {
let resp: AppendObjectResponse = append_object.send().await?;
//println!("AppendObjectResponse: object_size={:?}", resp.object_size);
next_offset_bytes = resp.object_size;
next_offset_bytes = resp.object_size();
// Finally check if we are done.
if buffer_size < part_size {

View File

@ -17,9 +17,11 @@ use crate::s3::Client;
use crate::s3::client::{MAX_MULTIPART_COUNT, MAX_PART_SIZE};
use crate::s3::error::Error;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::a_response_traits::HasEtagFromBody;
use crate::s3::response::{
AbortMultipartUploadResponse, ComposeObjectResponse, CopyObjectInternalResponse,
CopyObjectResponse, CreateMultipartUploadResponse, StatObjectResponse, UploadPartCopyResponse,
AbortMultipartUploadResponse, CompleteMultipartUploadResponse, ComposeObjectResponse,
CopyObjectInternalResponse, CopyObjectResponse, CreateMultipartUploadResponse,
StatObjectResponse, UploadPartCopyResponse,
};
use crate::s3::sse::{Sse, SseCustomerKey};
use crate::s3::types::{Directive, PartInfo, Retention, S3Api, S3Request, ToS3Request};
@ -445,7 +447,7 @@ impl CopyObject {
if self.source.offset.is_some()
|| self.source.length.is_some()
|| stat_resp.size > MAX_PART_SIZE
|| stat_resp.size()? > MAX_PART_SIZE
{
if let Some(v) = &self.metadata_directive {
match v {
@ -499,14 +501,8 @@ impl CopyObject {
.send()
.await?;
Ok(CopyObjectResponse {
headers: resp.headers,
bucket: resp.bucket,
object: resp.object,
region: resp.region,
etag: resp.etag,
version_id: resp.version_id,
})
let resp: CopyObjectResponse = resp; // retype to CopyObjectResponse
Ok(resp)
} else {
let resp: CopyObjectInternalResponse = self
.client
@ -526,14 +522,8 @@ impl CopyObject {
.send()
.await?;
Ok(CopyObjectResponse {
headers: resp.headers,
bucket: resp.bucket,
object: resp.object,
region: resp.region,
etag: resp.etag,
version_id: resp.version_id,
})
let resp: CopyObjectResponse = resp; // retype to CopyObjectResponse
Ok(resp)
}
}
}
@ -652,17 +642,9 @@ impl ComposeObjectInternal {
Err(e) => return (Err(e), upload_id),
};
(
Ok(ComposeObjectResponse {
headers: resp.headers,
bucket: resp.bucket,
object: resp.object,
region: resp.region,
etag: resp.etag,
version_id: resp.version_id,
}),
upload_id,
)
let resp: ComposeObjectResponse = resp; // retype to ComposeObjectResponse
(Ok(resp), upload_id)
} else {
let headers: Multimap = into_headers_copy_object(
self.extra_headers,
@ -687,7 +669,11 @@ impl ComposeObjectInternal {
};
// the multipart upload was successful: update the upload_id
upload_id.push_str(&cmu.upload_id);
let upload_id_cmu: String = match cmu.upload_id().await {
Ok(v) => v,
Err(e) => return (Err(e), upload_id),
};
upload_id.push_str(&upload_id_cmu);
let mut part_number = 0_u16;
let ssec_headers: Multimap = match self.sse {
@ -739,9 +725,14 @@ impl ComposeObjectInternal {
Err(e) => return (Err(e), upload_id),
};
let etag = match resp.etag() {
Ok(v) => v,
Err(e) => return (Err(e), upload_id),
};
parts.push(PartInfo {
number: part_number,
etag: resp.etag,
etag,
size,
});
} else {
@ -773,9 +764,14 @@ impl ComposeObjectInternal {
Err(e) => return (Err(e), upload_id),
};
let etag = match resp.etag() {
Ok(v) => v,
Err(e) => return (Err(e), upload_id),
};
parts.push(PartInfo {
number: part_number,
etag: resp.etag,
etag,
size,
});
@ -785,7 +781,7 @@ impl ComposeObjectInternal {
}
}
let resp = self
let resp: Result<CompleteMultipartUploadResponse, Error> = self
.client
.complete_multipart_upload(&self.bucket, &self.object, &upload_id, parts)
.region(self.region)
@ -793,17 +789,14 @@ impl ComposeObjectInternal {
.await;
match resp {
Ok(v) => (
Ok(ComposeObjectResponse {
Ok(v) => {
let resp = ComposeObjectResponse {
request: v.request,
headers: v.headers,
bucket: v.bucket,
object: v.object,
region: v.region,
etag: v.etag,
version_id: v.version_id,
}),
upload_id,
),
body: v.body,
};
(Ok(resp), upload_id)
}
Err(e) => (Err(e), upload_id),
}
}

View File

@ -241,7 +241,7 @@ impl ToS3Request for DeleteObjects {
fn to_s3request(self) -> Result<S3Request, Error> {
check_bucket_name(&self.bucket, true)?;
let mut data = String::from("<Delete>");
let mut data: String = String::from("<Delete>");
if !self.verbose_mode {
data.push_str("<Quiet>true</Quiet>");
}

View File

@ -15,6 +15,7 @@
use super::ObjectContent;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::a_response_traits::HasEtagFromHeaders;
use crate::s3::segmented_bytes::SegmentedBytes;
use crate::s3::utils::{check_object_name, insert};
use crate::s3::{
@ -665,7 +666,7 @@ impl PutObjectContent {
{
let size = seg_bytes.len() as u64;
let res: PutObjectResponse = PutObject(UploadPart {
let resp: PutObjectResponse = PutObject(UploadPart {
client: self.client.clone(),
extra_headers: self.extra_headers.clone(),
extra_query_params: self.extra_query_params.clone(),
@ -685,15 +686,7 @@ impl PutObjectContent {
.send()
.await?;
Ok(PutObjectContentResponse {
headers: res.headers,
bucket: res.bucket,
object: res.object,
region: res.region,
object_size: size,
etag: res.etag,
version_id: res.version_id,
})
Ok(PutObjectContentResponse::new(resp, size))
} else if object_size.is_known() && (seg_bytes.len() as u64) < part_size {
// Not enough data!
let expected: u64 = object_size.as_u64().unwrap();
@ -722,19 +715,15 @@ impl PutObjectContent {
.await?;
let client = self.client.clone();
let upload_id: String = create_mpu_resp.upload_id().await?;
let mpu_res = self
.send_mpu(
part_size,
create_mpu_resp.upload_id.clone(),
object_size,
seg_bytes,
)
.send_mpu(part_size, upload_id.clone(), object_size, seg_bytes)
.await;
if mpu_res.is_err() {
// If we failed to complete the multipart upload, we should abort it.
let _ =
AbortMultipartUpload::new(client, bucket, object, create_mpu_resp.upload_id)
let _ = AbortMultipartUpload::new(client, bucket, object, upload_id)
.send()
.await;
}
@ -815,7 +804,7 @@ impl PutObjectContent {
parts.push(PartInfo {
number: part_number,
etag: resp.etag,
etag: resp.etag()?,
size: buffer_size,
});
@ -835,7 +824,7 @@ impl PutObjectContent {
}
}
let res: CompleteMultipartUploadResponse = CompleteMultipartUpload {
let resp: CompleteMultipartUploadResponse = CompleteMultipartUpload {
client: self.client,
extra_headers: self.extra_headers,
extra_query_params: self.extra_query_params,
@ -848,15 +837,7 @@ impl PutObjectContent {
.send()
.await?;
Ok(PutObjectContentResponse {
headers: res.headers,
bucket: res.bucket,
object: res.object,
region: res.region,
object_size: size,
etag: res.etag,
version_id: res.version_id,
})
Ok(PutObjectContentResponse::new(resp, size))
}
}

View File

@ -21,16 +21,17 @@ use std::mem;
use std::path::{Path, PathBuf};
use std::sync::{Arc, OnceLock};
use crate::s3::builders::{BucketExists, ComposeSource};
use crate::s3::creds::Provider;
use crate::s3::error::{Error, ErrorCode, ErrorResponse};
use crate::s3::http::BaseUrl;
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::response::a_response_traits::{HasEtagFromHeaders, HasS3Fields};
use crate::s3::response::*;
use crate::s3::segmented_bytes::SegmentedBytes;
use crate::s3::signer::sign_v4_s3;
use crate::s3::utils::{EMPTY_SHA256, sha256_hash_sb, to_amz_date, utc_now};
use crate::s3::builders::{BucketExists, ComposeSource};
use crate::s3::multimap::{Multimap, MultimapExt};
use crate::s3::segmented_bytes::SegmentedBytes;
use bytes::Bytes;
use dashmap::DashMap;
use http::HeaderMap;
@ -280,7 +281,7 @@ impl Client {
let express = match BucketExists::new(self.clone(), bucket_name).send().await {
Ok(v) => {
if let Some(server) = v.headers.get("server") {
if let Some(server) = v.headers().get("server") {
if let Ok(s) = server.to_str() {
s.eq_ignore_ascii_case("MinIO Enterprise/S3Express")
} else {
@ -300,7 +301,6 @@ impl Client {
express
}
}
/// Add a bucket-region pair to the region cache if it does not exist.
pub(crate) fn add_bucket_region(&mut self, bucket: &str, region: impl Into<String>) {
self.shared
@ -360,9 +360,9 @@ impl Client {
.send()
.await?;
source.build_headers(stat_resp.size, stat_resp.etag)?;
let mut size = stat_resp.size()?;
source.build_headers(size, stat_resp.etag()?)?;
let mut size = stat_resp.size;
if let Some(l) = source.length {
size = l;
} else if let Some(o) = source.offset {
@ -493,15 +493,12 @@ impl Client {
// Sort headers alphabetically by name
header_strings.sort();
let body_str: String =
String::from_utf8(body.unwrap_or(&SegmentedBytes::new()).to_bytes().to_vec())?;
println!(
"S3 request: {} url={:?}; headers={:?}; body={}\n",
method,
url.path,
header_strings.join("; "),
body_str
body.unwrap()
);
}

View File

@ -36,6 +36,7 @@ impl Client {
/// use minio::s3::response::{AppendObjectResponse, PutObjectResponse};
/// use minio::s3::segmented_bytes::SegmentedBytes;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasObjectSize;
///
/// #[tokio::main]
/// async fn main() {
@ -49,7 +50,7 @@ impl Client {
/// let resp: AppendObjectResponse = client
/// .append_object("bucket-name", "object-name", data2, offset_bytes)
/// .send().await.unwrap();
/// println!("size of the final object is {} bytes", resp.object_size);
/// println!("size of the final object is {} bytes", resp.object_size());
/// }
/// ```
pub fn append_object<S1: Into<String>, S2: Into<String>>(
@ -85,6 +86,7 @@ impl Client {
/// use minio::s3::builders::ObjectContent;
/// use minio::s3::segmented_bytes::SegmentedBytes;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasObjectSize;
///
/// #[tokio::main]
/// async fn main() {
@ -97,7 +99,7 @@ impl Client {
/// let resp: AppendObjectResponse = client
/// .append_object_content("bucket-name", "object-name", content2)
/// .send().await.unwrap();
/// println!("size of the final object is {} bytes", resp.object_size);
/// println!("size of the final object is {} bytes", resp.object_size());
/// }
/// ```
pub fn append_object_content<S1: Into<String>, S2: Into<String>, C: Into<ObjectContent>>(

View File

@ -28,6 +28,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::BucketExistsResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
@ -35,7 +36,7 @@ impl Client {
/// let resp: BucketExistsResponse = client
/// .bucket_exists("bucket-name")
/// .send().await.unwrap();
/// println!("bucket '{}' exists: {}", resp.bucket, resp.exists);
/// println!("bucket '{}' exists: {}", resp.bucket(), resp.exists());
/// }
/// ```
pub fn bucket_exists<S: Into<String>>(&self, bucket: S) -> BucketExists {

View File

@ -33,6 +33,7 @@ impl Client {
/// use minio::s3::response::{UploadPartCopyResponse};
/// use minio::s3::segmented_bytes::SegmentedBytes;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasObject;
///
/// #[tokio::main]
/// async fn main() {
@ -42,7 +43,7 @@ impl Client {
/// let resp: UploadPartCopyResponse = client
/// .upload_part_copy("bucket-name", "object-name", "TODO")
/// .send().await.unwrap();
/// println!("uploaded {}", resp.object);
/// println!("uploaded {}", resp.object());
/// }
/// ```
pub fn upload_part_copy<S1: Into<String>, S2: Into<String>, S3: Into<String>>(

View File

@ -28,6 +28,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::CreateBucketResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::{HasBucket, HasRegion};
///
/// #[tokio::main]
/// async fn main() {
@ -35,7 +36,7 @@ impl Client {
/// let resp: CreateBucketResponse = client
/// .create_bucket("bucket-name")
/// .send().await.unwrap();
/// println!("Made bucket '{}' in region '{}'", resp.bucket, resp.region);
/// println!("Made bucket '{}' in region '{}'", resp.bucket(), resp.region());
/// }
/// ```
pub fn create_bucket<S: Into<String>>(&self, bucket: S) -> CreateBucket {

View File

@ -21,6 +21,7 @@ use crate::s3::response::{
DeleteBucketResponse, DeleteObjectResponse, DeleteObjectsResponse, PutObjectLegalHoldResponse,
};
use crate::s3::types::{S3Api, ToStream};
use bytes::Bytes;
use futures::StreamExt;
impl Client {
@ -35,13 +36,14 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::DeleteBucketResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::{HasBucket, HasRegion};
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
/// let resp: DeleteBucketResponse =
/// client.delete_bucket("bucket-name").send().await.unwrap();
/// println!("bucket '{}' in region '{}' is removed", resp.bucket, resp.region);
/// println!("bucket '{}' in region '{}' is removed", resp.bucket(), resp.region());
/// }
/// ```
pub fn delete_bucket<S: Into<String>>(&self, bucket: S) -> DeleteBucket {
@ -90,7 +92,7 @@ impl Client {
while let Some(item) = resp.next().await {
let resp: DeleteObjectsResponse = item?;
for obj in resp.result.into_iter() {
for obj in resp.result()?.into_iter() {
match obj {
DeleteResult::Deleted(_) => {}
DeleteResult::Error(v) => {
@ -115,14 +117,15 @@ impl Client {
}
}
}
match self.delete_bucket(bucket).send().await {
let request: DeleteBucket = self.delete_bucket(bucket);
match request.send().await {
Ok(resp) => Ok(resp),
Err(Error::S3Error(e)) => {
if e.code == ErrorCode::NoSuchBucket {
Ok(DeleteBucketResponse {
request: Default::default(), //TODO consider how to handle this
body: Bytes::new(),
headers: e.headers,
bucket: e.bucket_name,
region: String::new(),
})
} else {
Err(Error::S3Error(e))

View File

@ -28,6 +28,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::DeleteBucketEncryptionResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
@ -35,7 +36,7 @@ impl Client {
/// let resp: DeleteBucketEncryptionResponse = client
/// .delete_bucket_encryption("bucket-name")
/// .send().await.unwrap();
/// println!("bucket '{}' is deleted", resp.bucket);
/// println!("bucket '{}' is deleted", resp.bucket());
/// }
/// ```
pub fn delete_bucket_encryption<S: Into<String>>(&self, bucket: S) -> DeleteBucketEncryption {

View File

@ -28,6 +28,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::DeleteBucketLifecycleResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
@ -35,7 +36,7 @@ impl Client {
/// let resp: DeleteBucketLifecycleResponse = client
/// .delete_bucket_lifecycle("bucket-name")
/// .send().await.unwrap();
/// println!("lifecycle of bucket '{}' is deleted", resp.bucket);
/// println!("lifecycle of bucket '{}' is deleted", resp.bucket());
/// }
/// ```
pub fn delete_bucket_lifecycle<S: Into<String>>(&self, bucket: S) -> DeleteBucketLifecycle {

View File

@ -28,6 +28,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::DeleteBucketNotificationResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
@ -35,7 +36,7 @@ impl Client {
/// let resp: DeleteBucketNotificationResponse = client
/// .delete_bucket_notification("bucket-name")
/// .send().await.unwrap();
/// println!("notification of bucket '{}' is deleted", resp.bucket);
/// println!("notification of bucket '{}' is deleted", resp.bucket());
/// }
/// ```
pub fn delete_bucket_notification<S: Into<String>>(

View File

@ -28,6 +28,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::DeleteBucketPolicyResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
@ -35,7 +36,7 @@ impl Client {
/// let resp: DeleteBucketPolicyResponse = client
/// .delete_bucket_policy("bucket-name")
/// .send().await.unwrap();
/// println!("policy of bucket '{}' is deleted", resp.bucket);
/// println!("policy of bucket '{}' is deleted", resp.bucket());
/// }
/// ```
pub fn delete_bucket_policy<S: Into<String>>(&self, bucket: S) -> DeleteBucketPolicy {

View File

@ -30,6 +30,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::DeleteBucketReplicationResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
@ -37,7 +38,7 @@ impl Client {
/// let resp: DeleteBucketReplicationResponse = client
/// .delete_bucket_replication("bucket-name")
/// .send().await.unwrap();
/// println!("replication of bucket '{}' is deleted", resp.bucket);
/// println!("replication of bucket '{}' is deleted", resp.bucket());
/// }
/// ```
pub fn delete_bucket_replication<S: Into<String>>(&self, bucket: S) -> DeleteBucketReplication {

View File

@ -30,6 +30,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::DeleteBucketTaggingResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
@ -37,7 +38,7 @@ impl Client {
/// let resp: DeleteBucketTaggingResponse = client
/// .delete_bucket_tagging("bucket-name")
/// .send().await.unwrap();
/// println!("tags of bucket '{}' are deleted", resp.bucket);
/// println!("tags of bucket '{}' are deleted", resp.bucket());
/// }
/// ```
pub fn delete_bucket_tagging<S: Into<String>>(&self, bucket: S) -> DeleteBucketTagging {

View File

@ -30,6 +30,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::{DeleteObjectLockConfigResponse, CreateBucketResponse, PutObjectLockConfigResponse};
/// use minio::s3::types::{S3Api, ObjectLockConfig, RetentionMode};
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
@ -38,18 +39,19 @@ impl Client {
///
/// let resp: CreateBucketResponse =
/// client.create_bucket(bucket_name).object_lock(true).send().await.unwrap();
/// println!("created bucket '{}' with object locking enabled", resp.bucket);
/// 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: PutObjectLockConfigResponse =
/// client.put_object_lock_config(bucket_name).config(config).send().await.unwrap();
/// println!("configured object locking for bucket '{}'", resp.bucket);
/// 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);
/// println!("object locking of bucket '{}' is deleted", resp.bucket());
/// }
/// ```
pub fn delete_object_lock_config<S: Into<String>>(&self, bucket: S) -> DeleteObjectLockConfig {

View File

@ -30,6 +30,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::DeleteObjectTaggingResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::{HasBucket, HasObject};
///
/// #[tokio::main]
/// async fn main() {
@ -37,7 +38,7 @@ impl Client {
/// let resp: DeleteObjectTaggingResponse = client
/// .delete_object_tagging("bucket-name", "object_name")
/// .send().await.unwrap();
/// println!("legal hold of object '{}' in bucket '{}' is deleted", resp.object, resp.bucket);
/// println!("legal hold of object '{}' in bucket '{}' is deleted", resp.object(), resp.bucket());
/// }
/// ```
pub fn delete_object_tagging<S1: Into<String>, S2: Into<String>>(

View File

@ -33,6 +33,7 @@ impl Client {
/// use minio::s3::response::DeleteObjectResponse;
/// use minio::s3::builders::ObjectToDelete;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasVersion;
///
/// #[tokio::main]
/// async fn main() {
@ -40,7 +41,7 @@ impl Client {
/// let resp: DeleteObjectResponse = client
/// .delete_object("bucket-name", ObjectToDelete::from("object-name"))
/// .send().await.unwrap();
/// println!("the object is deleted. The delete marker has version '{:?}'", resp.version_id);
/// println!("the object is deleted. The delete marker has version '{:?}'", resp.version_id());
/// }
/// ```
pub fn delete_object<S: Into<String>, D: Into<ObjectToDelete>>(

View File

@ -28,6 +28,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::GetBucketEncryptionResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
@ -35,7 +36,7 @@ impl Client {
/// let resp: GetBucketEncryptionResponse = client
/// .get_bucket_encryption("bucket-name")
/// .send().await.unwrap();
/// println!("retrieved SseConfig '{:?}' from bucket '{}'", resp.config, resp.bucket);
/// println!("retrieved SseConfig '{:?}' from bucket '{}'", resp.config(), resp.bucket());
/// }
/// ```
pub fn get_bucket_encryption<S: Into<String>>(&self, bucket: S) -> GetBucketEncryption {

View File

@ -28,6 +28,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::GetBucketLifecycleResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
@ -35,7 +36,7 @@ impl Client {
/// let resp: GetBucketLifecycleResponse = client
/// .get_bucket_lifecycle("bucket-name")
/// .send().await.unwrap();
/// println!("retrieved bucket lifecycle config '{:?}' from bucket '{}'", resp.config, resp.bucket);
/// println!("retrieved bucket lifecycle config '{:?}' from bucket '{}'", resp.config(), resp.bucket());
/// }
/// ```
pub fn get_bucket_lifecycle<S: Into<String>>(&self, bucket: S) -> GetBucketLifecycle {

View File

@ -28,6 +28,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::GetBucketNotificationResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
@ -35,7 +36,7 @@ impl Client {
/// let resp: GetBucketNotificationResponse = client
/// .get_bucket_notification("bucket-name")
/// .send().await.unwrap();
/// println!("retrieved bucket notification config '{:?}' from bucket '{}'", resp.config, resp.bucket);
/// println!("retrieved bucket notification config '{:?}' from bucket '{}'", resp.config(), resp.bucket());
/// }
/// ```
pub fn get_bucket_notification<S: Into<String>>(&self, bucket: S) -> GetBucketNotification {

View File

@ -28,6 +28,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::GetBucketPolicyResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
@ -35,7 +36,7 @@ impl Client {
/// let resp: GetBucketPolicyResponse = client
/// .get_bucket_policy("bucket-name")
/// .send().await.unwrap();
/// println!("retrieved bucket policy config '{:?}' from bucket '{}'", resp.config, resp.bucket);
/// println!("retrieved bucket policy config '{:?}' from bucket '{}'", resp.config(), resp.bucket());
/// }
/// ```
pub fn get_bucket_policy<S: Into<String>>(&self, bucket: S) -> GetBucketPolicy {

View File

@ -30,6 +30,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::GetBucketReplicationResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
@ -37,7 +38,7 @@ impl Client {
/// let resp: GetBucketReplicationResponse = client
/// .get_bucket_replication("bucket-name")
/// .send().await.unwrap();
/// println!("retrieved bucket replication config '{:?}' from bucket '{}'", resp.config, resp.bucket);
/// println!("retrieved bucket replication config '{:?}' from bucket '{}'", resp.config(), resp.bucket());
/// }
/// ```
pub fn get_bucket_replication<S: Into<String>>(&self, bucket: S) -> GetBucketReplication {

View File

@ -30,6 +30,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::GetBucketTaggingResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::{HasBucket, HasTagging};
///
/// #[tokio::main]
/// async fn main() {
@ -37,7 +38,7 @@ impl Client {
/// let resp: GetBucketTaggingResponse = client
/// .get_bucket_tagging("bucket-name")
/// .send().await.unwrap();
/// println!("retrieved bucket tags '{:?}' from bucket '{}'", resp.tags, resp.bucket);
/// println!("retrieved bucket tags '{:?}' from bucket '{}'", resp.tags(), resp.bucket());
/// }
/// ```
pub fn get_bucket_tagging<S: Into<String>>(&self, bucket: S) -> GetBucketTagging {

View File

@ -30,6 +30,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::GetBucketVersioningResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
@ -37,7 +38,7 @@ impl Client {
/// let resp: GetBucketVersioningResponse = client
/// .get_bucket_versioning("bucket-name")
/// .send().await.unwrap();
/// println!("retrieved versioning status '{:?}' from bucket '{}'", resp.status, resp.bucket);
/// println!("retrieved versioning status '{:?}' from bucket '{}'", resp.status(), resp.bucket());
/// }
/// ```
pub fn get_bucket_versioning<S: Into<String>>(&self, bucket: S) -> GetBucketVersioning {

View File

@ -38,7 +38,7 @@ impl Client {
/// 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_bytes = resp.content().unwrap().to_segmented_bytes().await.unwrap().to_bytes();
/// let content_str = String::from_utf8(content_bytes.to_vec()).unwrap();
/// println!("retrieved content '{content_str}'");
/// }

View File

@ -30,6 +30,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::GetObjectLegalHoldResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::{HasBucket, HasObject};
///
/// #[tokio::main]
/// async fn main() {
@ -37,7 +38,7 @@ impl Client {
/// let resp: GetObjectLegalHoldResponse = client
/// .get_object_legal_hold("bucket-name", "object-name")
/// .send().await.unwrap();
/// println!("legal hold of object '{}' in bucket '{}' is enabled: {}", resp.object, resp.bucket, resp.enabled);
/// println!("legal hold of object '{}' in bucket '{}' is enabled: {:?}", resp.object(), resp.bucket(), resp.enabled());
/// }
/// ```
pub fn get_object_legal_hold<S1: Into<String>, S2: Into<String>>(

View File

@ -30,6 +30,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::GetObjectLockConfigResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
@ -37,7 +38,7 @@ impl Client {
/// let resp: GetObjectLockConfigResponse = client
/// .get_object_lock_config("bucket-name")
/// .send().await.unwrap();
/// println!("retrieved object lock config '{:?}' from bucket '{}' is enabled", resp.config, resp.bucket);
/// println!("retrieved object lock config '{:?}' from bucket '{}' is enabled", resp.config(), resp.bucket());
/// }
/// ```
pub fn get_object_lock_config<S: Into<String>>(&self, bucket: S) -> GetObjectLockConfig {

View File

@ -36,7 +36,7 @@ impl Client {
/// let resp: GetObjectPromptResponse = client
/// .get_object_prompt("bucket-name", "object-name", "What is it about?")
/// .send().await.unwrap();
/// println!("the prompt response is: '{}'", resp.prompt_response);
/// println!("the prompt response is: '{:?}'", resp.prompt_response());
/// }
/// ```
pub fn get_object_prompt<S1: Into<String>, S2: Into<String>, S3: Into<String>>(

View File

@ -30,6 +30,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::GetObjectRetentionResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
@ -37,7 +38,7 @@ impl Client {
/// let resp: GetObjectRetentionResponse = client
/// .get_object_retention("bucket-name", "object-name")
/// .send().await.unwrap();
/// println!("retrieved retention mode '{:?}' until '{:?}' from bucket '{}' is enabled", resp.retention_mode, resp.retain_until_date, resp.bucket);
/// println!("retrieved retention mode '{:?}' until '{:?}' from bucket '{}' is enabled", resp.retention_mode(), resp.retain_until_date(), resp.bucket());
/// }
/// ```
pub fn get_object_retention<S1: Into<String>, S2: Into<String>>(

View File

@ -30,6 +30,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::GetObjectTaggingResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::{HasBucket, HasObject, HasTagging};
///
/// #[tokio::main]
/// async fn main() {
@ -37,7 +38,7 @@ impl Client {
/// let resp: GetObjectTaggingResponse = client
/// .get_object_tagging("bucket-name", "object-name")
/// .send().await.unwrap();
/// println!("retrieved object tags '{:?}' from object '{}' in bucket '{}' is enabled", resp.tags, resp.object, resp.bucket);
/// println!("retrieved object tags '{:?}' from object '{}' in bucket '{}' is enabled", resp.tags(), resp.object(), resp.bucket());
/// }
/// ```
pub fn get_object_tagging<S1: Into<String>, S2: Into<String>>(

View File

@ -16,7 +16,6 @@
use super::{Client, DEFAULT_REGION};
use crate::s3::builders::GetRegion;
use crate::s3::error::Error;
use crate::s3::response::GetRegionResponse;
use crate::s3::types::S3Api;
impl Client {
@ -31,6 +30,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::GetRegionResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
@ -38,7 +38,7 @@ impl Client {
/// let resp: GetRegionResponse = client
/// .get_region("bucket-name")
/// .send().await.unwrap();
/// println!("retrieved region '{:?}' for bucket '{}'", resp.region_response, resp.bucket);
/// println!("retrieved region '{:?}' for bucket '{}'", resp.region_response(), resp.bucket());
/// }
/// ```
pub fn get_region<S: Into<String>>(&self, bucket: S) -> GetRegion {
@ -82,18 +82,20 @@ impl Client {
return Ok(v.value().clone());
}
// Otherwise, fetch the region and cache it
let resp: GetRegionResponse = self.get_region(&bucket).send().await?;
let resolved_region: String = if resp.region_response.is_empty() {
DEFAULT_REGION.to_owned()
// Otherwise, fetch the region from the server and cache it
let resolved_region: String = {
let region = self.get_region(&bucket).send().await?.region_response()?;
if !region.is_empty() {
region
} else {
resp.region_response
DEFAULT_REGION.to_owned()
}
};
self.shared
.region_map
.insert(bucket, resolved_region.clone());
Ok(resolved_region)
}
}

View File

@ -37,7 +37,7 @@ impl Client {
/// let resp: ListBucketsResponse = client
/// .list_buckets()
/// .send().await.unwrap();
/// println!("retrieved buckets '{:?}'", resp.buckets);
/// println!("retrieved buckets '{:?}'", resp.buckets());
/// }
/// ```
pub fn list_buckets(&self) -> ListBuckets {

View File

@ -31,6 +31,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::PutBucketEncryptionResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
@ -40,7 +41,7 @@ impl Client {
/// .put_bucket_encryption("bucket-name")
/// .sse_config(config)
/// .send().await.unwrap();
/// println!("set encryption on bucket '{}'", resp.bucket);
/// println!("set encryption on bucket '{}'", resp.bucket());
/// }
/// ```
pub fn put_bucket_encryption<S: Into<String>>(&self, bucket: S) -> PutBucketEncryption {

View File

@ -31,11 +31,11 @@ impl Client {
/// use minio::s3::response::PutBucketLifecycleResponse;
/// use minio::s3::types::{Filter, S3Api};
/// use minio::s3::lifecycle_config::{LifecycleRule, LifecycleConfig};
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
///
/// let rules: Vec<LifecycleRule> = vec![LifecycleRule {
/// id: String::from("rule1"),
/// filter: Filter {and_operator: None, prefix: Some(String::from("logs/")), tag: None},
@ -48,7 +48,7 @@ impl Client {
/// .put_bucket_lifecycle("bucket-name")
/// .life_cycle_config(LifecycleConfig { rules })
/// .send().await.unwrap();
/// println!("set bucket replication policy on bucket '{}'", resp.bucket);
/// println!("set bucket replication policy on bucket '{}'", resp.bucket());
/// }
/// ```
pub fn put_bucket_lifecycle<S: Into<String>>(&self, bucket: S) -> PutBucketLifecycle {

View File

@ -28,11 +28,11 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::types::{NotificationConfig, PrefixFilterRule, QueueConfig, S3Api, SuffixFilterRule};
/// use minio::s3::response::PutBucketNotificationResponse;
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
///
/// let config = NotificationConfig {
/// cloud_func_config_list: None,
/// queue_config_list: Some(vec![QueueConfig {
@ -56,7 +56,7 @@ impl Client {
/// .put_bucket_notification("bucket-name")
/// .notification_config(config)
/// .send().await.unwrap();
/// println!("set bucket notification for bucket '{:?}'", resp.bucket);
/// println!("set bucket notification for bucket '{:?}'", resp.bucket());
/// }
/// ```
pub fn put_bucket_notification<S: Into<String>>(&self, bucket: S) -> PutBucketNotification {

View File

@ -30,6 +30,7 @@ impl Client {
/// use minio::s3::builders::VersioningStatus;
/// use minio::s3::response::PutBucketPolicyResponse;
/// use minio::s3::types::{S3Api, AndOperator, Destination, Filter, ReplicationConfig, ReplicationRule};
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
@ -63,7 +64,7 @@ impl Client {
/// .put_bucket_policy("bucket-name")
/// .config(config.to_owned())
/// .send().await.unwrap();
/// println!("set bucket replication policy on bucket '{}'", resp.bucket);
/// println!("set bucket replication policy on bucket '{}'", resp.bucket());
/// }
/// ```
pub fn put_bucket_policy<S: Into<String>>(&self, bucket: S) -> PutBucketPolicy {

View File

@ -31,6 +31,7 @@ impl Client {
/// use minio::s3::builders::VersioningStatus;
/// use minio::s3::response::PutBucketReplicationResponse;
/// use minio::s3::types::{S3Api, AndOperator, Destination, Filter, ReplicationConfig, ReplicationRule};
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// use std::collections::HashMap;
///
@ -75,7 +76,7 @@ impl Client {
/// .put_bucket_replication("bucket-name")
/// .replication_config(ReplicationConfig {role: None, rules})
/// .send().await.unwrap();
/// println!("enabled versioning on bucket '{}'", resp.bucket);
/// println!("enabled versioning on bucket '{}'", resp.bucket());
/// }
/// ```
pub fn put_bucket_replication<S: Into<String>>(&self, bucket: S) -> PutBucketReplication {

View File

@ -31,6 +31,7 @@ impl Client {
/// use minio::s3::builders::VersioningStatus;
/// use minio::s3::response::PutBucketTaggingResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// use std::collections::HashMap;
///
@ -46,7 +47,7 @@ impl Client {
/// .put_bucket_tagging("bucket-name")
/// .tags(tags)
/// .send().await.unwrap();
/// println!("set tags on bucket '{}'", resp.bucket);
/// println!("set tags on bucket '{}'", resp.bucket());
/// }
/// ```
pub fn put_bucket_tagging<S: Into<String>>(&self, bucket: S) -> PutBucketTagging {

View File

@ -31,6 +31,7 @@ impl Client {
/// use minio::s3::builders::VersioningStatus;
/// use minio::s3::response::PutBucketVersioningResponse;
/// use minio::s3::types::{S3Api, ObjectLockConfig, RetentionMode};
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
@ -40,7 +41,7 @@ impl Client {
/// .put_bucket_versioning("bucket-name")
/// .versioning_status(VersioningStatus::Enabled)
/// .send().await.unwrap();
/// println!("enabled versioning on bucket '{}'", resp.bucket);
/// println!("enabled versioning on bucket '{}'", resp.bucket());
/// }
/// ```
pub fn put_bucket_versioning<S: Into<String>>(&self, bucket: S) -> PutBucketVersioning {

View File

@ -29,7 +29,11 @@ impl Client {
///
/// For handling large files requiring multipart upload, see [`create_multipart_upload`](#method.create_multipart_upload).
///
/// To execute the request, call [`PutObject::send()`](crate::s3::types::S3Api::send),
/// For handling large files requiring multipart upload, see [`create_multipart_upload`](#method.create_multipart_upload).
///
/// For handling large files requiring multipart upload, see [`create_multipart_upload`](#method.create_multipart_upload).
///
/// To execute the request, call [`PutObjects::send()`](crate::s3::types::S3Api::send),
/// which returns a [`Result`] containing a [`PutObjectResponse`](crate::s3::response::PutObjectResponse).
///
/// For more information, refer to the [AWS S3 PutObject API documentation](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html).
@ -41,6 +45,7 @@ impl Client {
/// use minio::s3::response::PutObjectResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::segmented_bytes::SegmentedBytes;
/// use minio::s3::response::a_response_traits::HasObject;
///
/// #[tokio::main]
/// async fn main() {
@ -48,7 +53,7 @@ impl Client {
/// let data = SegmentedBytes::from("Hello world".to_string());
/// let resp: PutObjectResponse =
/// client.put_object("bucket-name", "object-name", data).send().await.unwrap();
/// println!("successfully put object '{}'", resp.object);
/// println!("successfully put object '{}'", resp.object());
/// }
/// ```
pub fn put_object<S1: Into<String>, S2: Into<String>>(
@ -80,7 +85,7 @@ impl Client {
/// let resp: CreateMultipartUploadResponse = client
/// .create_multipart_upload("bucket-name", "large-object")
/// .send().await.unwrap();
/// println!("Initiated multipart upload with UploadId '{}'", resp.upload_id);
/// println!("Initiated multipart upload with UploadId '{:?}'", resp.upload_id().await);
/// }
/// ```
pub fn create_multipart_upload<S1: Into<String>, S2: Into<String>>(
@ -135,8 +140,8 @@ impl Client {
/// ```no_run
/// use minio::s3::Client;
/// use minio::s3::response::CompleteMultipartUploadResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::types::PartInfo;
/// use minio::s3::types::{S3Api, PartInfo};
/// use minio::s3::response::a_response_traits::HasObject;
///
/// #[tokio::main]
/// async fn main() {
@ -145,7 +150,7 @@ impl Client {
/// let resp: CompleteMultipartUploadResponse = client
/// .complete_multipart_upload("bucket-name", "object-name", "upload-id-123", parts)
/// .send().await.unwrap();
/// println!("Completed multipart upload for '{}'", resp.object);
/// println!("Completed multipart upload for '{}'", resp.object());
/// }
/// ```
pub fn complete_multipart_upload<S1: Into<String>, S2: Into<String>, S3: Into<String>>(
@ -178,6 +183,7 @@ impl Client {
/// use minio::s3::response::UploadPartResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::segmented_bytes::SegmentedBytes;
/// use minio::s3::response::a_response_traits::HasObject;
///
/// #[tokio::main]
/// async fn main() {
@ -186,7 +192,7 @@ impl Client {
/// let resp: UploadPartResponse = client
/// .upload_part("bucket-name", "object-name", "upload-id", 1, data)
/// .send().await.unwrap();
/// println!("Uploaded object: {}", resp.object);
/// println!("Uploaded object: {}", resp.object());
/// }
/// ```
pub fn upload_part<S1: Into<String>, S2: Into<String>, S3: Into<String>>(
@ -220,6 +226,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::PutObjectContentResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::{HasObject, HasEtagFromHeaders};
///
/// #[tokio::main]
/// async fn main() {
@ -228,7 +235,7 @@ impl Client {
/// let resp: PutObjectContentResponse = client
/// .put_object_content("bucket", "object", content)
/// .send().await.unwrap();
/// println!("Uploaded object '{}' with ETag '{}'", resp.object, resp.etag);
/// println!("Uploaded object '{}' with ETag '{:?}'", resp.object(), resp.etag());
/// }
/// ```
pub fn put_object_content<S1: Into<String>, S2: Into<String>, C: Into<ObjectContent>>(

View File

@ -30,6 +30,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::PutObjectLegalHoldResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
@ -37,7 +38,7 @@ impl Client {
/// let resp: PutObjectLegalHoldResponse = client
/// .put_object_legal_hold("bucket-name", "object-name", true)
/// .send().await.unwrap();
/// println!("legal hold of bucket '{}' is enabled", resp.bucket);
/// println!("legal hold of bucket '{}' is enabled", resp.bucket());
/// }
/// ```
pub fn put_object_legal_hold<S1: Into<String>, S2: Into<String>>(

View File

@ -30,6 +30,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::{CreateBucketResponse, PutObjectLockConfigResponse};
/// use minio::s3::types::{S3Api, ObjectLockConfig, RetentionMode};
/// use minio::s3::response::a_response_traits::HasBucket;
///
/// #[tokio::main]
/// async fn main() {
@ -38,14 +39,14 @@ impl Client {
///
/// let resp: CreateBucketResponse =
/// client.create_bucket(bucket_name).object_lock(true).send().await.unwrap();
/// println!("created bucket '{}' with object locking enabled", resp.bucket);
/// 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: PutObjectLockConfigResponse =
/// client.put_object_lock_config(bucket_name).config(config).send().await.unwrap();
/// println!("configured object locking for bucket '{}'", resp.bucket);
/// println!("configured object locking for bucket '{}'", resp.bucket());
/// }
/// ```
pub fn put_object_lock_config<S: Into<String>>(&self, bucket: S) -> PutObjectLockConfig {

View File

@ -32,6 +32,7 @@ impl Client {
/// use minio::s3::builders::ObjectToDelete;
/// use minio::s3::types::{S3Api, RetentionMode};
/// use minio::s3::utils::utc_now;
/// use minio::s3::response::a_response_traits::HasObject;
///
/// #[tokio::main]
/// async fn main() {
@ -42,7 +43,7 @@ impl Client {
/// .retention_mode(Some(RetentionMode::GOVERNANCE))
/// .retain_until_date(Some(retain_until_date))
/// .send().await.unwrap();
/// println!("set the object retention for object '{}'", resp.object);
/// println!("set the object retention for object '{}'", resp.object());
/// }
/// ```
pub fn put_object_retention<S1: Into<String>, S2: Into<String>>(

View File

@ -31,6 +31,7 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::PutObjectTaggingResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasObject;
///
/// #[tokio::main]
/// async fn main() {
@ -43,7 +44,7 @@ impl Client {
/// .put_object_tagging("bucket-name", "object-name")
/// .tags(tags)
/// .send().await.unwrap();
/// println!("set the object tags for object '{}'", resp.object);
/// println!("set the object tags for object '{}'", resp.object());
/// }
/// ```
pub fn put_object_tagging<S: Into<String>>(&self, bucket: S, object: S) -> PutObjectTagging {

View File

@ -28,13 +28,14 @@ impl Client {
/// use minio::s3::Client;
/// use minio::s3::response::StatObjectResponse;
/// use minio::s3::types::S3Api;
/// use minio::s3::response::a_response_traits::HasObject;
///
/// #[tokio::main]
/// async fn main() {
/// let client: Client = Default::default(); // configure your client here
/// let resp: StatObjectResponse =
/// client.stat_object("bucket-name", "object-name").send().await.unwrap();
/// println!("stat of object '{}' are {:#?}", resp.object, resp);
/// println!("stat of object '{}' are {:#?}", resp.object(), resp);
/// }
/// ```
pub fn stat_object<S1: Into<String>, S2: Into<String>>(

View File

@ -129,12 +129,7 @@ pub enum Error {
StrError(reqwest::header::ToStrError),
IntError(std::num::ParseIntError),
BoolError(std::str::ParseBoolError),
Utf8Error(Box<dyn std::error::Error + Send + Sync + 'static>),
/// Occurs when converting Vec<u8> to String (e.g. String::from_utf8)
//FromUtf8Error(alloc::string::FromUtf8Error),
/// Occurs when converting &[u8] to &str (e.g. std::str::from_utf8)
//Utf8Error(std::str::Utf8Error),
JsonError(serde_json::Error),
XmlError(String),
InvalidBaseUrl(String),
@ -203,7 +198,6 @@ impl fmt::Display for Error {
Error::IntError(e) => write!(f, "{e}"),
Error::BoolError(e) => write!(f, "{e}"),
Error::Utf8Error(e) => write!(f, "{e}"),
//Error::FromUtf8Error(e) => write!(f, "{e}"),
Error::JsonError(e) => write!(f, "{e}"),
Error::XmlError(m) => write!(f, "{m}"),
Error::InvalidBucketName(m) => write!(f, "{m}"),

View File

@ -60,6 +60,9 @@ mod put_object_tagging;
mod select_object_content;
mod stat_object;
#[macro_use]
pub mod a_response_traits;
pub use append_object::AppendObjectResponse;
pub use bucket_exists::BucketExistsResponse;
pub use copy_object::*;

View File

@ -0,0 +1,219 @@
use crate::s3::error::Error;
use crate::s3::types::S3Request;
use crate::s3::utils::{get_text, trim_quotes};
use bytes::{Buf, Bytes};
use http::HeaderMap;
use std::collections::HashMap;
use xmltree::Element;
#[macro_export]
/// Implements the `FromS3Response` trait for the specified types.
macro_rules! impl_from_s3response {
($($ty:ty),* $(,)?) => {
$(
#[async_trait::async_trait]
impl FromS3Response for $ty {
async fn from_s3response(
request: S3Request,
response: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp: reqwest::Response = response?;
Ok(Self {
request,
headers: mem::take(resp.headers_mut()),
body: resp.bytes().await?
})
}
}
)*
};
}
#[macro_export]
/// Implements the `FromS3Response` trait for the specified types with an additional `object_size` field.
macro_rules! impl_from_s3response_with_size {
($($ty:ty),* $(,)?) => {
$(
#[async_trait::async_trait]
impl FromS3Response for $ty {
async fn from_s3response(
request: S3Request,
response: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp: reqwest::Response = response?;
Ok(Self {
request,
headers: mem::take(resp.headers_mut()),
body: resp.bytes().await?,
object_size: 0, // Default value, can be set later
})
}
}
)*
};
}
#[macro_export]
/// Implements the `HasS3Fields` trait for the specified types.
macro_rules! impl_has_s3fields {
($($ty:ty),* $(,)?) => {
$(
impl HasS3Fields for $ty {
/// The request that was sent to the S3 API.
fn request(&self) -> &S3Request {
&self.request
}
/// The response of the S3 API.
fn headers(&self) -> &HeaderMap {
&self.headers
}
/// The response of the S3 API.
fn body(&self) -> &Bytes {
&self.body
}
}
)*
};
}
pub trait HasS3Fields {
/// The request that was sent to the S3 API.
fn request(&self) -> &S3Request;
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
fn headers(&self) -> &HeaderMap;
/// The response body returned by the server, which may contain the object data or other information.
fn body(&self) -> &Bytes;
}
/// Returns the name of the S3 bucket.
pub trait HasBucket: HasS3Fields {
/// Returns the name of the S3 bucket.
#[inline]
fn bucket(&self) -> &str {
self.request().bucket.as_deref().unwrap_or_default()
}
}
/// Returns the object key (name) of the S3 object.
pub trait HasObject: HasS3Fields {
/// Returns the object key (name) of the S3 object.
#[inline]
fn object(&self) -> &str {
self.request().object.as_deref().unwrap_or_default()
}
}
/// Returns the region of the S3 bucket.
pub trait HasRegion: HasS3Fields {
/// Returns the region of the S3 bucket.
#[inline]
fn region(&self) -> &str {
&self.request().inner_region
}
}
/// Returns the version ID of the object (`x-amz-version-id`), if versioning is enabled for the bucket.
pub trait HasVersion: HasS3Fields {
/// Returns the version ID of the object (`x-amz-version-id`), if versioning is enabled for the bucket.
#[inline]
fn version_id(&self) -> Option<&str> {
self.headers()
.get("x-amz-version-id")
.and_then(|v| v.to_str().ok())
}
}
/// Returns the value of the `ETag` header from response headers (for operations that return ETag in headers).
/// The ETag is typically a hash of the object content, but it may vary based on the storage backend.
pub trait HasEtagFromHeaders: HasS3Fields {
/// Returns the value of the `ETag` header from response headers (for operations that return ETag in headers).
/// The ETag is typically a hash of the object content, but it may vary based on the storage backend.
#[inline]
fn etag(&self) -> Result<String, Error> {
// Retrieve the ETag from the response headers.
let etag = self
.headers()
.get("etag")
.and_then(|v| v.to_str().ok())
.map(|s| s.trim_matches('"'))
.unwrap_or_default()
.to_string();
Ok(etag)
}
}
/// Returns the value of the `ETag` from the response body, which is a unique identifier for
/// the object version. The ETag is typically a hash of the object content, but it may vary
/// based on the storage backend.
pub trait HasEtagFromBody: HasS3Fields {
/// Returns the value of the `ETag` from the response body, which is a unique identifier for
/// the object version. The ETag is typically a hash of the object content, but it may vary
/// based on the storage backend.
fn etag(&self) -> Result<String, Error> {
// Retrieve the ETag from the response body.
let root = xmltree::Element::parse(self.body().clone().reader())?;
let etag: String = get_text(&root, "ETag")?;
Ok(trim_quotes(etag))
}
}
/// Returns the size of the object in bytes, as specified by the `x-amz-object-size` header.
pub trait HasObjectSize: HasS3Fields {
/// Returns the size of the object in bytes, as specified by the `x-amz-object-size` header.
#[inline]
fn object_size(&self) -> u64 {
self.headers()
.get("x-amz-object-size")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(0)
}
}
/// Value of the `x-amz-delete-marker` header.
/// Indicates whether the specified object version that was permanently deleted was (true) or
/// was not (false) a delete marker before deletion. In a simple DELETE, this header indicates
/// whether (true) or not (false) the current version of the object is a delete marker.
pub trait HasIsDeleteMarker: HasS3Fields {
/// Returns `true` if the object is a delete marker, `false` otherwise.
///
/// Value of the `x-amz-delete-marker` header.
/// Indicates whether the specified object version that was permanently deleted was (true) or
/// was not (false) a delete marker before deletion. In a simple DELETE, this header indicates
/// whether (true) or not (false) the current version of the object is a delete marker.
#[inline]
fn is_delete_marker(&self) -> Result<Option<bool>, Error> {
Ok(Some(
self.headers()
.get("x-amz-delete-marker")
.map(|v| v == "true")
.unwrap_or(false),
))
//Ok(match self.headers().get("x-amz-delete-marker") {
// Some(v) => Some(v.to_str()?.parse::<bool>()?),
// None => None,
//})
}
}
pub trait HasTagging: HasS3Fields {
/// Returns the tags associated with the bucket.
///
/// If the bucket has no tags, this will return an empty `HashMap`.
#[inline]
fn tags(&self) -> Result<HashMap<String, String>, Error> {
let mut tags = HashMap::new();
if self.body().is_empty() {
// Note: body is empty when server responses with NoSuchTagSet
return Ok(tags);
}
let mut root = Element::parse(self.body().clone().reader())?;
let element = root
.get_mut_child("TagSet")
.ok_or(Error::XmlError("<TagSet> tag not found".to_string()))?;
while let Some(v) = element.take_child("Tag") {
tags.insert(get_text(&v, "Key")?, get_text(&v, "Value")?);
}
Ok(tags)
}
}

View File

@ -14,78 +14,30 @@
// limitations under the License.
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{
HasBucket, HasEtagFromHeaders, HasObject, HasObjectSize, HasRegion, HasS3Fields, HasVersion,
};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::{take_bucket, take_object};
use async_trait::async_trait;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::Bytes;
use http::HeaderMap;
use std::mem;
/// Represents the response of the `append_object` API call.
/// This struct contains metadata and information about the object being appended.
///
/// # Fields
///
/// * `headers` - HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
/// * `region` - The AWS region where the bucket resides.
/// * `bucket` - Name of the bucket containing the object.
/// * `object` - Key (path) identifying the object within the bucket.
/// * `etag` - Entity tag representing a specific version of the object.
/// * `version_id` - Version ID of the object, if versioning is enabled. Value of the `x-amz-version-id` header.
/// * `object_size` - Value of the `x-amz-object-size` header.
#[derive(Debug, Clone)]
#[derive(Clone, Debug)]
pub struct AppendObjectResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket containing the object.
pub bucket: String,
/// Key (path) identifying the object within the bucket.
pub object: String,
/// Entity tag representing a specific version of the object.
pub etag: String,
/// Version ID of the object, if versioning is enabled. Value of the `x-amz-version-id` header.
pub version_id: Option<String>,
/// Value of the `x-amz-object-size` header.
pub object_size: u64,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for AppendObjectResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
let headers: HeaderMap = mem::take(resp.headers_mut());
impl_from_s3response!(AppendObjectResponse);
impl_has_s3fields!(AppendObjectResponse);
let etag: String = match headers.get("etag") {
Some(v) => v.to_str()?.to_string().trim_matches('"').to_string(),
_ => String::new(),
};
let version_id: Option<String> = match headers.get("x-amz-version-id") {
Some(v) => Some(v.to_str()?.to_string()),
None => None,
};
let object_size: u64 = match headers.get("x-amz-object-size") {
Some(v) => v.to_str()?.parse::<u64>()?,
None => 0,
};
Ok(Self {
headers,
bucket: take_bucket(req.bucket)?,
object: take_object(req.object)?,
region: req.inner_region,
etag,
version_id,
object_size,
})
}
}
impl HasBucket for AppendObjectResponse {}
impl HasObject for AppendObjectResponse {}
impl HasRegion for AppendObjectResponse {}
impl HasVersion for AppendObjectResponse {}
impl HasEtagFromHeaders for AppendObjectResponse {}
impl HasObjectSize for AppendObjectResponse {}

View File

@ -13,59 +13,57 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::impl_has_s3fields;
use crate::s3::error::{Error, ErrorCode};
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::take_bucket;
use async_trait::async_trait;
use bytes::Bytes;
use http::HeaderMap;
use std::mem;
/// Represents the response of the [bucket_exists()](crate::s3::client::Client::bucket_exists) API call.
/// This struct contains metadata and information about the existence of a bucket.
///
/// # Fields
///
/// * `headers` - HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
/// * `region` - The AWS region where the bucket resides. If the bucket does not exist, this will be an empty string.
/// * `bucket` - The name of the bucket being checked.
/// * `exists` - A boolean indicating whether the bucket exists or not.
#[derive(Clone, Debug)]
pub struct BucketExistsResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
request: S3Request,
headers: HeaderMap,
body: Bytes,
/// The AWS region where the bucket resides.
pub region: String,
/// The name of the bucket being checked.
pub bucket: String,
/// Whether the bucket exists or not.
pub exists: bool,
pub(crate) exists: bool,
}
impl_has_s3fields!(BucketExistsResponse);
impl HasBucket for BucketExistsResponse {}
impl HasRegion for BucketExistsResponse {}
#[async_trait]
impl FromS3Response for BucketExistsResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
request: S3Request,
response: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
match resp {
Ok(mut r) => Ok(Self {
headers: mem::take(r.headers_mut()),
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
match response {
Ok(mut resp) => Ok(Self {
request,
headers: mem::take(resp.headers_mut()),
body: resp.bytes().await?,
exists: true,
}),
Err(Error::S3Error(e)) if e.code == ErrorCode::NoSuchBucket => {
Ok(Self {
Err(Error::S3Error(e)) if e.code == ErrorCode::NoSuchBucket => Ok(Self {
request,
headers: e.headers,
region: String::new(), // NOTE the bucket does not exist and the region is not provided
bucket: take_bucket(req.bucket)?,
body: Bytes::new(),
exists: false,
})
}
}),
Err(e) => Err(e),
}
}
}
impl BucketExistsResponse {
/// Returns `true` if the bucket exists, `false` otherwise.
pub fn exists(&self) -> bool {
self.exists
}
}

View File

@ -14,186 +14,43 @@
// limitations under the License.
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{
HasBucket, HasEtagFromBody, HasObject, HasRegion, HasS3Fields, HasVersion,
};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::{get_text, take_bucket, take_object};
use async_trait::async_trait;
use bytes::{Buf, Bytes};
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::Bytes;
use http::HeaderMap;
use std::mem;
use xmltree::Element;
/// Base response struct that contains common functionality for S3 operations
#[derive(Clone, Debug)]
pub struct S3Response2 {
pub(crate) request: S3Request,
pub(crate) headers: HeaderMap,
pub(crate) body: Bytes,
}
impl_from_s3response!(S3Response2);
impl_has_s3fields!(S3Response2);
impl HasBucket for S3Response2 {}
impl HasObject for S3Response2 {}
impl HasRegion for S3Response2 {}
impl HasVersion for S3Response2 {}
impl HasEtagFromBody for S3Response2 {}
/// Represents the response of the `upload_part_copy` API call.
/// This struct contains metadata and information about the part being copied during a multipart upload.
///
/// # Fields
///
/// * `headers` - HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
/// * `region` - The AWS region where the bucket resides.
/// * `bucket` - Name of the bucket containing the object.
/// * `object` - Key (path) identifying the object within the bucket.
/// * `etag` - Entity tag representing a specific version of the object.
/// * `version_id` - Version ID of the object, if versioning is enabled. Value of the `x-amz-version-id` header.
#[derive(Clone, Debug)]
pub struct UploadPartCopyResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
pub type UploadPartCopyResponse = S3Response2;
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket containing the object.
pub bucket: String,
/// Key (path) identifying the object within the bucket.
pub object: String,
/// Entity tag representing a specific version of the object.
pub etag: String,
/// Version ID of the object, if versioning is enabled. Value of the `x-amz-version-id` header.
pub version_id: Option<String>,
}
#[async_trait]
impl FromS3Response for UploadPartCopyResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
let headers: HeaderMap = mem::take(resp.headers_mut());
let etag: String = {
let body: Bytes = resp.bytes().await?;
let root = Element::parse(body.reader())?;
get_text(&root, "ETag")?.trim_matches('"').to_string()
};
let version_id: Option<String> = headers
.get("x-amz-version-id")
.and_then(|v| v.to_str().ok().map(String::from));
Ok(Self {
headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
object: take_object(req.object)?,
etag,
version_id,
})
}
}
#[derive(Clone, Debug)]
pub struct CopyObjectInternalResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
pub region: String,
pub bucket: String,
pub object: String,
pub etag: String,
pub version_id: Option<String>,
}
#[async_trait]
impl FromS3Response for CopyObjectInternalResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let bucket = req
.bucket
.ok_or_else(|| Error::InvalidBucketName("no bucket specified".into()))?;
let object = req
.object
.ok_or_else(|| Error::InvalidObjectName("no object specified".into()))?;
let mut resp = resp?;
let headers: HeaderMap = mem::take(resp.headers_mut());
let etag: String = {
let body: Bytes = resp.bytes().await?;
let root = Element::parse(body.reader())?;
get_text(&root, "ETag")?.trim_matches('"').to_string()
};
let version_id: Option<String> = headers
.get("x-amz-version-id")
.and_then(|v| v.to_str().ok().map(String::from));
Ok(CopyObjectInternalResponse {
headers,
region: req.inner_region,
bucket,
object,
etag,
version_id,
})
}
}
/// Internal response type for copy operations
pub type CopyObjectInternalResponse = S3Response2;
/// Represents the response of the `copy_object` API call.
/// This struct contains metadata and information about the object being copied.
///
/// # Fields
///
/// * `headers` - HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
/// * `region` - The AWS region where the bucket resides.
/// * `bucket` - Name of the bucket containing the object.
/// * `object` - Key (path) identifying the object within the bucket.
/// * `etag` - Entity tag representing a specific version of the object.
/// * `version_id` - Version ID of the object, if versioning is enabled. Value of the `x-amz-version-id` header.
#[derive(Clone, Debug)]
pub struct CopyObjectResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket containing the object.
pub bucket: String,
/// Key (path) identifying the object within the bucket.
pub object: String,
/// Entity tag representing a specific version of the object.
pub etag: String,
/// Version ID of the object, if versioning is enabled. Value of the `x-amz-version-id` header.
pub version_id: Option<String>,
}
pub type CopyObjectResponse = S3Response2;
/// Represents the response of the `[compose_object()](crate::s3::client::Client::compose_object) API call.
/// This struct contains metadata and information about the composed object.
///
/// # Fields
///
/// * `headers` - HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
/// * `bucket` - Name of the bucket containing the composed object.
/// * `object` - Key (path) identifying the composed object within the bucket.
/// * `region` - The AWS region where the bucket resides.
/// * `etag` - Entity tag representing a specific version of the composed object.
/// * `version_id` - Version ID of the composed object, if versioning is enabled.
#[derive(Debug, Clone)]
pub struct ComposeObjectResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// Name of the bucket containing the composed object.
pub bucket: String,
/// Key (path) identifying the composed object within the bucket.
pub object: String,
/// The AWS region where the bucket resides.
pub region: String,
/// Entity tag representing a specific version of the composed object.
pub etag: String,
/// Version ID of the composed object, if versioning is enabled.
pub version_id: Option<String>,
}
pub type ComposeObjectResponse = S3Response2;

View File

@ -13,10 +13,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::impl_has_s3fields;
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::take_bucket;
use async_trait::async_trait;
use bytes::Bytes;
use http::HeaderMap;
use std::mem;
@ -25,31 +27,36 @@ use std::mem;
/// API
#[derive(Clone, Debug)]
pub struct CreateBucketResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket containing the object.
pub bucket: String,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
impl_has_s3fields!(CreateBucketResponse);
impl HasBucket for CreateBucketResponse {}
impl HasRegion for CreateBucketResponse {}
#[async_trait]
impl FromS3Response for CreateBucketResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
request: S3Request,
response: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut req = req;
let bucket: String = take_bucket(req.bucket)?;
req.client.add_bucket_region(&bucket, &req.inner_region);
let mut resp = resp?;
let mut resp: reqwest::Response = response?;
let mut request = request;
let bucket: &str = request
.bucket
.as_deref()
.ok_or_else(|| Error::InvalidBucketName("no bucket specified".into()))?;
let region: &str = &request.inner_region;
request.client.add_bucket_region(bucket, region);
Ok(Self {
request,
headers: mem::take(resp.headers_mut()),
region: req.inner_region,
bucket,
body: resp.bytes().await?,
})
}
}

View File

@ -13,10 +13,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::impl_has_s3fields;
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::take_bucket;
use async_trait::async_trait;
use bytes::Bytes;
use http::HeaderMap;
use std::mem;
@ -25,31 +26,34 @@ use std::mem;
/// API
#[derive(Clone, Debug)]
pub struct DeleteBucketResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket containing the object.
pub bucket: String,
pub(crate) request: S3Request,
pub(crate) headers: HeaderMap,
pub(crate) body: Bytes,
}
impl_has_s3fields!(DeleteBucketResponse);
#[async_trait]
impl HasBucket for DeleteBucketResponse {}
impl HasRegion for DeleteBucketResponse {}
#[async_trait::async_trait]
impl FromS3Response for DeleteBucketResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
request: S3Request,
response: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut req = req;
let bucket: String = take_bucket(req.bucket)?;
req.client.remove_bucket_region(&bucket);
let mut resp = resp?;
let mut resp: reqwest::Response = response?;
let mut request = request;
let bucket: &str = request
.bucket
.as_deref()
.ok_or_else(|| Error::InvalidBucketName("no bucket specified".into()))?;
request.client.remove_bucket_region(bucket);
Ok(Self {
request,
headers: mem::take(resp.headers_mut()),
region: req.inner_region,
bucket,
body: resp.bytes().await?,
})
}
}

View File

@ -14,44 +14,24 @@
// limitations under the License.
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::take_bucket;
use async_trait::async_trait;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::Bytes;
use http::HeaderMap;
use std::mem;
/// Represents the response of the [delete_bucket_encryption()](crate::s3::client::Client::delete_bucket_encryption) API call.
/// This struct contains metadata and information about the bucket whose encryption configuration was removed.
///
/// # Fields
///
/// * `headers` - HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
/// * `region` - The AWS region where the bucket resides.
/// * `bucket` - Name of the bucket from which the encryption configuration was removed.
#[derive(Clone, Debug)]
pub struct DeleteBucketEncryptionResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket from which the Encryption configuration was removed.
pub bucket: String,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for DeleteBucketEncryptionResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
impl_from_s3response!(DeleteBucketEncryptionResponse);
impl_has_s3fields!(DeleteBucketEncryptionResponse);
Ok(Self {
headers: mem::take(resp.headers_mut()),
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
})
}
}
impl HasBucket for DeleteBucketEncryptionResponse {}
impl HasRegion for DeleteBucketEncryptionResponse {}

View File

@ -14,44 +14,24 @@
// limitations under the License.
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::take_bucket;
use async_trait::async_trait;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::Bytes;
use http::HeaderMap;
use std::mem;
/// Represents the response of the [delete_bucket_lifecycle()](crate::s3::client::Client::delete_bucket_lifecycle) API call.
/// This struct contains metadata and information about the bucket whose lifecycle configuration was removed.
///
/// # Fields
///
/// * `headers` - HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
/// * `region` - The AWS region where the bucket resides.
/// * `bucket` - Name of the bucket from which the Bucket Lifecycle configuration was removed.
#[derive(Clone, Debug)]
pub struct DeleteBucketLifecycleResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket from which the Bucket Lifecycle configuration was removed.
pub bucket: String,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for DeleteBucketLifecycleResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
impl_from_s3response!(DeleteBucketLifecycleResponse);
impl_has_s3fields!(DeleteBucketLifecycleResponse);
Ok(Self {
headers: mem::take(resp.headers_mut()),
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
})
}
}
impl HasBucket for DeleteBucketLifecycleResponse {}
impl HasRegion for DeleteBucketLifecycleResponse {}

View File

@ -14,44 +14,24 @@
// limitations under the License.
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::take_bucket;
use async_trait::async_trait;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::Bytes;
use http::HeaderMap;
use std::mem;
/// Represents the response of the [delete_bucket_notification()](crate::s3::client::Client::delete_bucket_notification) API call.
/// This struct contains metadata and information about the bucket whose notifications were removed.
///
/// # Fields
///
/// * `headers` - HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
/// * `region` - The AWS region where the bucket resides.
/// * `bucket` - Name of the bucket from which the Bucket Notifications were removed.
#[derive(Clone, Debug)]
pub struct DeleteBucketNotificationResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket from which the Bucket Notifications were removed.
pub bucket: String,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for DeleteBucketNotificationResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
impl_from_s3response!(DeleteBucketNotificationResponse);
impl_has_s3fields!(DeleteBucketNotificationResponse);
Ok(Self {
headers: mem::take(resp.headers_mut()),
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
})
}
}
impl HasBucket for DeleteBucketNotificationResponse {}
impl HasRegion for DeleteBucketNotificationResponse {}

View File

@ -13,49 +13,45 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::impl_has_s3fields;
use crate::s3::error::{Error, ErrorCode};
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::take_bucket;
use async_trait::async_trait;
use bytes::Bytes;
use http::HeaderMap;
use std::mem;
/// Represents the response of the [delete_bucket_policy()](crate::s3::client::Client::delete_bucket_policy) API call.
/// This struct contains metadata and information about the bucket whose policy was removed.
///
/// # Fields
///
/// * `headers` - HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
/// * `region` - The AWS region where the bucket resides.
/// * `bucket` - Name of the bucket from which the Bucket Policy was removed.
#[derive(Clone, Debug)]
pub struct DeleteBucketPolicyResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket from which the Bucket Policy was removed.
pub bucket: String,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
impl_has_s3fields!(DeleteBucketPolicyResponse);
impl HasBucket for DeleteBucketPolicyResponse {}
impl HasRegion for DeleteBucketPolicyResponse {}
#[async_trait]
impl FromS3Response for DeleteBucketPolicyResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
request: S3Request,
response: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
match resp {
Ok(mut r) => Ok(Self {
headers: mem::take(r.headers_mut()),
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
match response {
Ok(mut resp) => Ok(Self {
request,
headers: mem::take(resp.headers_mut()),
body: resp.bytes().await?,
}),
Err(Error::S3Error(e)) if e.code == ErrorCode::NoSuchBucketPolicy => Ok(Self {
request,
headers: e.headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
body: Bytes::new(),
}),
Err(e) => Err(e),
}

View File

@ -13,52 +13,48 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::impl_has_s3fields;
use crate::s3::error::{Error, ErrorCode};
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::take_bucket;
use async_trait::async_trait;
use bytes::Bytes;
use http::HeaderMap;
use std::mem;
/// Represents the response of the `[delete_bucket_replication()](crate::s3::client::Client::delete_bucket_replication) API call.
/// This struct contains metadata and information about the bucket whose replication configuration was removed.
///
/// # Fields
///
/// * `headers` - HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
/// * `region` - The AWS region where the bucket resides.
/// * `bucket` - Name of the bucket from which the Replication configuration was removed.
#[derive(Clone, Debug)]
pub struct DeleteBucketReplicationResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket from which the Replication configuration was removed.
pub bucket: String,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
impl_has_s3fields!(DeleteBucketReplicationResponse);
impl HasBucket for DeleteBucketReplicationResponse {}
impl HasRegion for DeleteBucketReplicationResponse {}
#[async_trait]
impl FromS3Response for DeleteBucketReplicationResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
request: S3Request,
response: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
match resp {
Ok(mut r) => Ok(Self {
headers: mem::take(r.headers_mut()),
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
match response {
Ok(mut resp) => Ok(Self {
request,
headers: mem::take(resp.headers_mut()),
body: resp.bytes().await?,
}),
Err(Error::S3Error(e))
if e.code == ErrorCode::ReplicationConfigurationNotFoundError =>
{
Ok(Self {
request,
headers: e.headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
body: Bytes::new(),
})
}
Err(e) => Err(e),

View File

@ -14,44 +14,24 @@
// limitations under the License.
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::take_bucket;
use async_trait::async_trait;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::Bytes;
use http::HeaderMap;
use std::mem;
/// Represents the response of the [delete_bucket_tagging()](crate::s3::client::Client::delete_bucket_tagging) API call.
/// This struct contains metadata and information about the bucket whose tags were removed.
///
/// # Fields
///
/// * `headers` - HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
/// * `region` - The AWS region where the bucket resides.
/// * `bucket` - Name of the bucket from which the tags were removed.
#[derive(Clone, Debug)]
pub struct DeleteBucketTaggingResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket from which the tags were removed.
pub bucket: String,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for DeleteBucketTaggingResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
impl_from_s3response!(DeleteBucketTaggingResponse);
impl_has_s3fields!(DeleteBucketTaggingResponse);
Ok(Self {
headers: mem::take(resp.headers_mut()),
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
})
}
}
impl HasBucket for DeleteBucketTaggingResponse {}
impl HasRegion for DeleteBucketTaggingResponse {}

View File

@ -13,58 +13,32 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use async_trait::async_trait;
use bytes::Buf;
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{
HasBucket, HasIsDeleteMarker, HasRegion, HasS3Fields, HasVersion,
};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::{get_default_text, get_option_text, get_text};
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::{Buf, Bytes};
use http::HeaderMap;
use std::mem;
use xmltree::Element;
use crate::s3::{
error::Error,
types::{FromS3Response, S3Request},
utils::{get_default_text, get_option_text, get_text},
};
#[derive(Debug, Clone)]
#[derive(Clone, Debug)]
pub struct DeleteObjectResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// Value of the `x-amz-delete-marker` header.
/// Indicates whether the specified object version that was permanently deleted was (true) or
/// was not (false) a delete marker before deletion. In a simple DELETE, this header indicates
/// whether (true) or not (false) the current version of the object is a delete marker.
pub is_delete_marker: bool,
/// Value of the `x-amz-version-id` header.
/// If a delete marker was created, this field will contain the version_id of the delete marker.
pub version_id: Option<String>,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for DeleteObjectResponse {
async fn from_s3response(
_req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
let headers: HeaderMap = mem::take(resp.headers_mut());
let is_delete_marker = headers
.get("x-amz-delete-marker")
.map(|v| v == "true")
.unwrap_or(false);
impl_from_s3response!(DeleteObjectResponse);
impl_has_s3fields!(DeleteObjectResponse);
let version_id: Option<String> = headers
.get("x-amz-version-id")
.and_then(|v| v.to_str().ok().map(String::from));
Ok(DeleteObjectResponse {
headers,
is_delete_marker,
version_id,
})
}
}
impl HasBucket for DeleteObjectResponse {}
impl HasRegion for DeleteObjectResponse {}
impl HasVersion for DeleteObjectResponse {}
impl HasIsDeleteMarker for DeleteObjectResponse {}
/// Error info returned by the S3 API when an object could not be deleted.
#[derive(Clone, Debug)]
@ -84,18 +58,6 @@ pub struct DeletedObject {
pub delete_marker_version_id: Option<String>,
}
/// Response of
/// [delete_objects()](crate::s3::client::Client::delete_objects)
/// S3 API. It is also returned by the
/// [remove_objects()](crate::s3::client::Client::delete_objects_streaming) API in the
/// form of a stream.
#[derive(Clone, Debug)]
pub struct DeleteObjectsResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
pub result: Vec<DeleteResult>,
}
/// Result of deleting an object.
#[derive(Clone, Debug)]
pub enum DeleteResult {
@ -116,24 +78,30 @@ impl DeleteResult {
pub fn is_deleted(&self) -> bool {
matches!(self, DeleteResult::Deleted(_))
}
pub fn is_error(&self) -> bool {
matches!(self, DeleteResult::Error(_))
}
}
#[async_trait]
impl FromS3Response for DeleteObjectsResponse {
async fn from_s3response(
_req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let resp = resp?;
let headers = resp.headers().clone();
/// Response of
/// [delete_objects()](crate::s3::client::Client::delete_objects)
/// S3 API. It is also returned by the
/// [remove_objects()](crate::s3::client::Client::delete_objects_streaming) API in the
/// form of a stream.
#[derive(Clone, Debug)]
pub struct DeleteObjectsResponse {
request: S3Request,
pub(crate) headers: HeaderMap,
body: Bytes,
}
let body = resp.bytes().await?;
impl_from_s3response!(DeleteObjectsResponse);
impl_has_s3fields!(DeleteObjectsResponse);
let root = Element::parse(body.reader())?;
impl DeleteObjectsResponse {
/// Returns the bucket name for which the delete operation was performed.
pub fn result(&self) -> Result<Vec<DeleteResult>, Error> {
let root = Element::parse(self.body.clone().reader())?;
let result = root
.children
.iter()
@ -158,7 +126,6 @@ impl FromS3Response for DeleteObjectsResponse {
}
})
.collect::<Result<Vec<DeleteResult>, Error>>()?;
Ok(Self { headers, result })
Ok(result)
}
}

View File

@ -14,9 +14,10 @@
// limitations under the License.
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::take_bucket;
use async_trait::async_trait;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::Bytes;
use http::HeaderMap;
use std::mem;
@ -29,28 +30,13 @@ use std::mem;
/// For more information, refer to the [AWS S3 DeleteObjectLockConfiguration API documentation](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjectLockConfiguration.html).
#[derive(Clone, Debug)]
pub struct DeleteObjectLockConfigResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket from which the Object Lock configuration was removed.
pub bucket: String,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for DeleteObjectLockConfigResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
impl_from_s3response!(DeleteObjectLockConfigResponse);
impl_has_s3fields!(DeleteObjectLockConfigResponse);
Ok(Self {
headers: mem::take(resp.headers_mut()),
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
})
}
}
impl HasBucket for DeleteObjectLockConfigResponse {}
impl HasRegion for DeleteObjectLockConfigResponse {}

View File

@ -14,10 +14,12 @@
// limitations under the License.
use crate::s3::error::Error;
use crate::s3::multimap::MultimapExt;
use crate::s3::response::a_response_traits::{
HasBucket, HasObject, HasRegion, HasS3Fields, HasVersion,
};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::{take_bucket, take_object};
use async_trait::async_trait;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::Bytes;
use http::HeaderMap;
use std::mem;
@ -30,38 +32,15 @@ use std::mem;
/// For more information, refer to the [AWS S3 DeleteObjectTagging API documentation](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjectTagging.html).
#[derive(Clone, Debug)]
pub struct DeleteObjectTaggingResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket containing the object.
pub bucket: String,
/// Key (name) identifying the object within the bucket.
pub object: String,
/// The version ID of the object from which the tags were removed.
///
/// If versioning is not enabled on the bucket, this field may be `None`.
pub version_id: Option<String>,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for DeleteObjectTaggingResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
impl_from_s3response!(DeleteObjectTaggingResponse);
impl_has_s3fields!(DeleteObjectTaggingResponse);
Ok(Self {
headers: mem::take(resp.headers_mut()),
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
object: take_object(req.object)?,
version_id: req.query_params.take_version(),
})
}
}
impl HasBucket for DeleteObjectTaggingResponse {}
impl HasRegion for DeleteObjectTaggingResponse {}
impl HasObject for DeleteObjectTaggingResponse {}
impl HasVersion for DeleteObjectTaggingResponse {}

View File

@ -13,11 +13,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::impl_has_s3fields;
use crate::s3::error::{Error, ErrorCode};
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request, SseConfig};
use crate::s3::utils::{get_option_text, get_text, take_bucket};
use crate::s3::utils::{get_option_text, get_text};
use async_trait::async_trait;
use bytes::Buf;
use bytes::{Buf, Bytes};
use http::HeaderMap;
use std::mem;
use xmltree::Element;
@ -32,35 +34,26 @@ use xmltree::Element;
/// For more information, refer to the [AWS S3 GetBucketEncryption API documentation](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketEncryption.html).
#[derive(Clone, Debug)]
pub struct GetBucketEncryptionResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket whose encryption configuration is retrieved.
pub bucket: String,
/// The default server-side encryption configuration of the bucket.
///
/// This includes the encryption algorithm and, if applicable, the AWS KMS key ID used for encrypting objects.
///
/// If the bucket has no default encryption configuration, the `get_bucket_encryption` API call may return an error
/// with the code `ServerSideEncryptionConfigurationNotFoundError`. It's advisable to handle this case appropriately in your application.
pub config: SseConfig,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for GetBucketEncryptionResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
match resp {
Ok(mut r) => {
let headers: HeaderMap = mem::take(r.headers_mut());
let body = r.bytes().await?;
let mut root = Element::parse(body.reader())?;
impl_has_s3fields!(GetBucketEncryptionResponse);
impl HasBucket for GetBucketEncryptionResponse {}
impl HasRegion for GetBucketEncryptionResponse {}
impl GetBucketEncryptionResponse {
/// Returns the default server-side encryption configuration of the bucket.
///
/// This includes the encryption algorithm and, if applicable, the AWS KMS key ID used for encrypting objects.
/// If the bucket has no default encryption configuration, this method returns a default `SseConfig` with empty fields.
pub fn config(&self) -> Result<SseConfig, Error> {
if self.body.is_empty() {
return Ok(SseConfig::default());
}
let mut root = Element::parse(self.body.clone().reader())?; // clone of Bytes is inexpensive
let rule = root
.get_mut_child("Rule")
@ -72,24 +65,32 @@ impl FromS3Response for GetBucketEncryptionResponse {
"<ApplyServerSideEncryptionByDefault> tag not found".into(),
))?;
Ok(Self {
headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
config: SseConfig {
Ok(SseConfig {
sse_algorithm: get_text(sse_by_default, "SSEAlgorithm")?,
kms_master_key_id: get_option_text(sse_by_default, "KMSMasterKeyID"),
},
})
}
}
#[async_trait]
impl FromS3Response for GetBucketEncryptionResponse {
async fn from_s3response(
request: S3Request,
response: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
match response {
Ok(mut resp) => Ok(Self {
request,
headers: mem::take(resp.headers_mut()),
body: resp.bytes().await?,
}),
Err(Error::S3Error(e))
if e.code == ErrorCode::ServerSideEncryptionConfigurationNotFoundError =>
{
Ok(Self {
request,
headers: e.headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
config: Default::default(),
body: Bytes::new(),
})
}
Err(e) => Err(e),

View File

@ -15,10 +15,10 @@
use crate::s3::error::Error;
use crate::s3::lifecycle_config::LifecycleConfig;
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::{UtcTime, take_bucket};
use async_trait::async_trait;
use bytes::Buf;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::{Buf, Bytes};
use chrono::{DateTime, NaiveDateTime, Utc};
use http::HeaderMap;
use std::mem;
@ -33,56 +33,36 @@ use xmltree::Element;
/// For more information, refer to the [AWS S3 GetBucketLifecycleConfiguration API documentation](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLifecycleConfiguration.html).
#[derive(Clone, Debug)]
pub struct GetBucketLifecycleResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket whose lifecycle configuration is retrieved.
pub bucket: String,
/// The lifecycle configuration of the bucket.
///
/// This includes a set of rules that define actions applied to objects, such as transitioning
/// them to different storage classes, expiring them, or aborting incomplete multipart uploads.
///
/// If the bucket has no lifecycle configuration, this field may contain an empty configuration.
pub config: LifecycleConfig,
/// Optional value of `X-Minio-LifecycleConfig-UpdatedAt` header, indicating the last update
/// time of the lifecycle configuration.
pub updated_at: Option<UtcTime>,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for GetBucketLifecycleResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
let headers: HeaderMap = mem::take(resp.headers_mut());
let config: LifecycleConfig = {
let body = resp.bytes().await?;
let root = Element::parse(body.reader())?;
LifecycleConfig::from_xml(&root)?
};
let updated_at: Option<DateTime<Utc>> = headers
impl_from_s3response!(GetBucketLifecycleResponse);
impl_has_s3fields!(GetBucketLifecycleResponse);
impl HasBucket for GetBucketLifecycleResponse {}
impl HasRegion for GetBucketLifecycleResponse {}
impl GetBucketLifecycleResponse {
/// Returns the lifecycle configuration of the bucket.
///
/// This configuration includes rules for managing the lifecycle of objects in the bucket,
/// such as transitioning them to different storage classes or expiring them after a specified period.
pub fn config(&self) -> Result<LifecycleConfig, Error> {
LifecycleConfig::from_xml(&Element::parse(self.body.clone().reader())?)
}
/// Returns the last update time of the lifecycle configuration
/// (`X-Minio-LifecycleConfig-UpdatedAt`), if available.
pub fn updated_at(&self) -> Option<DateTime<Utc>> {
self.headers
.get("x-minio-lifecycleconfig-updatedat")
.and_then(|v| v.to_str().ok())
.and_then(|v| {
NaiveDateTime::parse_from_str(v, "%Y%m%dT%H%M%SZ")
.ok()
.map(|naive| DateTime::from_naive_utc_and_offset(naive, Utc))
});
Ok(Self {
headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
config,
updated_at,
})
}
}

View File

@ -14,10 +14,10 @@
// limitations under the License.
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, NotificationConfig, S3Request};
use crate::s3::utils::take_bucket;
use async_trait::async_trait;
use bytes::Buf;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::{Buf, Bytes};
use http::HeaderMap;
use std::mem;
use xmltree::Element;
@ -31,42 +31,23 @@ use xmltree::Element;
/// For more information, refer to the [AWS S3 GetBucketNotificationConfiguration API documentation](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketNotificationConfiguration.html).
#[derive(Clone, Debug)]
pub struct GetBucketNotificationResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket whose notification configuration is retrieved.
pub bucket: String,
/// The notification configuration of the bucket.
///
/// This includes the event types and the destinations (e.g., SNS topics, SQS queues, Lambda functions)
/// configured to receive notifications for those events.
///
/// If the bucket has no notification configuration, this field may contain an empty configuration.
pub config: NotificationConfig,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for GetBucketNotificationResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
impl_from_s3response!(GetBucketNotificationResponse);
impl_has_s3fields!(GetBucketNotificationResponse);
let headers: HeaderMap = mem::take(resp.headers_mut());
let body = resp.bytes().await?;
let mut root = Element::parse(body.reader())?;
let config = NotificationConfig::from_xml(&mut root)?;
impl HasBucket for GetBucketNotificationResponse {}
impl HasRegion for GetBucketNotificationResponse {}
Ok(Self {
headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
config,
})
impl GetBucketNotificationResponse {
/// Returns the notification configuration of the bucket.
///
/// This configuration includes the event types and the destinations (e.g., SNS topics, SQS queues, Lambda functions)
/// configured to receive notifications for those events.
pub fn config(&self) -> Result<NotificationConfig, Error> {
NotificationConfig::from_xml(&mut Element::parse(self.body.clone().reader())?)
}
}

View File

@ -13,10 +13,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::impl_has_s3fields;
use crate::s3::error::{Error, ErrorCode};
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::take_bucket;
use async_trait::async_trait;
use bytes::Bytes;
use http::HeaderMap;
use std::mem;
@ -29,45 +31,44 @@ use std::mem;
/// For more information, refer to the [AWS S3 GetBucketPolicy API documentation](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketPolicy.html).
#[derive(Clone, Debug)]
pub struct GetBucketPolicyResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
/// The AWS region where the bucket resides.
pub region: String,
impl_has_s3fields!(GetBucketPolicyResponse);
/// Name of the bucket whose policy is retrieved.
pub bucket: String,
impl HasBucket for GetBucketPolicyResponse {}
impl HasRegion for GetBucketPolicyResponse {}
/// The bucket policy as a JSON-formatted string.
impl GetBucketPolicyResponse {
/// Returns the bucket policy as a JSON-formatted string.
///
/// This policy defines access permissions for the bucket. It specifies who can access the bucket,
/// what actions they can perform, and under what conditions.
///
/// For example, a policy might grant read-only access to anonymous users or restrict access to specific IP addresses.
///
/// Note: If the bucket has no policy, the `get_bucket_policy` API call may return an error
/// with the code `NoSuchBucketPolicy`. It's advisable to handle this case appropriately in your application.
pub config: String,
/// This method retrieves the policy associated with the bucket, which defines permissions
/// for accessing the bucket and its contents.
pub fn config(&self) -> Result<&str, Error> {
std::str::from_utf8(&self.body).map_err(|e| {
Error::Utf8Error(format!("Failed to parse bucket policy as UTF-8: {}", e).into())
})
}
}
#[async_trait]
impl FromS3Response for GetBucketPolicyResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
request: S3Request,
response: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
match resp {
Ok(mut r) => Ok(Self {
headers: mem::take(r.headers_mut()),
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
config: r.text().await?,
match response {
Ok(mut resp) => Ok(Self {
request,
headers: mem::take(resp.headers_mut()),
body: resp.bytes().await?,
}),
Err(Error::S3Error(e)) if e.code == ErrorCode::NoSuchBucketPolicy => Ok(Self {
request,
headers: e.headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
config: String::from("{}"),
body: Bytes::from_static("{}".as_ref()),
}),
Err(e) => Err(e),
}

View File

@ -14,10 +14,10 @@
// limitations under the License.
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, ReplicationConfig, S3Request};
use crate::s3::utils::take_bucket;
use async_trait::async_trait;
use bytes::Buf;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::{Buf, Bytes};
use http::HeaderMap;
use std::mem;
use xmltree::Element;
@ -30,42 +30,26 @@ use xmltree::Element;
/// For more information, refer to the [AWS S3 GetBucketReplication API documentation](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketReplication.html).
#[derive(Clone, Debug)]
pub struct GetBucketReplicationResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
/// The AWS region where the bucket resides.
pub region: String,
impl_from_s3response!(GetBucketReplicationResponse);
impl_has_s3fields!(GetBucketReplicationResponse);
/// Name of the bucket whose replication configuration is retrieved.
pub bucket: String,
impl HasBucket for GetBucketReplicationResponse {}
impl HasRegion for GetBucketReplicationResponse {}
/// The replication configuration of the bucket.
impl GetBucketReplicationResponse {
/// Returns the replication configuration of the bucket.
///
/// This includes the IAM role that Amazon S3 assumes to replicate objects on your behalf,
/// and one or more replication rules that specify the conditions under which objects are replicated.
///
/// For more details on replication configuration elements, see the [AWS S3 Replication Configuration documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/replication-add-config.html).
pub config: ReplicationConfig,
}
#[async_trait]
impl FromS3Response for GetBucketReplicationResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
let headers: HeaderMap = mem::take(resp.headers_mut());
let body = resp.bytes().await?;
let root = Element::parse(body.reader())?;
let config = ReplicationConfig::from_xml(&root)?;
Ok(Self {
headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
config,
})
pub fn config(&self) -> Result<ReplicationConfig, Error> {
let root = Element::parse(self.body.clone().reader())?;
ReplicationConfig::from_xml(&root)
}
}

View File

@ -13,15 +13,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::impl_has_s3fields;
use crate::s3::error::{Error, ErrorCode};
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields, HasTagging};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::{get_text, take_bucket};
use async_trait::async_trait;
use bytes::Buf;
use bytes::Bytes;
use http::HeaderMap;
use std::collections::HashMap;
use std::mem;
use xmltree::Element;
/// Response from the [`get_bucket_tagging`](crate::s3::client::Client::get_bucket_tagging) API call,
/// providing the set of tags associated with an S3 bucket.
@ -32,57 +31,33 @@ use xmltree::Element;
/// For more information, refer to the [AWS S3 GetBucketTagging API documentation](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketTagging.html).
#[derive(Clone, Debug)]
pub struct GetBucketTaggingResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket whose tags are retrieved.
pub bucket: String,
/// A collection of tags assigned to the bucket.
///
/// Each tag is a key-value pair represented as a `HashMap<String, String>`.
/// If the bucket has no tags, this map will be empty.
///
/// Note: If the bucket has no tags, the `get_bucket_tags` API call may return an error
/// with the code `NoSuchTagSet`. It's advisable to handle this case appropriately in your application.
pub tags: HashMap<String, String>,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
impl_has_s3fields!(GetBucketTaggingResponse);
impl HasBucket for GetBucketTaggingResponse {}
impl HasRegion for GetBucketTaggingResponse {}
impl HasTagging for GetBucketTaggingResponse {}
#[async_trait]
impl FromS3Response for GetBucketTaggingResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
request: S3Request,
response: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
match resp {
Ok(mut r) => {
let headers: HeaderMap = mem::take(r.headers_mut());
let body = r.bytes().await?;
let mut root = Element::parse(body.reader())?;
let element = root
.get_mut_child("TagSet")
.ok_or(Error::XmlError("<TagSet> tag not found".to_string()))?;
let mut tags = HashMap::new();
while let Some(v) = element.take_child("Tag") {
tags.insert(get_text(&v, "Key")?, get_text(&v, "Value")?);
}
Ok(Self {
headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
tags,
})
}
match response {
Ok(mut resp) => Ok(Self {
request,
headers: mem::take(resp.headers_mut()),
body: resp.bytes().await?,
}),
Err(Error::S3Error(e)) if e.code == ErrorCode::NoSuchTagSet => Ok(Self {
request,
headers: e.headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
tags: HashMap::new(),
body: Bytes::new(),
}),
Err(e) => Err(e),
}

View File

@ -15,10 +15,11 @@
use crate::s3::builders::VersioningStatus;
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::{get_option_text, take_bucket};
use async_trait::async_trait;
use bytes::Buf;
use crate::s3::utils::get_option_text;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::{Buf, Bytes};
use http::HeaderMap;
use std::mem;
use xmltree::Element;
@ -32,59 +33,40 @@ use xmltree::Element;
/// For more information, refer to the [AWS S3 GetBucketVersioning API documentation](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketVersioning.html).
#[derive(Clone, Debug)]
pub struct GetBucketVersioningResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket whose versioning configuration is retrieved.
pub bucket: String,
/// The versioning status of the bucket.
///
/// - `Some(VersioningStatus::Enabled)`: Versioning is enabled.
/// - `Some(VersioningStatus::Suspended)`: Versioning is suspended.
/// - `None`: Versioning has never been configured for this bucket.
pub status: Option<VersioningStatus>,
/// Indicates whether MFA delete is enabled for the bucket.
///
/// - `Some(true)`: MFA delete is enabled.
/// - `Some(false)`: MFA delete is disabled.
/// - `None`: MFA delete has never been configured for this bucket.
///
/// Note: MFA delete adds an extra layer of security by requiring additional authentication
/// for certain operations. For more details, see the [AWS S3 MFA Delete documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/MultiFactorAuthenticationDelete.html).
pub mfa_delete: Option<bool>,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for GetBucketVersioningResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
impl_from_s3response!(GetBucketVersioningResponse);
impl_has_s3fields!(GetBucketVersioningResponse);
let headers: HeaderMap = mem::take(resp.headers_mut());
impl HasBucket for GetBucketVersioningResponse {}
impl HasRegion for GetBucketVersioningResponse {}
let body = resp.bytes().await?;
let root = Element::parse(body.reader())?;
let status: Option<VersioningStatus> =
get_option_text(&root, "Status").map(|v| match v.as_str() {
impl GetBucketVersioningResponse {
/// Returns the versioning status of the bucket.
///
/// This method retrieves the current versioning status, which can be:
/// - `Some(VersioningStatus::Enabled)` if versioning is enabled.
/// - `Some(VersioningStatus::Suspended)` if versioning is suspended.
/// - `None` if versioning has never been configured for this bucket.
pub fn status(&self) -> Result<Option<VersioningStatus>, Error> {
let root = Element::parse(self.body.clone().reader())?;
Ok(get_option_text(&root, "Status").map(|v| match v.as_str() {
"Enabled" => VersioningStatus::Enabled,
_ => VersioningStatus::Suspended, // Default case
});
let mfa_delete: Option<bool> =
get_option_text(&root, "MFADelete").map(|v| v.eq_ignore_ascii_case("Enabled"));
}))
}
Ok(Self {
headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
status,
mfa_delete,
})
/// Returns whether MFA delete is enabled for the bucket.
///
/// This method retrieves the MFA delete setting, which can be:
/// - `Some(true)` if MFA delete is enabled.
/// - `Some(false)` if MFA delete is disabled.
/// - `None` if MFA delete has never been configured for this bucket.
pub fn mfa_delete(&self) -> Result<Option<bool>, Error> {
let root = Element::parse(self.body.clone().reader())?;
Ok(get_option_text(&root, "MFADelete").map(|v| v.eq_ignore_ascii_case("Enabled")))
}
}

View File

@ -13,75 +13,64 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::s3::utils::{take_bucket, take_object};
use crate::impl_has_s3fields;
use crate::s3::response::a_response_traits::{
HasBucket, HasEtagFromHeaders, HasObject, HasRegion, HasS3Fields, HasVersion,
};
use crate::s3::{
builders::ObjectContent,
error::Error,
types::{FromS3Response, S3Request},
};
use async_trait::async_trait;
use bytes::Bytes;
use futures_util::TryStreamExt;
use http::HeaderMap;
use std::mem;
pub struct GetObjectResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
request: S3Request,
headers: HeaderMap,
body: Bytes, // Note: not used
resp: reqwest::Response,
}
/// The AWS region where the bucket resides.
pub region: String,
impl_has_s3fields!(GetObjectResponse);
/// Name of the bucket containing the object.
pub bucket: String,
impl HasBucket for GetObjectResponse {}
impl HasRegion for GetObjectResponse {}
impl HasObject for GetObjectResponse {}
impl HasVersion for GetObjectResponse {}
impl HasEtagFromHeaders for GetObjectResponse {}
/// Key (path) identifying the object within the bucket.
pub object: String,
impl GetObjectResponse {
/// Returns the content of the object as a (streaming) byte buffer. Note: consumes the response.
pub fn content(self) -> Result<ObjectContent, Error> {
let content_length: u64 = self.object_size()?;
let body = self.resp.bytes_stream().map_err(std::io::Error::other);
Ok(ObjectContent::new_from_stream(body, Some(content_length)))
}
/// Entity tag representing a specific version of the object.
pub etag: Option<String>,
/// Version ID of the object, if versioning is enabled. Value of the `x-amz-version-id` header.
pub version_id: Option<String>,
/// The content of the object as a stream or byte buffer.
pub content: ObjectContent,
/// Size of the object in bytes.
pub object_size: u64,
/// Returns the content size (in Bytes) of the object.
pub fn object_size(&self) -> Result<u64, Error> {
self.resp
.content_length()
.ok_or(Error::ContentLengthUnknown)
}
}
#[async_trait]
impl FromS3Response for GetObjectResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
request: S3Request,
response: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
let headers: HeaderMap = mem::take(resp.headers_mut());
let etag: Option<String> = headers
.get("etag")
.and_then(|v| v.to_str().ok())
.map(|s| s.trim_matches('"').to_string());
let version_id: Option<String> = headers
.get("x-amz-version-id")
.and_then(|v| v.to_str().ok().map(String::from));
let content_length: u64 = resp.content_length().ok_or(Error::ContentLengthUnknown)?;
let body = resp.bytes_stream().map_err(std::io::Error::other);
let content = ObjectContent::new_from_stream(body, Some(content_length));
let mut resp = response?;
Ok(Self {
headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
object: take_object(req.object)?,
version_id,
content,
object_size: content_length,
etag,
request,
headers: mem::take(resp.headers_mut()),
body: Bytes::new(),
resp,
})
}
}

View File

@ -13,12 +13,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::s3::error::{Error, ErrorCode};
use crate::s3::multimap::MultimapExt;
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{
HasBucket, HasObject, HasRegion, HasS3Fields, HasVersion,
};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::{get_default_text, take_bucket, take_object};
use async_trait::async_trait;
use bytes::Buf;
use crate::s3::utils::get_default_text;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::{Buf, Bytes};
use http::HeaderMap;
use std::mem;
use xmltree::Element;
@ -28,57 +30,28 @@ use xmltree::Element;
/// API
#[derive(Clone, Debug)]
pub struct GetObjectLegalHoldResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket containing the object.
pub bucket: String,
/// Key (path) identifying the object within the bucket.
pub object: String,
/// Version ID of the object, if versioning is enabled. Value of the `x-amz-version-id` header.
pub version_id: Option<String>,
/// Indicates whether the object legal hold is enabled.
pub enabled: bool,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for GetObjectLegalHoldResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
match resp {
Ok(mut r) => {
let headers: HeaderMap = mem::take(r.headers_mut());
let body = r.bytes().await?;
let root = Element::parse(body.reader())?;
impl_from_s3response!(GetObjectLegalHoldResponse);
impl_has_s3fields!(GetObjectLegalHoldResponse);
Ok(Self {
headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
object: take_object(req.object)?,
version_id: req.query_params.take_version(),
enabled: get_default_text(&root, "Status") == "ON",
})
}
Err(Error::S3Error(e)) if e.code == ErrorCode::NoSuchObjectLockConfiguration => {
Ok(Self {
headers: e.headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
object: take_object(req.object)?,
version_id: req.query_params.take_version(),
enabled: false,
})
}
Err(e) => Err(e),
impl HasBucket for GetObjectLegalHoldResponse {}
impl HasRegion for GetObjectLegalHoldResponse {}
impl HasObject for GetObjectLegalHoldResponse {}
impl HasVersion for GetObjectLegalHoldResponse {}
impl GetObjectLegalHoldResponse {
/// Returns the legal hold status of the object.
///
/// This method retrieves whether the legal hold is enabled for the specified object.
pub fn enabled(&self) -> Result<bool, Error> {
if self.body.is_empty() {
return Ok(false); // No legal hold configuration present due to NoSuchObjectLockConfiguration
}
let root = Element::parse(self.body.clone().reader())?;
Ok(get_default_text(&root, "Status") == "ON")
}
}

View File

@ -14,10 +14,10 @@
// limitations under the License.
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{HasBucket, HasObject, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, ObjectLockConfig, S3Request};
use crate::s3::utils::take_bucket;
use async_trait::async_trait;
use bytes::Buf;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::{Buf, Bytes};
use http::HeaderMap;
use std::mem;
use xmltree::Element;
@ -29,36 +29,24 @@ use xmltree::Element;
/// helping to enforce write-once-read-many (WORM) protection.
#[derive(Clone, Debug)]
pub struct GetObjectLockConfigResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket for which the Object Lock configuration is retrieved.
pub bucket: String,
/// The Object Lock configuration of the bucket, including retention settings and legal hold status.
pub config: ObjectLockConfig,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for GetObjectLockConfigResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
impl_from_s3response!(GetObjectLockConfigResponse);
impl_has_s3fields!(GetObjectLockConfigResponse);
let headers: HeaderMap = mem::take(resp.headers_mut());
let body = resp.bytes().await?;
let root = Element::parse(body.reader())?;
impl HasBucket for GetObjectLockConfigResponse {}
impl HasRegion for GetObjectLockConfigResponse {}
impl HasObject for GetObjectLockConfigResponse {}
Ok(Self {
headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
config: ObjectLockConfig::from_xml(&root)?,
})
impl GetObjectLockConfigResponse {
/// Returns the Object Lock configuration of the bucket.
///
/// This method retrieves the Object Lock settings, which include retention mode and period,
/// as well as legal hold status for the bucket.
pub fn config(&self) -> Result<ObjectLockConfig, Error> {
ObjectLockConfig::from_xml(&Element::parse(self.body.clone().reader())?)
}
}

View File

@ -14,47 +14,33 @@
// limitations under the License.
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{HasBucket, HasObject, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::{take_bucket, take_object};
use async_trait::async_trait;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::Bytes;
use http::HeaderMap;
use std::mem;
pub struct GetObjectPromptResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket containing the object.
pub bucket: String,
/// Key (path) identifying the object within the bucket.
pub object: String,
/// The prompt response for the object.
pub prompt_response: String,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for GetObjectPromptResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
impl_from_s3response!(GetObjectPromptResponse);
impl_has_s3fields!(GetObjectPromptResponse);
let headers: HeaderMap = mem::take(resp.headers_mut());
let body = resp.bytes().await?;
let prompt_response: String = String::from_utf8(body.to_vec())?;
impl HasBucket for GetObjectPromptResponse {}
impl HasRegion for GetObjectPromptResponse {}
impl HasObject for GetObjectPromptResponse {}
Ok(Self {
headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
object: take_object(req.object)?,
prompt_response,
impl GetObjectPromptResponse {
/// Returns the prompt response for the object.
///
/// This method retrieves the content of the object as a UTF-8 encoded string.
pub fn prompt_response(&self) -> Result<&str, Error> {
std::str::from_utf8(&self.body).map_err(|e| {
Error::Utf8Error(format!("Failed to parse prompt_response as UTF-8: {}", e).into())
})
}
}

View File

@ -13,12 +13,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::impl_has_s3fields;
use crate::s3::error::{Error, ErrorCode};
use crate::s3::multimap::MultimapExt;
use crate::s3::response::a_response_traits::{
HasBucket, HasObject, HasRegion, HasS3Fields, HasVersion,
};
use crate::s3::types::{FromS3Response, RetentionMode, S3Request};
use crate::s3::utils::{UtcTime, from_iso8601utc, get_option_text, take_bucket, take_object};
use crate::s3::utils::{UtcTime, from_iso8601utc, get_option_text};
use async_trait::async_trait;
use bytes::Buf;
use bytes::{Buf, Bytes};
use http::HeaderMap;
use std::mem;
use xmltree::Element;
@ -26,67 +29,65 @@ use xmltree::Element;
/// Response of [get_object_retention()](crate::s3::client::Client::get_object_retention) API
#[derive(Clone, Debug)]
pub struct GetObjectRetentionResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
/// The AWS region where the bucket resides.
pub region: String,
impl_has_s3fields!(GetObjectRetentionResponse);
/// Name of the bucket containing the object.
pub bucket: String,
impl HasBucket for GetObjectRetentionResponse {}
impl HasRegion for GetObjectRetentionResponse {}
impl HasObject for GetObjectRetentionResponse {}
impl HasVersion for GetObjectRetentionResponse {}
/// Key (path) identifying the object within the bucket.
pub object: String,
impl GetObjectRetentionResponse {
/// Returns the retention mode of the object.
///
/// This method retrieves the retention mode, which can be either `Governance` or `Compliance`.
pub fn retention_mode(&self) -> Result<Option<RetentionMode>, Error> {
if self.body.is_empty() {
return Ok(None);
}
let root = Element::parse(self.body.clone().reader())?;
Ok(match get_option_text(&root, "Mode") {
Some(v) => Some(RetentionMode::parse(&v)?),
_ => None,
})
}
/// Version ID of the object, if versioning is enabled. Value of the `x-amz-version-id` header.
pub version_id: Option<String>,
/// The retention mode of the object.
pub retention_mode: Option<RetentionMode>,
/// The date until which the object is retained.
pub retain_until_date: Option<UtcTime>,
/// Returns the date until which the object is retained.
///
/// This method retrieves the retention date, which indicates when the object will no longer be retained.
pub fn retain_until_date(&self) -> Result<Option<UtcTime>, Error> {
if self.body.is_empty() {
return Ok(None);
}
let root = Element::parse(self.body.clone().reader())?;
Ok(match get_option_text(&root, "RetainUntilDate") {
Some(v) => Some(from_iso8601utc(&v)?),
_ => None,
})
}
}
#[async_trait]
impl FromS3Response for GetObjectRetentionResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
request: S3Request,
response: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
match resp {
Ok(mut r) => {
let headers = mem::take(r.headers_mut());
let body = r.bytes().await?;
let root = Element::parse(body.reader())?;
let retention_mode = match get_option_text(&root, "Mode") {
Some(v) => Some(RetentionMode::parse(&v)?),
_ => None,
};
let retain_until_date = match get_option_text(&root, "RetainUntilDate") {
Some(v) => Some(from_iso8601utc(&v)?),
_ => None,
};
Ok(Self {
headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
object: take_object(req.object)?,
version_id: req.query_params.take_version(),
retention_mode,
retain_until_date,
})
}
match response {
Ok(mut resp) => Ok(Self {
request,
headers: mem::take(resp.headers_mut()),
body: resp.bytes().await?,
}),
Err(Error::S3Error(e)) if e.code == ErrorCode::NoSuchObjectLockConfiguration => {
Ok(Self {
request,
headers: e.headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
object: take_object(req.object)?,
version_id: req.query_params.take_version(),
retention_mode: None,
retain_until_date: None,
body: Bytes::new(),
})
}
Err(e) => Err(e),

View File

@ -14,67 +14,30 @@
// limitations under the License.
use crate::s3::error::Error;
use crate::s3::multimap::MultimapExt;
use crate::s3::response::a_response_traits::{
HasBucket, HasObject, HasRegion, HasS3Fields, HasTagging, HasVersion,
};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::{get_text, take_bucket, take_object};
use async_trait::async_trait;
use bytes::Buf;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::Bytes;
use http::HeaderMap;
use std::collections::HashMap;
use std::mem;
use xmltree::Element;
/// Response of
/// [get_object_tags()](crate::s3::client::Client::get_object_tagging)
/// API
#[derive(Clone, Debug)]
pub struct GetObjectTaggingResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket containing the object.
pub bucket: String,
/// Key (path) identifying the object within the bucket.
pub object: String,
/// Version ID of the object, if versioning is enabled. Value of the `x-amz-version-id` header.
pub version_id: Option<String>,
/// Tags associated with the object.
pub tags: HashMap<String, String>,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for GetObjectTaggingResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
impl_from_s3response!(GetObjectTaggingResponse);
impl_has_s3fields!(GetObjectTaggingResponse);
let headers: HeaderMap = mem::take(resp.headers_mut());
let body = resp.bytes().await?;
let mut root = Element::parse(body.reader())?;
let element = root
.get_mut_child("TagSet")
.ok_or(Error::XmlError("<TagSet> tag not found".to_string()))?;
let mut tags = HashMap::new();
while let Some(v) = element.take_child("Tag") {
tags.insert(get_text(&v, "Key")?, get_text(&v, "Value")?);
}
Ok(Self {
headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
object: take_object(req.object)?,
version_id: req.query_params.take_version(),
tags,
})
}
}
impl HasBucket for GetObjectTaggingResponse {}
impl HasRegion for GetObjectTaggingResponse {}
impl HasObject for GetObjectTaggingResponse {}
impl HasVersion for GetObjectTaggingResponse {}
impl HasTagging for GetObjectTaggingResponse {}

View File

@ -15,10 +15,10 @@
use crate::s3::client::DEFAULT_REGION;
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::take_bucket;
use async_trait::async_trait;
use bytes::Buf;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::{Buf, Bytes};
use http::HeaderMap;
use std::mem;
use xmltree::Element;
@ -28,44 +28,28 @@ use xmltree::Element;
/// API
#[derive(Clone, Debug)]
pub struct GetRegionResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket containing the object.
pub bucket: String,
/// The region response for the bucket.
pub region_response: String,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for GetRegionResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
impl_from_s3response!(GetRegionResponse);
impl_has_s3fields!(GetRegionResponse);
let headers: HeaderMap = mem::take(resp.headers_mut());
let region_response: String = {
let body = resp.bytes().await?;
let root = Element::parse(body.reader())?;
impl HasBucket for GetRegionResponse {}
impl HasRegion for GetRegionResponse {}
impl GetRegionResponse {
/// Returns the region response for the bucket.
///
/// This method retrieves the region where the bucket is located.
pub fn region_response(&self) -> Result<String, Error> {
let root = Element::parse(self.body.clone().reader())?;
let mut location = root.get_text().unwrap_or_default().to_string();
if location.is_empty() {
location = String::from(DEFAULT_REGION);
}
location
};
Ok(Self {
headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
region_response,
})
Ok(location)
}
}

View File

@ -14,10 +14,11 @@
// limitations under the License.
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::HasS3Fields;
use crate::s3::types::{Bucket, FromS3Response, S3Request};
use crate::s3::utils::{from_iso8601utc, get_text};
use async_trait::async_trait;
use bytes::Buf;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::{Buf, Bytes};
use http::HeaderMap;
use std::mem;
use xmltree::Element;
@ -25,24 +26,18 @@ use xmltree::Element;
/// Response of [list_buckets()](crate::s3::client::Client::list_buckets) API
#[derive(Debug, Clone)]
pub struct ListBucketsResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// the list of buckets that are present in the account.
pub buckets: Vec<Bucket>,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for ListBucketsResponse {
async fn from_s3response(
_req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
let headers: HeaderMap = mem::take(resp.headers_mut());
impl_from_s3response!(ListBucketsResponse);
impl_has_s3fields!(ListBucketsResponse);
let body = resp.bytes().await?;
let mut root = Element::parse(body.reader())?;
impl ListBucketsResponse {
/// Returns the list of buckets in the account.
pub fn buckets(&self) -> Result<Vec<Bucket>, Error> {
let mut root = Element::parse(self.body().clone().reader())?;
let buckets_xml = root
.get_mut_child("Buckets")
.ok_or(Error::XmlError("<Buckets> tag not found".into()))?;
@ -55,7 +50,6 @@ impl FromS3Response for ListBucketsResponse {
creation_date: from_iso8601utc(&get_text(&bucket, "CreationDate")?)?,
})
}
Ok(Self { headers, buckets })
Ok(buckets)
}
}

View File

@ -12,12 +12,8 @@
//! Response types for ListObjects APIs
use async_trait::async_trait;
use bytes::Buf;
use reqwest::header::HeaderMap;
use std::collections::HashMap;
use std::mem;
use crate::impl_has_s3fields;
use crate::s3::response::a_response_traits::HasS3Fields;
use crate::s3::{
error::Error,
types::{FromS3Response, ListEntry, S3Request},
@ -26,6 +22,11 @@ use crate::s3::{
xml::{Element, MergeXmlElements},
},
};
use async_trait::async_trait;
use bytes::{Buf, Bytes};
use reqwest::header::HeaderMap;
use std::collections::HashMap;
use std::mem;
fn url_decode(
encoding_type: &Option<String>,
@ -188,8 +189,10 @@ fn parse_list_objects_common_prefixes(
/// Response of [list_objects_v1()](crate::s3::client::Client::list_objects_v1) S3 API
#[derive(Clone, Debug)]
pub struct ListObjectsV1Response {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
request: S3Request,
headers: HeaderMap,
body: Bytes,
pub name: String,
pub encoding_type: Option<String>,
pub prefix: Option<String>,
@ -201,18 +204,20 @@ pub struct ListObjectsV1Response {
pub next_marker: Option<String>,
}
impl_has_s3fields!(ListObjectsV1Response);
#[async_trait]
impl FromS3Response for ListObjectsV1Response {
async fn from_s3response(
_req: S3Request,
resp: Result<reqwest::Response, Error>,
request: S3Request,
response: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
let mut resp = response?;
let headers: HeaderMap = mem::take(resp.headers_mut());
let body = resp.bytes().await?;
let xmltree_root = xmltree::Element::parse(body.reader())?;
let root = Element::from(&xmltree_root);
let xmltree_root = xmltree::Element::parse(body.clone().reader())?;
let root = Element::from(&xmltree_root);
let (name, encoding_type, prefix, delimiter, is_truncated, max_keys) =
parse_common_list_objects_response(&root)?;
let marker = url_decode(&encoding_type, root.get_child_text("Marker"))?;
@ -224,8 +229,11 @@ impl FromS3Response for ListObjectsV1Response {
}
parse_list_objects_common_prefixes(&mut contents, &root, &encoding_type)?;
Ok(ListObjectsV1Response {
Ok(Self {
request,
headers,
body,
name,
encoding_type,
prefix,
@ -242,8 +250,10 @@ impl FromS3Response for ListObjectsV1Response {
/// Response of [list_objects_v2()](crate::s3::client::Client::list_objects_v2) S3 API
#[derive(Clone, Debug)]
pub struct ListObjectsV2Response {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
request: S3Request,
headers: HeaderMap,
body: Bytes,
pub name: String,
pub encoding_type: Option<String>,
pub prefix: Option<String>,
@ -257,19 +267,20 @@ pub struct ListObjectsV2Response {
pub next_continuation_token: Option<String>,
}
impl_has_s3fields!(ListObjectsV2Response);
#[async_trait]
impl FromS3Response for ListObjectsV2Response {
async fn from_s3response(
_req: S3Request,
resp: Result<reqwest::Response, Error>,
request: S3Request,
response: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
let mut resp = response?;
let headers: HeaderMap = mem::take(resp.headers_mut());
let body = resp.bytes().await?;
let xmltree_root = xmltree::Element::parse(body.reader())?;
let root = Element::from(&xmltree_root);
let xmltree_root = xmltree::Element::parse(body.clone().reader())?;
let root = Element::from(&xmltree_root);
let (name, encoding_type, prefix, delimiter, is_truncated, max_keys) =
parse_common_list_objects_response(&root)?;
let key_count = root
@ -283,8 +294,11 @@ impl FromS3Response for ListObjectsV2Response {
parse_list_objects_contents(&mut contents, &root, "Contents", &encoding_type, false)?;
parse_list_objects_common_prefixes(&mut contents, &root, &encoding_type)?;
Ok(ListObjectsV2Response {
Ok(Self {
request,
headers,
body,
name,
encoding_type,
prefix,
@ -303,8 +317,10 @@ impl FromS3Response for ListObjectsV2Response {
/// Response of [list_object_versions()](crate::s3::client::Client::list_object_versions) S3 API
#[derive(Clone, Debug)]
pub struct ListObjectVersionsResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
request: S3Request,
headers: HeaderMap,
body: Bytes,
pub name: String,
pub encoding_type: Option<String>,
pub prefix: Option<String>,
@ -318,18 +334,20 @@ pub struct ListObjectVersionsResponse {
pub next_version_id_marker: Option<String>,
}
impl_has_s3fields!(ListObjectVersionsResponse);
#[async_trait]
impl FromS3Response for ListObjectVersionsResponse {
async fn from_s3response(
_req: S3Request,
resp: Result<reqwest::Response, Error>,
request: S3Request,
response: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let resp = resp?;
let headers = resp.headers().clone();
let mut resp = response?;
let headers: HeaderMap = mem::take(resp.headers_mut());
let body = resp.bytes().await?;
let xmltree_root = xmltree::Element::parse(body.reader())?;
let root = Element::from(&xmltree_root);
let xmltree_root = xmltree::Element::parse(body.clone().reader())?;
let root = Element::from(&xmltree_root);
let (name, encoding_type, prefix, delimiter, is_truncated, max_keys) =
parse_common_list_objects_response(&root)?;
let key_marker = url_decode(&encoding_type, root.get_child_text("KeyMarker"))?;
@ -340,8 +358,11 @@ impl FromS3Response for ListObjectVersionsResponse {
parse_list_objects_contents(&mut contents, &root, "Version", &encoding_type, true)?;
parse_list_objects_common_prefixes(&mut contents, &root, &encoding_type)?;
Ok(ListObjectVersionsResponse {
Ok(Self {
request,
headers,
body,
name,
encoding_type,
prefix,
@ -360,8 +381,10 @@ impl FromS3Response for ListObjectVersionsResponse {
/// Response of [list_objects()](crate::s3::client::Client::list_objects) API
#[derive(Clone, Debug, Default)]
pub struct ListObjectsResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
request: S3Request,
headers: HeaderMap,
body: Bytes,
pub name: String,
pub encoding_type: Option<String>,
pub prefix: Option<String>,
@ -387,10 +410,15 @@ pub struct ListObjectsResponse {
pub next_version_id_marker: Option<String>,
}
impl_has_s3fields!(ListObjectsResponse);
impl From<ListObjectVersionsResponse> for ListObjectsResponse {
fn from(value: ListObjectVersionsResponse) -> Self {
ListObjectsResponse {
Self {
request: value.request,
headers: value.headers,
body: value.body,
name: value.name,
encoding_type: value.encoding_type,
prefix: value.prefix,
@ -409,8 +437,11 @@ impl From<ListObjectVersionsResponse> for ListObjectsResponse {
impl From<ListObjectsV2Response> for ListObjectsResponse {
fn from(value: ListObjectsV2Response) -> Self {
ListObjectsResponse {
Self {
request: value.request,
headers: value.headers,
body: value.body,
name: value.name,
encoding_type: value.encoding_type,
prefix: value.prefix,
@ -430,7 +461,10 @@ impl From<ListObjectsV2Response> for ListObjectsResponse {
impl From<ListObjectsV1Response> for ListObjectsResponse {
fn from(value: ListObjectsV1Response) -> Self {
Self {
request: value.request,
headers: value.headers,
body: value.body,
name: value.name,
encoding_type: value.encoding_type,
prefix: value.prefix,

View File

@ -13,28 +13,31 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::impl_has_s3fields;
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, NotificationRecords, S3Request};
use crate::s3::utils::take_bucket;
use futures_util::{Stream, StreamExt, TryStreamExt};
use async_std::stream::Stream;
use bytes::Bytes;
use futures_util::{StreamExt, TryStreamExt};
use http::HeaderMap;
use std::mem;
/// Response of
/// [listen _bucket_notification()](crate::s3::client::Client::listen_bucket_notification)
/// [listen_bucket_notification()](crate::s3::client::Client::listen_bucket_notification)
/// API
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct ListenBucketNotificationResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket containing the object.
pub bucket: String,
request: S3Request,
headers: HeaderMap,
body: Bytes, // Note: not used
}
impl_has_s3fields!(ListenBucketNotificationResponse);
impl HasBucket for ListenBucketNotificationResponse {}
impl HasRegion for ListenBucketNotificationResponse {}
#[async_trait::async_trait]
impl FromS3Response
for (
@ -43,14 +46,13 @@ impl FromS3Response
)
{
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
request: S3Request,
response: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
let headers: HeaderMap = mem::take(resp.headers_mut());
let mut resp = response?;
// A simple stateful decoder that buffers bytes and yields complete lines
let byte_stream = resp.bytes_stream(); // This is a futures::Stream<Item = Result<Bytes, reqwest::Error>>
let headers: HeaderMap = mem::take(resp.headers_mut());
let byte_stream = resp.bytes_stream();
let line_stream = Box::pin(async_stream::try_stream! {
let mut buf = Vec::new();
@ -94,9 +96,9 @@ impl FromS3Response
Ok((
ListenBucketNotificationResponse {
request,
headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
body: Bytes::new(),
},
Box::new(line_stream),
))

View File

@ -14,10 +14,11 @@
// limitations under the License.
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request, SseConfig};
use crate::s3::utils::{get_option_text, get_text, take_bucket};
use async_trait::async_trait;
use bytes::Buf;
use crate::s3::utils::{get_option_text, get_text};
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::{Buf, Bytes};
use http::HeaderMap;
use std::mem;
use xmltree::Element;
@ -27,30 +28,21 @@ use xmltree::Element;
/// API
#[derive(Clone, Debug)]
pub struct PutBucketEncryptionResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket containing the object.
pub bucket: String,
/// Server-side encryption configuration.
pub config: SseConfig,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for PutBucketEncryptionResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
impl_from_s3response!(PutBucketEncryptionResponse);
impl_has_s3fields!(PutBucketEncryptionResponse);
let headers: HeaderMap = mem::take(resp.headers_mut());
let body = resp.bytes().await?;
let mut root = Element::parse(body.reader())?;
impl HasBucket for PutBucketEncryptionResponse {}
impl HasRegion for PutBucketEncryptionResponse {}
impl PutBucketEncryptionResponse {
/// Returns the server-side encryption configuration.
pub fn config(&self) -> Result<SseConfig, Error> {
let mut root = Element::parse(self.body().clone().reader())?;
let rule = root
.get_mut_child("Rule")
@ -62,14 +54,9 @@ impl FromS3Response for PutBucketEncryptionResponse {
"<ApplyServerSideEncryptionByDefault> tag not found",
)))?;
Ok(Self {
headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
config: SseConfig {
Ok(SseConfig {
sse_algorithm: get_text(sse_by_default, "SSEAlgorithm")?,
kms_master_key_id: get_option_text(sse_by_default, "KMSMasterKeyID"),
},
})
}
}

View File

@ -14,37 +14,23 @@
// limitations under the License.
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::take_bucket;
use async_trait::async_trait;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::Bytes;
use http::HeaderMap;
use std::mem;
/// Response of [put_bucket_lifecycle()](crate::s3::client::Client::put_bucket_lifecycle) API
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct PutBucketLifecycleResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket containing the object.
pub bucket: String,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for PutBucketLifecycleResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
impl_from_s3response!(PutBucketLifecycleResponse);
impl_has_s3fields!(PutBucketLifecycleResponse);
Ok(Self {
headers: mem::take(resp.headers_mut()),
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
})
}
}
impl HasBucket for PutBucketLifecycleResponse {}
impl HasRegion for PutBucketLifecycleResponse {}

View File

@ -14,37 +14,23 @@
// limitations under the License.
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::take_bucket;
use async_trait::async_trait;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::Bytes;
use http::HeaderMap;
use std::mem;
/// Response of [put_bucket_notification()](crate::s3::client::Client::put_bucket_notification) API
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct PutBucketNotificationResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket containing the object.
pub bucket: String,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for PutBucketNotificationResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
impl_from_s3response!(PutBucketNotificationResponse);
impl_has_s3fields!(PutBucketNotificationResponse);
Ok(Self {
headers: mem::take(resp.headers_mut()),
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
})
}
}
impl HasBucket for PutBucketNotificationResponse {}
impl HasRegion for PutBucketNotificationResponse {}

View File

@ -14,37 +14,23 @@
// limitations under the License.
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::take_bucket;
use async_trait::async_trait;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::Bytes;
use http::HeaderMap;
use std::mem;
/// Response of [put_bucket_policy()](crate::s3::client::Client::put_bucket_policy) API
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct PutBucketPolicyResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket containing the object.
pub bucket: String,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for PutBucketPolicyResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
impl_from_s3response!(PutBucketPolicyResponse);
impl_has_s3fields!(PutBucketPolicyResponse);
Ok(Self {
headers: mem::take(resp.headers_mut()),
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
})
}
}
impl HasBucket for PutBucketPolicyResponse {}
impl HasRegion for PutBucketPolicyResponse {}

View File

@ -14,37 +14,23 @@
// limitations under the License.
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::take_bucket;
use async_trait::async_trait;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::Bytes;
use http::HeaderMap;
use std::mem;
/// Response of [put_bucket_replication()](crate::s3::client::Client::put_bucket_replication) API
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct PutBucketReplicationResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket containing the object.
pub bucket: String,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for PutBucketReplicationResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
impl_from_s3response!(PutBucketReplicationResponse);
impl_has_s3fields!(PutBucketReplicationResponse);
Ok(Self {
headers: mem::take(resp.headers_mut()),
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
})
}
}
impl HasBucket for PutBucketReplicationResponse {}
impl HasRegion for PutBucketReplicationResponse {}

View File

@ -14,9 +14,10 @@
// limitations under the License.
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::take_bucket;
use async_trait::async_trait;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::Bytes;
use http::HeaderMap;
use std::mem;
@ -25,28 +26,13 @@ use std::mem;
/// API
#[derive(Clone, Debug)]
pub struct PutBucketTaggingResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket containing the object.
pub bucket: String,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for PutBucketTaggingResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
impl_from_s3response!(PutBucketTaggingResponse);
impl_has_s3fields!(PutBucketTaggingResponse);
Ok(Self {
headers: mem::take(resp.headers_mut()),
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
})
}
}
impl HasBucket for PutBucketTaggingResponse {}
impl HasRegion for PutBucketTaggingResponse {}

View File

@ -14,37 +14,23 @@
// limitations under the License.
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{HasBucket, HasRegion, HasS3Fields};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::take_bucket;
use async_trait::async_trait;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::Bytes;
use http::HeaderMap;
use std::mem;
/// Response of [put_bucket_versioning()](crate::s3::client::Client::put_bucket_versioning) API
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct PutBucketVersioningResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket containing the object.
pub bucket: String,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for PutBucketVersioningResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
impl_from_s3response!(PutBucketVersioningResponse);
impl_has_s3fields!(PutBucketVersioningResponse);
Ok(Self {
headers: mem::take(resp.headers_mut()),
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
})
}
}
impl HasBucket for PutBucketVersioningResponse {}
impl HasRegion for PutBucketVersioningResponse {}

View File

@ -1,5 +1,5 @@
// MinIO Rust Library for Amazon S3 Compatible Cloud Storage
// Copyright 2023 MinIO, Inc.
// 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.
@ -13,126 +13,112 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use async_trait::async_trait;
use bytes::Buf;
use crate::s3::error::Error;
use crate::s3::response::a_response_traits::{
HasBucket, HasEtagFromHeaders, HasObject, HasRegion, HasS3Fields, HasVersion,
};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::get_text;
use crate::{impl_from_s3response, impl_from_s3response_with_size, impl_has_s3fields};
use bytes::{Buf, Bytes};
use http::HeaderMap;
use std::mem;
use xmltree::Element;
use crate::s3::utils::{take_bucket, take_object};
use crate::s3::{
error::Error,
types::{FromS3Response, S3Request},
utils::get_text,
};
// region
/// Base response struct that contains common functionality for S3 operations
#[derive(Clone, Debug)]
pub struct S3Response1 {
pub(crate) request: S3Request,
pub(crate) headers: HeaderMap,
pub(crate) body: Bytes,
}
impl_from_s3response!(S3Response1);
impl_has_s3fields!(S3Response1);
impl HasBucket for S3Response1 {}
impl HasObject for S3Response1 {}
impl HasRegion for S3Response1 {}
impl HasVersion for S3Response1 {}
impl HasEtagFromHeaders for S3Response1 {}
/// Extended response struct for operations that need additional data like object size
#[derive(Clone, Debug)]
pub struct S3Response1WithSize {
request: S3Request,
headers: HeaderMap,
body: Bytes,
/// Additional object size information
pub(crate) object_size: u64,
}
impl_from_s3response_with_size!(S3Response1WithSize);
impl_has_s3fields!(S3Response1WithSize);
impl HasBucket for S3Response1WithSize {}
impl HasObject for S3Response1WithSize {}
impl HasRegion for S3Response1WithSize {}
impl HasVersion for S3Response1WithSize {}
impl HasEtagFromHeaders for S3Response1WithSize {}
impl S3Response1WithSize {
pub fn new(response: S3Response1, object_size: u64) -> Self {
Self {
request: response.request,
headers: response.headers,
body: response.body,
object_size,
}
}
/// Returns the object size for the response
pub fn object_size(&self) -> u64 {
self.object_size
}
}
/// Extended response struct for multipart operations that need upload_id
#[derive(Clone, Debug)]
pub struct S3MultipartResponse {
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
impl_from_s3response!(S3MultipartResponse);
impl_has_s3fields!(S3MultipartResponse);
impl HasBucket for S3MultipartResponse {}
impl HasObject for S3MultipartResponse {}
impl HasRegion for S3MultipartResponse {}
impl HasVersion for S3MultipartResponse {}
impl HasEtagFromHeaders for S3MultipartResponse {}
impl S3MultipartResponse {
/// Returns the upload ID for the multipart upload, while consuming the response.
pub async fn upload_id(&self) -> Result<String, Error> {
let root = Element::parse(self.body.clone().reader())?;
get_text(&root, "UploadId").map_err(|e| Error::InvalidUploadId(e.to_string()))
}
}
/// Response of [put_object_api()](crate::s3::client::Client::put_object) API
#[derive(Debug, Clone)]
pub struct PutObjectResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
pub bucket: String,
pub object: String,
pub region: String,
pub etag: String,
pub version_id: Option<String>,
}
#[async_trait]
impl FromS3Response for PutObjectResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
let headers: HeaderMap = mem::take(resp.headers_mut());
let etag: String = headers
.get("etag")
.and_then(|v| v.to_str().ok()) // Convert to &str safely
.map(|s| s.trim_matches('"').to_string()) // Trim and convert to String
.unwrap_or_default();
let version_id: Option<String> = headers
.get("x-amz-version-id")
.and_then(|v| v.to_str().ok().map(String::from));
Ok(Self {
headers,
bucket: take_bucket(req.bucket)?,
object: take_object(req.object)?,
region: req.inner_region,
etag,
version_id,
})
}
}
pub type PutObjectResponse = S3Response1;
/// Response of [create_multipart_upload()](crate::s3::client::Client::create_multipart_upload) API
#[derive(Debug, Clone)]
pub struct CreateMultipartUploadResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
pub region: String,
pub bucket: String,
pub object: String,
pub upload_id: String,
}
#[async_trait]
impl FromS3Response for CreateMultipartUploadResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
let headers: HeaderMap = mem::take(resp.headers_mut());
let body = resp.bytes().await?;
let root = Element::parse(body.reader())?;
let upload_id: String =
get_text(&root, "UploadId").map_err(|e| Error::InvalidUploadId(e.to_string()))?;
Ok(Self {
headers,
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
object: take_object(req.object)?,
upload_id,
})
}
}
pub type CreateMultipartUploadResponse = S3MultipartResponse;
/// Response of [abort_multipart_upload()](crate::s3::client::Client::abort_multipart_upload) API
pub type AbortMultipartUploadResponse = CreateMultipartUploadResponse;
pub type AbortMultipartUploadResponse = S3MultipartResponse;
/// Response of [complete_multipart_upload()](crate::s3::client::Client::complete_multipart_upload) API
pub type CompleteMultipartUploadResponse = PutObjectResponse;
pub type CompleteMultipartUploadResponse = S3Response1;
/// Response of [upload_part()](crate::s3::client::Client::upload_part) API
pub type UploadPartResponse = PutObjectResponse;
pub type UploadPartResponse = S3Response1;
#[derive(Debug, Clone)]
pub struct PutObjectContentResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket containing the object.
pub bucket: String,
/// Key (path) identifying the object within the bucket.
pub object: String,
/// Size of the object in bytes.
pub object_size: u64,
/// Entity tag representing a specific version of the object.
pub etag: String,
/// Version ID of the object, if versioning is enabled. Value of the `x-amz-version-id` header.
pub version_id: Option<String>,
}
/// Response for put_object operations that include object size information
pub type PutObjectContentResponse = S3Response1WithSize;

View File

@ -14,10 +14,12 @@
// limitations under the License.
use crate::s3::error::Error;
use crate::s3::multimap::MultimapExt;
use crate::s3::response::a_response_traits::{
HasBucket, HasObject, HasRegion, HasS3Fields, HasVersion,
};
use crate::s3::types::{FromS3Response, S3Request};
use crate::s3::utils::{take_bucket, take_object};
use async_trait::async_trait;
use crate::{impl_from_s3response, impl_has_s3fields};
use bytes::Bytes;
use http::HeaderMap;
use std::mem;
@ -30,38 +32,15 @@ use std::mem;
/// For more information, refer to the [AWS S3 PutObjectLegalHold API documentation](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectLegalHold.html).
#[derive(Clone, Debug)]
pub struct PutObjectLegalHoldResponse {
/// HTTP headers returned by the server, containing metadata such as `Content-Type`, `ETag`, etc.
pub headers: HeaderMap,
/// The AWS region where the bucket resides.
pub region: String,
/// Name of the bucket containing the object.
pub bucket: String,
/// Key (name) identifying the object within the bucket.
pub object: String,
/// The version ID of the object from which the legal hold was removed.
///
/// If versioning is not enabled on the bucket, this field may be `None`.
pub version_id: Option<String>,
request: S3Request,
headers: HeaderMap,
body: Bytes,
}
#[async_trait]
impl FromS3Response for PutObjectLegalHoldResponse {
async fn from_s3response(
req: S3Request,
resp: Result<reqwest::Response, Error>,
) -> Result<Self, Error> {
let mut resp = resp?;
impl_from_s3response!(PutObjectLegalHoldResponse);
impl_has_s3fields!(PutObjectLegalHoldResponse);
Ok(Self {
headers: mem::take(resp.headers_mut()),
region: req.inner_region,
bucket: take_bucket(req.bucket)?,
object: take_object(req.object)?,
version_id: req.query_params.take_version(),
})
}
}
impl HasBucket for PutObjectLegalHoldResponse {}
impl HasRegion for PutObjectLegalHoldResponse {}
impl HasObject for PutObjectLegalHoldResponse {}
impl HasVersion for PutObjectLegalHoldResponse {}

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