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;
8use std::fmt;
9
10/// Binary Blob Type
11///
12/// Blobs represent protocol-agnostic binary content.
13///
14/// The [`fmt::Debug`] and [`fmt::Display`] implementations render the contents
15/// as a lowercase hex-encoded string. This avoids noisy byte-array output
16/// in service logs while still preserving the underlying data.
17#[derive(Default, PartialEq, Eq, Hash, Clone)]
18pub struct Blob {
19    inner: Bytes,
20}
21
22struct B<'b>(&'b [u8]);
23
24impl std::fmt::Debug for B<'_> {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        self.0.iter().try_for_each(|b| write!(f, "{b:02x}"))
27    }
28}
29
30impl fmt::Debug for Blob {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        f.debug_struct("Blob")
33            .field("inner", &format_args!("{:?}", B(&self.inner)))
34            .finish()
35    }
36}
37
38impl fmt::Display for Blob {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        write!(f, "{:?}", B(&self.inner))
41    }
42}
43
44impl Blob {
45    /// Creates a new blob from the given `input`.
46    pub fn new<T: Into<Vec<u8>>>(input: T) -> Self {
47        Blob {
48            inner: input.into().into(),
49        }
50    }
51
52    /// Creates a new blob, reusing an existing shared allocation when possible.
53    pub fn from_maybe_shared<T: AsRef<[u8]> + 'static>(input: T) -> Self {
54        let mut input = Some(input);
55        let inner =
56            if let Some(bytes) = (&mut input as &mut dyn Any).downcast_mut::<Option<Bytes>>() {
57                bytes.take().expect("input is present")
58            } else {
59                Bytes::copy_from_slice(input.as_ref().expect("input is present").as_ref())
60            };
61        Blob { inner }
62    }
63
64    /// Consumes the `Blob` and returns its contents as `Bytes`.
65    pub fn into_bytes(self) -> Bytes {
66        self.inner
67    }
68
69    /// Consumes the `Blob` and returns a `Vec<u8>` with its contents.
70    pub fn into_inner(self) -> Vec<u8> {
71        self.inner.into()
72    }
73}
74
75impl AsRef<[u8]> for Blob {
76    fn as_ref(&self) -> &[u8] {
77        &self.inner
78    }
79}
80
81impl From<Vec<u8>> for Blob {
82    fn from(value: Vec<u8>) -> Self {
83        Blob::new(value)
84    }
85}
86
87impl From<Blob> for Vec<u8> {
88    fn from(value: Blob) -> Self {
89        value.into_inner()
90    }
91}
92
93impl From<&[u8]> for Blob {
94    fn from(value: &[u8]) -> Self {
95        Blob::new(value)
96    }
97}
98
99#[cfg(all(aws_sdk_unstable, feature = "serde-serialize"))]
100mod serde_serialize {
101    use super::*;
102    use serde::Serialize;
103
104    impl Serialize for Blob {
105        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
106        where
107            S: serde::Serializer,
108        {
109            if serializer.is_human_readable() {
110                serializer.serialize_str(&crate::base64::encode(&self.inner))
111            } else {
112                serializer.serialize_bytes(&self.inner)
113            }
114        }
115    }
116}
117
118#[cfg(all(aws_sdk_unstable, feature = "serde-deserialize"))]
119mod serde_deserialize {
120    use super::*;
121    use serde::{de::Visitor, Deserialize};
122
123    struct HumanReadableBlobVisitor;
124    impl<'de> Visitor<'de> for HumanReadableBlobVisitor {
125        type Value = Blob;
126        fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127            formatter.write_str("expected base64 encoded string")
128        }
129
130        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
131        where
132            E: serde::de::Error,
133        {
134            match crate::base64::decode(v) {
135                Ok(inner) => Ok(Blob::from(inner)),
136                Err(e) => Err(E::custom(e)),
137            }
138        }
139    }
140
141    struct NotHumanReadableBlobVisitor;
142    impl<'de> Visitor<'de> for NotHumanReadableBlobVisitor {
143        type Value = Blob;
144        fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145            formatter.write_str("expected bytes")
146        }
147
148        fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
149        where
150            E: serde::de::Error,
151        {
152            Ok(Blob::from(v))
153        }
154    }
155
156    impl<'de> Deserialize<'de> for Blob {
157        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
158        where
159            D: serde::Deserializer<'de>,
160        {
161            if deserializer.is_human_readable() {
162                deserializer.deserialize_str(HumanReadableBlobVisitor)
163            } else {
164                deserializer.deserialize_byte_buf(NotHumanReadableBlobVisitor)
165            }
166        }
167    }
168}
169
170#[cfg(test)]
171mod test {
172    use crate::Blob;
173    use bytes::Bytes;
174
175    #[test]
176    fn blob_conversion() {
177        let my_bytes: &[u8] = &[1u8, 2u8, 3u8];
178        let my_vec = vec![1u8, 2u8, 3u8];
179        let orig_vec = my_vec.clone();
180
181        let blob1: Blob = my_bytes.into();
182        let vec1: Vec<u8> = blob1.into();
183        assert_eq!(orig_vec, vec1);
184
185        let blob2: Blob = my_vec.into();
186        let vec2: Vec<u8> = blob2.into();
187        assert_eq!(orig_vec, vec2);
188    }
189
190    #[test]
191    fn blob_reuses_bytes() {
192        let bytes = Bytes::from_static(b"some shared bytes");
193        let original_ptr = bytes.as_ptr();
194
195        let blob = Blob::from_maybe_shared(bytes);
196        let bytes = blob.into_bytes();
197
198        assert_eq!(original_ptr, bytes.as_ptr());
199    }
200
201    #[test]
202    fn blob_new_accepts_borrowed_data() {
203        let bytes = vec![1, 2, 3];
204        let blob = Blob::new(bytes.as_slice());
205
206        assert_eq!(bytes, blob.as_ref());
207    }
208
209    #[test]
210    fn blob_display_is_hex_encoded() {
211        let blob = Blob::new(vec![0xDE, 0xAD, 0xBE, 0xEF]);
212        assert_eq!(format!("{blob}"), "deadbeef");
213    }
214
215    #[test]
216    fn blob_debug_is_hex_encoded() {
217        let blob = Blob::new(vec![0x00, 0x01, 0x02, 0xFF]);
218        assert_eq!(format!("{blob:?}"), "Blob { inner: 000102ff }");
219    }
220
221    #[test]
222    fn empty_blob_display_and_debug() {
223        let blob = Blob::new(Vec::<u8>::new());
224        assert_eq!(format!("{blob}"), "");
225        assert_eq!(format!("{blob:?}"), "Blob { inner:  }");
226    }
227}
228
229#[cfg(all(
230    aws_sdk_unstable,
231    feature = "serde-serialize",
232    feature = "serde-deserialize"
233))]
234mod test_serde {
235    use crate::Blob;
236    use serde::{Deserialize, Serialize};
237
238    #[derive(Deserialize, Serialize, Debug, PartialEq)]
239    #[allow(dead_code)]
240    struct ForTest {
241        blob: Blob,
242    }
243
244    #[test]
245    fn human_readable_blob() {
246        let aws_in_base64 = r#"{"blob":"QVdT"}"#;
247        let for_test = ForTest {
248            blob: Blob {
249                inner: vec![b'A', b'W', b'S'].into(),
250            },
251        };
252        assert_eq!(for_test, serde_json::from_str(aws_in_base64).unwrap());
253        assert_eq!(serde_json::to_string(&for_test).unwrap(), aws_in_base64);
254    }
255
256    #[test]
257    fn not_human_readable_blob() {
258        use std::collections::HashMap;
259        use std::ffi::CString;
260
261        let for_test = ForTest {
262            blob: Blob {
263                inner: vec![b'A', b'W', b'S'].into(),
264            },
265        };
266        let mut buf = vec![];
267        let res = ciborium::ser::into_writer(&for_test, &mut buf);
268        assert!(res.is_ok());
269
270        // checks whether the bytes are deserialized properly
271        let n: HashMap<String, CString> =
272            ciborium::de::from_reader(std::io::Cursor::new(buf.clone())).unwrap();
273        assert!(n.get("blob").is_some());
274        assert!(n.get("blob") == CString::new([65, 87, 83]).ok().as_ref());
275
276        let de: ForTest = ciborium::de::from_reader(std::io::Cursor::new(buf)).unwrap();
277        assert_eq!(for_test, de);
278    }
279}