Unverified Commit a6446bf2 authored by Copilot's avatar Copilot Committed by GitHub
Browse files

feat(s3s): use strongly typed ETag for conditional request headers (#403)

parent 0ed460ed
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
@@ -46,12 +46,28 @@ pub fn collect_rust_types(model: &smithy::Model, ops: &Operations) -> RustTypes
            "ETag",          //
        ];

        // ETag-related header types that should be aliased to ETag instead of String
        let etag_alias_types = [
            "IfMatch",               //
            "IfNoneMatch",           //
            "CopySourceIfMatch",     //
            "CopySourceIfNoneMatch", //
        ];

        if provided_types.contains(&rs_shape_name.as_str()) {
            let ty = rust::Type::provided(&rs_shape_name);
            insert(rs_shape_name, ty);
            continue;
        }

        if etag_alias_types.contains(&rs_shape_name.as_str()) {
            if let smithy::Shape::String(shape) = shape {
                let ty = rust::Type::alias(&rs_shape_name, "ETag", shape.traits.doc());
                insert(rs_shape_name, ty);
                continue;
            }
        }

        match shape {
            smithy::Shape::Boolean(shape) => {
                let ty = rust::Type::alias(&rs_shape_name, "bool", shape.traits.doc());
+25 −0
Original line number Diff line number Diff line
use std::str::FromStr;

use http::HeaderValue;
use http::header::InvalidHeaderValue;
use stdx::str::StrExt;
@@ -138,6 +140,14 @@ impl ETag {
    }
}

impl FromStr for ETag {
    type Err = ParseETagError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Self::parse_http_header(s.as_bytes())
    }
}

#[cfg(test)]
mod tests {
    use super::{ETag, ParseETagError};
@@ -282,4 +292,19 @@ mod tests {
            assert_eq!(p.as_weak(), Some(v));
        }
    }

    #[test]
    fn from_str_trait() {
        // strong ETag via FromStr
        let e: ETag = "\"abc123\"".parse().expect("parse strong from str");
        assert_eq!(e.as_strong(), Some("abc123"));

        // weak ETag via FromStr
        let e: ETag = "W/\"xyz\"".parse().expect("parse weak from str");
        assert_eq!(e.as_weak(), Some("xyz"));

        // invalid format via FromStr
        let err = "abc".parse::<ETag>().unwrap_err();
        assert!(matches!(err, ParseETagError::InvalidFormat));
    }
}
+4 −43
Original line number Diff line number Diff line
@@ -2759,11 +2759,11 @@ impl fmt::Debug for CopyPartResult {
    }
}
pub type CopySourceIfMatch = String;
pub type CopySourceIfMatch = ETag;
pub type CopySourceIfModifiedSince = Timestamp;
pub type CopySourceIfNoneMatch = String;
pub type CopySourceIfNoneMatch = ETag;
pub type CopySourceIfUnmodifiedSince = Timestamp;
@@ -10555,7 +10555,7 @@ pub type HttpRedirectCode = String;
pub type ID = String;
pub type IfMatch = String;
pub type IfMatch = ETag;
pub type IfMatchInitiatedTime = Timestamp;
@@ -10565,7 +10565,7 @@ pub type IfMatchSize = i64;
pub type IfModifiedSince = Timestamp;
pub type IfNoneMatch = String;
pub type IfNoneMatch = ETag;
pub type IfUnmodifiedSince = Timestamp;
@@ -31881,12 +31881,6 @@ impl DtoExt for CompleteMultipartUploadInput {
        if self.expected_bucket_owner.as_deref() == Some("") {
            self.expected_bucket_owner = None;
        }
        if self.if_match.as_deref() == Some("") {
            self.if_match = None;
        }
        if self.if_none_match.as_deref() == Some("") {
            self.if_none_match = None;
        }
        if let Some(ref mut val) = self.multipart_upload {
            val.ignore_empty_strings();
        }
@@ -32014,12 +32008,6 @@ impl DtoExt for CopyObjectInput {
        if self.content_language.as_deref() == Some("") {
            self.content_language = None;
        }
        if self.copy_source_if_match.as_deref() == Some("") {
            self.copy_source_if_match = None;
        }
        if self.copy_source_if_none_match.as_deref() == Some("") {
            self.copy_source_if_none_match = None;
        }
        if self.copy_source_sse_customer_algorithm.as_deref() == Some("") {
            self.copy_source_sse_customer_algorithm = None;
        }
@@ -32532,9 +32520,6 @@ impl DtoExt for DeleteObjectInput {
        if self.expected_bucket_owner.as_deref() == Some("") {
            self.expected_bucket_owner = None;
        }
        if self.if_match.as_deref() == Some("") {
            self.if_match = None;
        }
        if self.mfa.as_deref() == Some("") {
            self.mfa = None;
        }
@@ -33116,12 +33101,6 @@ impl DtoExt for GetObjectInput {
        if self.expected_bucket_owner.as_deref() == Some("") {
            self.expected_bucket_owner = None;
        }
        if self.if_match.as_deref() == Some("") {
            self.if_match = None;
        }
        if self.if_none_match.as_deref() == Some("") {
            self.if_none_match = None;
        }
        if let Some(ref val) = self.request_payer {
            if val.as_str() == "" {
                self.request_payer = None;
@@ -33427,12 +33406,6 @@ impl DtoExt for HeadObjectInput {
        if self.expected_bucket_owner.as_deref() == Some("") {
            self.expected_bucket_owner = None;
        }
        if self.if_match.as_deref() == Some("") {
            self.if_match = None;
        }
        if self.if_none_match.as_deref() == Some("") {
            self.if_none_match = None;
        }
        if let Some(ref val) = self.request_payer {
            if val.as_str() == "" {
                self.request_payer = None;
@@ -34845,12 +34818,6 @@ impl DtoExt for PutObjectInput {
        if self.grant_write_acp.as_deref() == Some("") {
            self.grant_write_acp = None;
        }
        if self.if_match.as_deref() == Some("") {
            self.if_match = None;
        }
        if self.if_none_match.as_deref() == Some("") {
            self.if_none_match = None;
        }
        if let Some(ref val) = self.object_lock_legal_hold_status {
            if val.as_str() == "" {
                self.object_lock_legal_hold_status = None;
@@ -35465,12 +35432,6 @@ impl DtoExt for Transition {
}
impl DtoExt for UploadPartCopyInput {
    fn ignore_empty_strings(&mut self) {
        if self.copy_source_if_match.as_deref() == Some("") {
            self.copy_source_if_match = None;
        }
        if self.copy_source_if_none_match.as_deref() == Some("") {
            self.copy_source_if_none_match = None;
        }
        if self.copy_source_range.as_deref() == Some("") {
            self.copy_source_range = None;
        }
+4 −43
Original line number Diff line number Diff line
@@ -2763,11 +2763,11 @@ impl fmt::Debug for CopyPartResult {
    }
}
pub type CopySourceIfMatch = String;
pub type CopySourceIfMatch = ETag;
pub type CopySourceIfModifiedSince = Timestamp;
pub type CopySourceIfNoneMatch = String;
pub type CopySourceIfNoneMatch = ETag;
pub type CopySourceIfUnmodifiedSince = Timestamp;
@@ -10639,7 +10639,7 @@ pub type HttpRedirectCode = String;
pub type ID = String;
pub type IfMatch = String;
pub type IfMatch = ETag;
pub type IfMatchInitiatedTime = Timestamp;
@@ -10649,7 +10649,7 @@ pub type IfMatchSize = i64;
pub type IfModifiedSince = Timestamp;
pub type IfNoneMatch = String;
pub type IfNoneMatch = ETag;
pub type IfUnmodifiedSince = Timestamp;
@@ -32105,12 +32105,6 @@ impl DtoExt for CompleteMultipartUploadInput {
        if self.expected_bucket_owner.as_deref() == Some("") {
            self.expected_bucket_owner = None;
        }
        if self.if_match.as_deref() == Some("") {
            self.if_match = None;
        }
        if self.if_none_match.as_deref() == Some("") {
            self.if_none_match = None;
        }
        if let Some(ref mut val) = self.multipart_upload {
            val.ignore_empty_strings();
        }
@@ -32238,12 +32232,6 @@ impl DtoExt for CopyObjectInput {
        if self.content_language.as_deref() == Some("") {
            self.content_language = None;
        }
        if self.copy_source_if_match.as_deref() == Some("") {
            self.copy_source_if_match = None;
        }
        if self.copy_source_if_none_match.as_deref() == Some("") {
            self.copy_source_if_none_match = None;
        }
        if self.copy_source_sse_customer_algorithm.as_deref() == Some("") {
            self.copy_source_sse_customer_algorithm = None;
        }
@@ -32762,9 +32750,6 @@ impl DtoExt for DeleteObjectInput {
        if self.expected_bucket_owner.as_deref() == Some("") {
            self.expected_bucket_owner = None;
        }
        if self.if_match.as_deref() == Some("") {
            self.if_match = None;
        }
        if self.mfa.as_deref() == Some("") {
            self.mfa = None;
        }
@@ -33356,12 +33341,6 @@ impl DtoExt for GetObjectInput {
        if self.expected_bucket_owner.as_deref() == Some("") {
            self.expected_bucket_owner = None;
        }
        if self.if_match.as_deref() == Some("") {
            self.if_match = None;
        }
        if self.if_none_match.as_deref() == Some("") {
            self.if_none_match = None;
        }
        if let Some(ref val) = self.request_payer {
            if val.as_str() == "" {
                self.request_payer = None;
@@ -33667,12 +33646,6 @@ impl DtoExt for HeadObjectInput {
        if self.expected_bucket_owner.as_deref() == Some("") {
            self.expected_bucket_owner = None;
        }
        if self.if_match.as_deref() == Some("") {
            self.if_match = None;
        }
        if self.if_none_match.as_deref() == Some("") {
            self.if_none_match = None;
        }
        if let Some(ref val) = self.request_payer {
            if val.as_str() == "" {
                self.request_payer = None;
@@ -35085,12 +35058,6 @@ impl DtoExt for PutObjectInput {
        if self.grant_write_acp.as_deref() == Some("") {
            self.grant_write_acp = None;
        }
        if self.if_match.as_deref() == Some("") {
            self.if_match = None;
        }
        if self.if_none_match.as_deref() == Some("") {
            self.if_none_match = None;
        }
        if let Some(ref val) = self.object_lock_legal_hold_status {
            if val.as_str() == "" {
                self.object_lock_legal_hold_status = None;
@@ -35711,12 +35678,6 @@ impl DtoExt for Transition {
}
impl DtoExt for UploadPartCopyInput {
    fn ignore_empty_strings(&mut self) {
        if self.copy_source_if_match.as_deref() == Some("") {
            self.copy_source_if_match = None;
        }
        if self.copy_source_if_none_match.as_deref() == Some("") {
            self.copy_source_if_none_match = None;
        }
        if self.copy_source_range.as_deref() == Some("") {
            self.copy_source_range = None;
        }