Skip to main content

byte_wrapper/
base64_vec.rs

1// Copyright (c) The byte-wrapper Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! The [`Base64Vec`] newtype wrapper.
5
6use alloc::vec::Vec;
7use base64::Engine;
8use core::{error, fmt, str::FromStr};
9
10/// A byte vector that displays and parses as base64.
11///
12/// `Base64Vec` wraps `Vec<u8>`, providing [`Display`](fmt::Display)
13/// and [`FromStr`] implementations that use base64 encoding.
14///
15/// With the **`serde`** feature enabled, it also implements
16/// `Serialize` and `Deserialize` (base64 strings in human-readable
17/// formats, raw bytes in binary formats), and can be used with
18/// `#[serde(with = "Base64Vec")]` on `Vec<u8>` fields.
19///
20/// # Examples
21///
22/// ```
23/// use byte_wrapper::Base64Vec;
24///
25/// let b = Base64Vec::new(vec![1, 2, 3]);
26/// assert_eq!(b.to_string(), "AQID");
27///
28/// let parsed: Base64Vec = "AQID".parse().unwrap();
29/// assert_eq!(*parsed, [1, 2, 3]);
30/// ```
31#[must_use]
32#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
33pub struct Base64Vec(pub Vec<u8>);
34
35impl Base64Vec {
36    /// Creates a new `Base64Vec` from a byte vector.
37    #[inline]
38    pub fn new(bytes: Vec<u8>) -> Self {
39        Self(bytes)
40    }
41
42    /// Returns the inner byte vector.
43    #[inline]
44    #[must_use]
45    pub fn into_inner(self) -> Vec<u8> {
46        self.0
47    }
48}
49
50/// Error returned by [`Base64Vec::from_str`].
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub enum ParseBase64Error {
53    /// An invalid byte was found at the given offset.
54    InvalidByte {
55        /// Byte offset of the invalid symbol.
56        offset: usize,
57        /// The invalid byte value.
58        byte: u8,
59    },
60    /// The input length (in valid base64 symbols) is invalid.
61    InvalidLength {
62        /// The invalid length.
63        length: usize,
64    },
65    /// The last non-padding symbol has nonzero trailing bits that
66    /// would be discarded, indicating corrupted or truncated input.
67    InvalidLastSymbol {
68        /// Byte offset of the invalid symbol.
69        offset: usize,
70        /// The invalid byte value.
71        byte: u8,
72    },
73    /// Padding was absent, incorrect, or otherwise not as expected.
74    InvalidPadding,
75}
76
77impl fmt::Display for ParseBase64Error {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        match self {
80            ParseBase64Error::InvalidByte { offset, byte } => {
81                write!(f, "invalid base64 symbol {byte}, offset {offset}",)
82            }
83            ParseBase64Error::InvalidLength { length } => {
84                write!(f, "invalid base64 input length: {length}",)
85            }
86            ParseBase64Error::InvalidLastSymbol { offset, byte } => {
87                write!(
88                    f,
89                    "invalid base64 last symbol {byte}, \
90                     offset {offset}",
91                )
92            }
93            ParseBase64Error::InvalidPadding => {
94                write!(f, "invalid base64 padding")
95            }
96        }
97    }
98}
99
100impl error::Error for ParseBase64Error {}
101
102fn from_decode_error(e: base64::DecodeError) -> ParseBase64Error {
103    match e {
104        base64::DecodeError::InvalidByte(offset, byte) => {
105            ParseBase64Error::InvalidByte { offset, byte }
106        }
107        base64::DecodeError::InvalidLength(length) => {
108            ParseBase64Error::InvalidLength { length }
109        }
110        base64::DecodeError::InvalidLastSymbol {
111            offset,
112            symbol,
113            symbol_value: _,
114        } => ParseBase64Error::InvalidLastSymbol { offset, byte: symbol },
115        base64::DecodeError::InvalidPadding => ParseBase64Error::InvalidPadding,
116    }
117}
118
119impl FromStr for Base64Vec {
120    type Err = ParseBase64Error;
121
122    fn from_str(s: &str) -> Result<Self, Self::Err> {
123        base64::engine::general_purpose::STANDARD
124            .decode(s)
125            .map(Self)
126            .map_err(from_decode_error)
127    }
128}
129
130impl fmt::Debug for Base64Vec {
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        f.debug_tuple("Base64Vec")
133            .field(&base64::engine::general_purpose::STANDARD.encode(&self.0))
134            .finish()
135    }
136}
137
138impl fmt::Display for Base64Vec {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        base64::engine::general_purpose::STANDARD.encode(&self.0).fmt(f)
141    }
142}
143
144impl core::ops::Deref for Base64Vec {
145    type Target = Vec<u8>;
146
147    #[inline]
148    fn deref(&self) -> &Self::Target {
149        &self.0
150    }
151}
152
153impl core::ops::DerefMut for Base64Vec {
154    #[inline]
155    fn deref_mut(&mut self) -> &mut Self::Target {
156        &mut self.0
157    }
158}
159
160impl AsRef<[u8]> for Base64Vec {
161    #[inline]
162    fn as_ref(&self) -> &[u8] {
163        &self.0
164    }
165}
166
167impl AsMut<[u8]> for Base64Vec {
168    #[inline]
169    fn as_mut(&mut self) -> &mut [u8] {
170        &mut self.0
171    }
172}
173
174impl From<Vec<u8>> for Base64Vec {
175    #[inline]
176    fn from(bytes: Vec<u8>) -> Self {
177        Self(bytes)
178    }
179}
180
181impl From<Base64Vec> for Vec<u8> {
182    #[inline]
183    fn from(base64_vec: Base64Vec) -> Self {
184        base64_vec.0
185    }
186}
187
188#[cfg(feature = "serde")]
189mod serde_impls {
190    use super::Base64Vec;
191    use alloc::vec::Vec;
192    use base64::Engine;
193    use core::fmt;
194    use serde_core::{
195        Deserializer, Serializer,
196        de::{SeqAccess, Visitor},
197    };
198
199    /// Serializes a byte slice as base64 if human-readable, or as
200    /// raw bytes if not.
201    fn serialize_bytes<S>(
202        bytes: &[u8],
203        serializer: S,
204    ) -> Result<S::Ok, S::Error>
205    where
206        S: Serializer,
207    {
208        if serializer.is_human_readable() {
209            let encoded =
210                base64::engine::general_purpose::STANDARD.encode(bytes);
211            serializer.serialize_str(&encoded)
212        } else {
213            serializer.serialize_bytes(bytes)
214        }
215    }
216
217    /// Deserializes base64 strings (if human-readable) or byte
218    /// arrays (if not) to `Vec<u8>`.
219    fn deserialize_bytes<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
220    where
221        D: Deserializer<'de>,
222    {
223        use serde_core::de::Error;
224
225        if deserializer.is_human_readable() {
226            struct Base64Visitor;
227
228            impl<'de2> Visitor<'de2> for Base64Visitor {
229                type Value = Vec<u8>;
230
231                fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
232                    write!(f, "a base64-encoded string")
233                }
234
235                fn visit_str<E>(self, data: &str) -> Result<Self::Value, E>
236                where
237                    E: Error,
238                {
239                    base64::engine::general_purpose::STANDARD
240                        .decode(data)
241                        .map_err(Error::custom)
242                }
243            }
244
245            deserializer.deserialize_str(Base64Visitor)
246        } else {
247            struct BytesVisitor;
248
249            impl<'de2> Visitor<'de2> for BytesVisitor {
250                type Value = Vec<u8>;
251
252                fn expecting(
253                    &self,
254                    formatter: &mut fmt::Formatter,
255                ) -> fmt::Result {
256                    write!(formatter, "a byte array")
257                }
258
259                fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
260                where
261                    E: Error,
262                {
263                    Ok(v.to_vec())
264                }
265
266                fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
267                where
268                    E: Error,
269                {
270                    Ok(v)
271                }
272
273                fn visit_seq<A>(
274                    self,
275                    mut seq: A,
276                ) -> Result<Self::Value, A::Error>
277                where
278                    A: SeqAccess<'de2>,
279                {
280                    let hint = seq.size_hint().unwrap_or(0);
281                    let mut out = Vec::with_capacity(hint.min(4096));
282                    while let Some(byte) = seq.next_element()? {
283                        out.push(byte);
284                    }
285                    Ok(out)
286                }
287            }
288
289            deserializer.deserialize_bytes(BytesVisitor)
290        }
291    }
292
293    impl Base64Vec {
294        /// Serializes a byte vector as base64 if the format is
295        /// human-readable, or as raw bytes otherwise.
296        ///
297        /// Intended for use with `#[serde(with = "Base64Vec")]`.
298        ///
299        /// # Examples
300        ///
301        /// ```
302        /// use byte_wrapper::Base64Vec;
303        /// use serde::{Deserialize, Serialize};
304        ///
305        /// #[derive(Serialize, Deserialize)]
306        /// struct Blob {
307        ///     #[serde(with = "Base64Vec")]
308        ///     data: Vec<u8>,
309        /// }
310        ///
311        /// let b = Blob { data: vec![1, 2, 3] };
312        /// let json = serde_json::to_string(&b).unwrap();
313        /// assert_eq!(json, r#"{"data":"AQID"}"#);
314        /// ```
315        #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
316        pub fn serialize<S>(
317            bytes: &[u8],
318            serializer: S,
319        ) -> Result<S::Ok, S::Error>
320        where
321            S: Serializer,
322        {
323            serialize_bytes(bytes, serializer)
324        }
325
326        /// Deserializes a byte vector from base64 if the format is
327        /// human-readable, or as raw bytes otherwise.
328        ///
329        /// Intended for use with `#[serde(with = "Base64Vec")]`.
330        ///
331        /// # Examples
332        ///
333        /// ```
334        /// use byte_wrapper::Base64Vec;
335        /// use serde::{Deserialize, Serialize};
336        ///
337        /// #[derive(Serialize, Deserialize)]
338        /// struct Blob {
339        ///     #[serde(with = "Base64Vec")]
340        ///     data: Vec<u8>,
341        /// }
342        ///
343        /// let b: Blob = serde_json::from_str(r#"{"data":"AQID"}"#).unwrap();
344        /// assert_eq!(b.data, [1, 2, 3]);
345        /// ```
346        #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
347        pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
348        where
349            D: Deserializer<'de>,
350        {
351            deserialize_bytes(deserializer)
352        }
353    }
354
355    #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
356    impl serde_core::Serialize for Base64Vec {
357        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
358        where
359            S: Serializer,
360        {
361            serialize_bytes(&self.0, serializer)
362        }
363    }
364
365    #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
366    impl<'de> serde_core::Deserialize<'de> for Base64Vec {
367        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
368        where
369            D: Deserializer<'de>,
370        {
371            deserialize_bytes(deserializer).map(Self)
372        }
373    }
374}
375
376#[cfg(feature = "schemars08")]
377mod schemars_impls {
378    use super::Base64Vec;
379    use crate::schemars_util::x_rust_type_extension;
380    use alloc::string::String;
381    use schemars08::{
382        JsonSchema,
383        r#gen::SchemaGenerator,
384        schema::{InstanceType, Schema, SchemaObject},
385    };
386
387    impl JsonSchema for Base64Vec {
388        fn schema_name() -> String {
389            "Base64Vec".into()
390        }
391
392        fn is_referenceable() -> bool {
393            false
394        }
395
396        fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
397            let mut extensions = x_rust_type_extension("Base64Vec");
398            extensions.insert("contentEncoding".into(), "base64".into());
399            Schema::Object(SchemaObject {
400                instance_type: Some(InstanceType::String.into()),
401                format: Some("byte".into()),
402                extensions,
403                ..Default::default()
404            })
405        }
406    }
407}