Skip to content

Commit

Permalink
GetObjectAttributes WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
durch committed Sep 2, 2024
1 parent f580f9e commit 8fe4f42
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 4 deletions.
35 changes: 32 additions & 3 deletions s3/src/bucket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -929,6 +929,26 @@ impl Bucket {
request.response_data(false).await
}

#[maybe_async::maybe_async]
pub async fn get_object_attributes<S: AsRef<str>>(
&self,
path: S,
expected_bucket_owner: &str,
version_id: Option<String>,
) -> Result<GetObjectAttributesOutput, S3Error> {
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::<GetObjectAttributesOutput>(
response.as_str()?,
)?)
}

/// Checks if an object exists at the specified S3 path.
///
/// # Example:
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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);
}
}
8 changes: 8 additions & 0 deletions s3/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ pub enum Command<'a> {
configuration: BucketLifecycleConfiguration,
},
DeleteBucketLifecycle,
GetObjectAttributes {
expected_bucket_owner: String,
version_id: Option<String>,
},
}

impl<'a> Command<'a> {
Expand Down Expand Up @@ -201,6 +205,7 @@ impl<'a> Command<'a> {
HttpMethod::Post
}
Command::HeadObject => HttpMethod::Head,
Command::GetObjectAttributes { .. } => HttpMethod::Get,
}
}

Expand Down Expand Up @@ -246,6 +251,7 @@ impl<'a> Command<'a> {
Command::DeleteBucketCors { .. } => 0,
Command::GetBucketLifecycle => 0,
Command::DeleteBucketLifecycle { .. } => 0,
Command::GetObjectAttributes { .. } => 0,
};
Ok(result)
}
Expand Down Expand Up @@ -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(),
}
}

Expand Down Expand Up @@ -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)
}
Expand Down
39 changes: 38 additions & 1 deletion s3/src/request/request_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions s3/src/request/tokio_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
85 changes: 85 additions & 0 deletions s3/src/serde_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,91 @@ pub struct Owner {
pub id: String,
}

// <GetObjectAttributesOutput>
// <ETag>string</ETag>
// <Checksum>
// <ChecksumCRC32>string</ChecksumCRC32>
// <ChecksumCRC32C>string</ChecksumCRC32C>
// <ChecksumSHA1>string</ChecksumSHA1>
// <ChecksumSHA256>string</ChecksumSHA256>
// </Checksum>
// <ObjectParts>
// <IsTruncated>boolean</IsTruncated>
// <MaxParts>integer</MaxParts>
// <NextPartNumberMarker>integer</NextPartNumberMarker>
// <PartNumberMarker>integer</PartNumberMarker>
// <Part>
// <ChecksumCRC32>string</ChecksumCRC32>
// <ChecksumCRC32C>string</ChecksumCRC32C>
// <ChecksumSHA1>string</ChecksumSHA1>
// <ChecksumSHA256>string</ChecksumSHA256>
// <PartNumber>integer</PartNumber>
// <Size>long</Size>
// </Part>
// ...
// <PartsCount>integer</PartsCount>
// </ObjectParts>
// <StorageClass>string</StorageClass>
// <ObjectSize>long</ObjectSize>
// </GetObjectAttributesOutput>
#[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<AttributesPart>,
#[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 {
Expand Down

0 comments on commit 8fe4f42

Please sign in to comment.