Unverified Commit 77395c35 authored by Thomas Cameron's avatar Thomas Cameron Committed by GitHub
Browse files

Add serde support to Blob type (#2647)

## Motivation and Context
This is a child PR of https://github.com/awslabs/smithy-rs/pull/2616



The changes that this PR introduces is same as the ones that were merged
to `unstable-serde-support` branch before.

Initially, we tried to make commit to unstable-serde-support branch and
merge changes one by one in small PRs. However, in order to make it up
to date with the main branch, we would need to go through a large PR of
over 700 files.

Thus, I decided to create individual PRs that commits directly to `main`
branch.

## Description
- Implements `serde` support to `Blob`

## Testing
- Test checks whether the serialized/de-serialized data matches with the
expected value

## Checklist
NA

----

_By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice._

---------

Co-authored-by: default avatarJohn DiSanti <johndisanti@gmail.com>
parent 74a72041
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
[package]
name = "aws-smithy-types"
version = "0.0.0-smithy-rs-head"
authors = ["AWS Rust SDK Team <aws-sdk-rust@amazon.com>", "Russell Cohen <rcoh@amazon.com>"]
authors = [
    "AWS Rust SDK Team <aws-sdk-rust@amazon.com>",
    "Russell Cohen <rcoh@amazon.com>",
]
description = "Types for smithy-rs codegen."
edition = "2021"
license = "Apache-2.0"
+3 −1
Original line number Diff line number Diff line
@@ -5,8 +5,10 @@
#

# This script contains additional CI checks to run for this specific package

set -e

echo "### Checking for duplicate dependency versions in the normal dependency graph with all features enabled"
cargo tree -d --edges normal --all-features

echo "### Checking whether the features are properly feature-gated"
! cargo tree -e no-dev | grep serde
+125 −0
Original line number Diff line number Diff line
@@ -30,3 +30,128 @@ impl AsRef<[u8]> for Blob {
        &self.inner
    }
}

#[cfg(all(aws_sdk_unstable, feature = "serde-serialize"))]
mod serde_serialize {
    use super::*;
    use crate::base64;
    use serde::Serialize;

    impl Serialize for Blob {
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: serde::Serializer,
        {
            if serializer.is_human_readable() {
                serializer.serialize_str(&crate::base64::encode(&self.inner))
            } else {
                serializer.serialize_bytes(&self.inner)
            }
        }
    }
}

#[cfg(all(aws_sdk_unstable, feature = "serde-deserialize"))]
mod serde_deserialize {
    use super::*;
    use crate::base64;
    use serde::{de::Visitor, Deserialize};

    struct HumanReadableBlobVisitor;
    impl<'de> Visitor<'de> for HumanReadableBlobVisitor {
        type Value = Blob;
        fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            formatter.write_str("expected base64 encoded string")
        }

        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
        where
            E: serde::de::Error,
        {
            match base64::decode(v) {
                Ok(inner) => Ok(Blob { inner }),
                Err(e) => Err(E::custom(e)),
            }
        }
    }

    struct NotHumanReadableBlobVisitor;
    impl<'de> Visitor<'de> for NotHumanReadableBlobVisitor {
        type Value = Blob;
        fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            formatter.write_str("expected bytes")
        }

        fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
        where
            E: serde::de::Error,
        {
            Ok(Blob { inner: v })
        }
    }

    impl<'de> Deserialize<'de> for Blob {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where
            D: serde::Deserializer<'de>,
        {
            if deserializer.is_human_readable() {
                deserializer.deserialize_str(HumanReadableBlobVisitor)
            } else {
                deserializer.deserialize_byte_buf(NotHumanReadableBlobVisitor)
            }
        }
    }
}

#[cfg(test)]
#[cfg(all(
    aws_sdk_unstable,
    feature = "serde-serialize",
    feature = "serde-deserialize"
))]
mod test_serde {
    use crate::Blob;
    use serde::{Deserialize, Serialize};
    use std::collections::HashMap;

    #[derive(Deserialize, Serialize, Debug, PartialEq)]
    struct ForTest {
        blob: Blob,
    }

    #[test]
    fn human_readable_blob() {
        let aws_in_base64 = r#"{"blob":"QVdT"}"#;
        let for_test = ForTest {
            blob: Blob {
                inner: vec![b'A', b'W', b'S'],
            },
        };
        assert_eq!(for_test, serde_json::from_str(aws_in_base64).unwrap());
        assert_eq!(serde_json::to_string(&for_test).unwrap(), aws_in_base64);
    }

    #[test]
    fn not_human_readable_blob() {
        use std::ffi::CString;

        let for_test = ForTest {
            blob: Blob {
                inner: vec![b'A', b'W', b'S'],
            },
        };
        let mut buf = vec![];
        let res = ciborium::ser::into_writer(&for_test, &mut buf);
        assert!(res.is_ok());

        // checks whether the bytes are deserialized properly
        let n: HashMap<String, CString> =
            ciborium::de::from_reader(std::io::Cursor::new(buf.clone())).unwrap();
        assert!(n.get("blob").is_some());
        assert!(n.get("blob") == CString::new([65, 87, 83]).ok().as_ref());

        let de: ForTest = ciborium::de::from_reader(std::io::Cursor::new(buf)).unwrap();
        assert_eq!(for_test, de);
    }
}