Skip to main content

aws_smithy_types/
blob.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6use bytes::Bytes;
7use std::any::Any;
8
9/// Binary Blob Type
10///
11/// Blobs represent protocol-agnostic binary content.
12#[derive(Debug, Default, PartialEq, Eq, Hash, Clone)]
13pub struct Blob {
14    inner: Bytes,
15}
16
17impl Blob {
18    /// Creates a new blob from the given `input`.
19    pub fn new<T: Into<Vec<u8>>>(input: T) -> Self {
20        Blob {
21            inner: input.into().into(),
22        }
23    }
24
25    /// Creates a new blob, reusing an existing shared allocation when possible.
26    pub fn from_maybe_shared<T: AsRef<[u8]> + 'static>(input: T) -> Self {
27        let mut input = Some(input);
28        let inner =
29            if let Some(bytes) = (&mut input as &mut dyn Any).downcast_mut::<Option<Bytes>>() {
30                bytes.take().expect("input is present")
31            } else {
32                Bytes::copy_from_slice(input.as_ref().expect("input is present").as_ref())
33            };
34        Blob { inner }
35    }
36
37    /// Consumes the `Blob` and returns its contents as `Bytes`.
38    pub fn into_bytes(self) -> Bytes {
39        self.inner
40    }
41
42    /// Consumes the `Blob` and returns a `Vec<u8>` with its contents.
43    pub fn into_inner(self) -> Vec<u8> {
44        self.inner.into()
45    }
46}
47
48impl AsRef<[u8]> for Blob {
49    fn as_ref(&self) -> &[u8] {
50        &self.inner
51    }
52}
53
54impl From<Vec<u8>> for Blob {
55    fn from(value: Vec<u8>) -> Self {
56        Blob::new(value)
57    }
58}
59
60impl From<Blob> for Vec<u8> {
61    fn from(value: Blob) -> Self {
62        value.into_inner()
63    }
64}
65
66impl From<&[u8]> for Blob {
67    fn from(value: &[u8]) -> Self {
68        Blob::new(value)
69    }
70}
71
72#[cfg(all(aws_sdk_unstable, feature = "serde-serialize"))]
73mod serde_serialize {
74    use super::*;
75    use serde::Serialize;
76
77    impl Serialize for Blob {
78        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
79        where
80            S: serde::Serializer,
81        {
82            if serializer.is_human_readable() {
83                serializer.serialize_str(&crate::base64::encode(&self.inner))
84            } else {
85                serializer.serialize_bytes(&self.inner)
86            }
87        }
88    }
89}
90
91#[cfg(all(aws_sdk_unstable, feature = "serde-deserialize"))]
92mod serde_deserialize {
93    use super::*;
94    use serde::{de::Visitor, Deserialize};
95
96    struct HumanReadableBlobVisitor;
97    impl<'de> Visitor<'de> for HumanReadableBlobVisitor {
98        type Value = Blob;
99        fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100            formatter.write_str("expected base64 encoded string")
101        }
102
103        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
104        where
105            E: serde::de::Error,
106        {
107            match crate::base64::decode(v) {
108                Ok(inner) => Ok(Blob::from(inner)),
109                Err(e) => Err(E::custom(e)),
110            }
111        }
112    }
113
114    struct NotHumanReadableBlobVisitor;
115    impl<'de> Visitor<'de> for NotHumanReadableBlobVisitor {
116        type Value = Blob;
117        fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118            formatter.write_str("expected bytes")
119        }
120
121        fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
122        where
123            E: serde::de::Error,
124        {
125            Ok(Blob::from(v))
126        }
127    }
128
129    impl<'de> Deserialize<'de> for Blob {
130        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
131        where
132            D: serde::Deserializer<'de>,
133        {
134            if deserializer.is_human_readable() {
135                deserializer.deserialize_str(HumanReadableBlobVisitor)
136            } else {
137                deserializer.deserialize_byte_buf(NotHumanReadableBlobVisitor)
138            }
139        }
140    }
141}
142
143#[cfg(test)]
144mod test {
145    use crate::Blob;
146    use bytes::Bytes;
147
148    #[test]
149    fn blob_conversion() {
150        let my_bytes: &[u8] = &[1u8, 2u8, 3u8];
151        let my_vec = vec![1u8, 2u8, 3u8];
152        let orig_vec = my_vec.clone();
153
154        let blob1: Blob = my_bytes.into();
155        let vec1: Vec<u8> = blob1.into();
156        assert_eq!(orig_vec, vec1);
157
158        let blob2: Blob = my_vec.into();
159        let vec2: Vec<u8> = blob2.into();
160        assert_eq!(orig_vec, vec2);
161    }
162
163    #[test]
164    fn blob_reuses_bytes() {
165        let bytes = Bytes::from_static(b"some shared bytes");
166        let original_ptr = bytes.as_ptr();
167
168        let blob = Blob::from_maybe_shared(bytes);
169        let bytes = blob.into_bytes();
170
171        assert_eq!(original_ptr, bytes.as_ptr());
172    }
173
174    #[test]
175    fn blob_new_accepts_borrowed_data() {
176        let bytes = vec![1, 2, 3];
177        let blob = Blob::new(bytes.as_slice());
178
179        assert_eq!(bytes, blob.as_ref());
180    }
181}
182
183#[cfg(all(
184    aws_sdk_unstable,
185    feature = "serde-serialize",
186    feature = "serde-deserialize"
187))]
188mod test_serde {
189    use crate::Blob;
190    use serde::{Deserialize, Serialize};
191
192    #[derive(Deserialize, Serialize, Debug, PartialEq)]
193    #[allow(dead_code)]
194    struct ForTest {
195        blob: Blob,
196    }
197
198    #[test]
199    fn human_readable_blob() {
200        let aws_in_base64 = r#"{"blob":"QVdT"}"#;
201        let for_test = ForTest {
202            blob: Blob {
203                inner: vec![b'A', b'W', b'S'].into(),
204            },
205        };
206        assert_eq!(for_test, serde_json::from_str(aws_in_base64).unwrap());
207        assert_eq!(serde_json::to_string(&for_test).unwrap(), aws_in_base64);
208    }
209
210    #[test]
211    fn not_human_readable_blob() {
212        use std::collections::HashMap;
213        use std::ffi::CString;
214
215        let for_test = ForTest {
216            blob: Blob {
217                inner: vec![b'A', b'W', b'S'].into(),
218            },
219        };
220        let mut buf = vec![];
221        let res = ciborium::ser::into_writer(&for_test, &mut buf);
222        assert!(res.is_ok());
223
224        // checks whether the bytes are deserialized properly
225        let n: HashMap<String, CString> =
226            ciborium::de::from_reader(std::io::Cursor::new(buf.clone())).unwrap();
227        assert!(n.get("blob").is_some());
228        assert!(n.get("blob") == CString::new([65, 87, 83]).ok().as_ref());
229
230        let de: ForTest = ciborium::de::from_reader(std::io::Cursor::new(buf)).unwrap();
231        assert_eq!(for_test, de);
232    }
233}