From 8fe4f42ca9b50ddc310bb79091728b3ee4f98a73 Mon Sep 17 00:00:00 2001 From: durch Date: Mon, 2 Sep 2024 16:15:16 +0200 Subject: [PATCH] GetObjectAttributes WIP --- s3/src/bucket.rs | 35 ++++++++++++-- s3/src/command.rs | 8 ++++ s3/src/request/request_trait.rs | 39 ++++++++++++++- s3/src/request/tokio_backend.rs | 2 + s3/src/serde_types.rs | 85 +++++++++++++++++++++++++++++++++ 5 files changed, 165 insertions(+), 4 deletions(-) diff --git a/s3/src/bucket.rs b/s3/src/bucket.rs index 5c6aa70de6..551834d22e 100644 --- a/s3/src/bucket.rs +++ b/s3/src/bucket.rs @@ -89,8 +89,8 @@ use crate::error::S3Error; use crate::post_policy::PresignedPost; use crate::serde_types::{ BucketLifecycleConfiguration, BucketLocationResult, CompleteMultipartUploadData, - CorsConfiguration, HeadObjectResult, InitiateMultipartUploadResponse, ListBucketResult, - ListMultipartUploadsResult, Part, + CorsConfiguration, GetObjectAttributesOutput, HeadObjectResult, + InitiateMultipartUploadResponse, ListBucketResult, ListMultipartUploadsResult, Part, }; #[allow(unused_imports)] use crate::utils::{error_from_response_data, PutStreamResponse}; @@ -929,6 +929,26 @@ impl Bucket { request.response_data(false).await } + #[maybe_async::maybe_async] + pub async fn get_object_attributes>( + &self, + path: S, + expected_bucket_owner: &str, + version_id: Option, + ) -> Result { + let command = Command::GetObjectAttributes { + expected_bucket_owner: expected_bucket_owner.to_string(), + version_id, + }; + let request = RequestImpl::new(self, path.as_ref(), command).await?; + + let response = request.response_data(false).await?; + + Ok(quick_xml::de::from_str::( + response.as_str()?, + )?) + } + /// Checks if an object exists at the specified S3 path. /// /// # Example: @@ -2814,6 +2834,12 @@ mod test { let response_data = bucket.put_object(s3_path, &test).await.unwrap(); assert_eq!(response_data.status_code(), 200); + + // let attributes = bucket + // .get_object_attributes(s3_path, "904662384344", None) + // .await + // .unwrap(); + let response_data = bucket.get_object(s3_path).await.unwrap(); assert_eq!(response_data.status_code(), 200); assert_eq!(test, response_data.as_slice()); @@ -3579,7 +3605,10 @@ mod test { let cors_response = bucket.get_bucket_cors(expected_bucket_owner).await.unwrap(); assert_eq!(cors_response, cors_config); - let response = bucket.delete_bucket_cors(expected_bucket_owner).await.unwrap(); + let response = bucket + .delete_bucket_cors(expected_bucket_owner) + .await + .unwrap(); assert_eq!(response.status_code(), 204); } } diff --git a/s3/src/command.rs b/s3/src/command.rs index 47fbe1fb6f..ca0b2b10ea 100644 --- a/s3/src/command.rs +++ b/s3/src/command.rs @@ -165,6 +165,10 @@ pub enum Command<'a> { configuration: BucketLifecycleConfiguration, }, DeleteBucketLifecycle, + GetObjectAttributes { + expected_bucket_owner: String, + version_id: Option, + }, } impl<'a> Command<'a> { @@ -201,6 +205,7 @@ impl<'a> Command<'a> { HttpMethod::Post } Command::HeadObject => HttpMethod::Head, + Command::GetObjectAttributes { .. } => HttpMethod::Get, } } @@ -246,6 +251,7 @@ impl<'a> Command<'a> { Command::DeleteBucketCors { .. } => 0, Command::GetBucketLifecycle => 0, Command::DeleteBucketLifecycle { .. } => 0, + Command::GetObjectAttributes { .. } => 0, }; Ok(result) } @@ -282,6 +288,7 @@ impl<'a> Command<'a> { Command::PutObjectTagging { .. } => "text/plain".into(), Command::UploadPart { .. } => "text/plain".into(), Command::CreateBucket { .. } => "text/plain".into(), + Command::GetObjectAttributes { .. } => "text/plain".into(), } } @@ -345,6 +352,7 @@ impl<'a> Command<'a> { Command::CopyObject { .. } => EMPTY_PAYLOAD_SHA.into(), Command::UploadPart { .. } => EMPTY_PAYLOAD_SHA.into(), Command::InitiateMultipartUpload { .. } => EMPTY_PAYLOAD_SHA.into(), + Command::GetObjectAttributes { .. } => EMPTY_PAYLOAD_SHA.into(), }; Ok(result) } diff --git a/s3/src/request/request_trait.rs b/s3/src/request/request_trait.rs index cc5f1b324e..a6409b4f52 100644 --- a/s3/src/request/request_trait.rs +++ b/s3/src/request/request_trait.rs @@ -403,7 +403,31 @@ pub trait Request { | Command::DeleteBucketCors { .. } => { url_str.push_str("?cors"); } - _ => {} + Command::GetObjectAttributes { version_id, .. } => { + if let Some(version_id) = version_id { + url_str.push_str(&format!("?attributes&versionId={}", version_id)); + } else { + url_str.push_str("?attributes&versionId=null"); + } + } + Command::HeadObject => {} + Command::DeleteObject => {} + Command::DeleteObjectTagging => {} + Command::GetObject => {} + Command::GetObjectRange { .. } => {} + Command::GetObjectTagging => {} + Command::ListObjects { .. } => {} + Command::ListObjectsV2 { .. } => {} + Command::GetBucketLocation => {} + Command::PresignGet { .. } => {} + Command::PresignPut { .. } => {} + Command::PresignDelete { .. } => {} + Command::DeleteBucket => {} + Command::ListBuckets => {} + Command::CopyObject { .. } => {} + Command::PutObjectTagging { .. } => {} + Command::UploadPart { .. } => {} + Command::CreateBucket { .. } => {} } let mut url = Url::parse(&url_str)?; @@ -645,6 +669,19 @@ pub trait Request { HeaderName::from_static("x-amz-expected-bucket-owner"), expected_bucket_owner.parse()?, ); + } else if let Command::GetObjectAttributes { + expected_bucket_owner, + .. + } = self.command() + { + headers.insert( + HeaderName::from_static("x-amz-expected-bucket-owner"), + expected_bucket_owner.parse()?, + ); + headers.insert( + HeaderName::from_static("x-amz-object-attributes"), + "ETag".parse()?, + ); } // This must be last, as it signs the other headers, omitted if no secret key is provided diff --git a/s3/src/request/tokio_backend.rs b/s3/src/request/tokio_backend.rs index ce88807659..ffe5813a1f 100644 --- a/s3/src/request/tokio_backend.rs +++ b/s3/src/request/tokio_backend.rs @@ -104,6 +104,8 @@ impl<'a> Request for ReqwestRequest<'a> { let request = request.build()?; + // println!("Request: {:?}", request); + let response = client.execute(request).await?; if cfg!(feature = "fail-on-err") && !response.status().is_success() { diff --git a/s3/src/serde_types.rs b/s3/src/serde_types.rs index 2ca0d439ea..8936009ca6 100644 --- a/s3/src/serde_types.rs +++ b/s3/src/serde_types.rs @@ -19,6 +19,91 @@ pub struct Owner { pub id: String, } +// +// string +// +// string +// string +// string +// string +// +// +// boolean +// integer +// integer +// integer +// +// string +// string +// string +// string +// integer +// long +// +// ... +// integer +// +// string +// long +// +#[derive(Deserialize, Debug)] +pub struct GetObjectAttributesOutput { + #[serde(rename = "ETag")] + pub etag: String, + #[serde(rename = "Checksum")] + pub checksum: Checksum, + #[serde(rename = "ObjectParts")] + pub object_parts: ObjectParts, + #[serde(rename = "StorageClass")] + pub storage_class: String, + #[serde(rename = "ObjectSize")] + pub object_size: u64, +} + +#[derive(Deserialize, Debug)] +pub struct Checksum { + #[serde(rename = "ChecksumCRC32")] + pub checksum_crc32: String, + #[serde(rename = "ChecksumCRC32C")] + pub checksum_crc32c: String, + #[serde(rename = "ChecksumSHA1")] + pub checksum_sha1: String, + #[serde(rename = "ChecksumSHA256")] + pub checksum_sha256: String, +} + +#[derive(Deserialize, Debug)] +pub struct ObjectParts { + #[serde(rename = "IsTruncated")] + pub is_truncated: bool, + #[serde(rename = "MaxParts")] + pub max_parts: i32, + #[serde(rename = "NextPartNumberMarker")] + pub next_part_number_marker: i32, + #[serde(rename = "PartNumberMarker")] + pub part_number_marker: i32, + #[serde(rename = "Part")] + pub part: Vec, + #[serde(rename = "PartsCount")] + pub parts_count: u64, +} + +#[derive(Deserialize, Debug)] +pub struct AttributesPart { + #[serde(rename = "ChecksumCRC32")] + pub checksum_crc32: String, + #[serde(rename = "ChecksumCRC32C")] + pub checksum_crc32c: String, + #[serde(rename = "ChecksumSHA1")] + pub checksum_sha1: String, + #[serde(rename = "ChecksumSHA256")] + pub checksum_sha256: String, + #[serde(rename = "PartNumber")] + pub part_number: i32, + #[serde(rename = "Size")] + pub size: u64, +} + /// An individual object in a `ListBucketResult` #[derive(Deserialize, Debug, Clone)] pub struct Object {