1use bytes::Bytes;
7use std::any::Any;
8use std::fmt;
9
10#[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 pub fn new<T: Into<Vec<u8>>>(input: T) -> Self {
47 Blob {
48 inner: input.into().into(),
49 }
50 }
51
52 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 pub fn into_bytes(self) -> Bytes {
66 self.inner
67 }
68
69 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 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}