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(offset, byte) => {
111            ParseBase64Error::InvalidLastSymbol { offset, byte }
112        }
113        base64::DecodeError::InvalidPadding => ParseBase64Error::InvalidPadding,
114    }
115}
116
117impl FromStr for Base64Vec {
118    type Err = ParseBase64Error;
119
120    fn from_str(s: &str) -> Result<Self, Self::Err> {
121        base64::engine::general_purpose::STANDARD
122            .decode(s)
123            .map(Self)
124            .map_err(from_decode_error)
125    }
126}
127
128impl fmt::Debug for Base64Vec {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        f.debug_tuple("Base64Vec")
131            .field(&base64::engine::general_purpose::STANDARD.encode(&self.0))
132            .finish()
133    }
134}
135
136impl fmt::Display for Base64Vec {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        base64::engine::general_purpose::STANDARD.encode(&self.0).fmt(f)
139    }
140}
141
142impl core::ops::Deref for Base64Vec {
143    type Target = Vec<u8>;
144
145    #[inline]
146    fn deref(&self) -> &Self::Target {
147        &self.0
148    }
149}
150
151impl core::ops::DerefMut for Base64Vec {
152    #[inline]
153    fn deref_mut(&mut self) -> &mut Self::Target {
154        &mut self.0
155    }
156}
157
158impl AsRef<[u8]> for Base64Vec {
159    #[inline]
160    fn as_ref(&self) -> &[u8] {
161        &self.0
162    }
163}
164
165impl AsMut<[u8]> for Base64Vec {
166    #[inline]
167    fn as_mut(&mut self) -> &mut [u8] {
168        &mut self.0
169    }
170}
171
172impl From<Vec<u8>> for Base64Vec {
173    #[inline]
174    fn from(bytes: Vec<u8>) -> Self {
175        Self(bytes)
176    }
177}
178
179impl From<Base64Vec> for Vec<u8> {
180    #[inline]
181    fn from(base64_vec: Base64Vec) -> Self {
182        base64_vec.0
183    }
184}
185
186#[cfg(feature = "serde")]
187mod serde_impls {
188    use super::Base64Vec;
189    use alloc::vec::Vec;
190    use base64::Engine;
191    use core::fmt;
192    use serde_core::{
193        Deserializer, Serializer,
194        de::{SeqAccess, Visitor},
195    };
196
197    /// Serializes a byte slice as base64 if human-readable, or as
198    /// raw bytes if not.
199    fn serialize_bytes<S>(
200        bytes: &[u8],
201        serializer: S,
202    ) -> Result<S::Ok, S::Error>
203    where
204        S: Serializer,
205    {
206        if serializer.is_human_readable() {
207            let encoded =
208                base64::engine::general_purpose::STANDARD.encode(bytes);
209            serializer.serialize_str(&encoded)
210        } else {
211            serializer.serialize_bytes(bytes)
212        }
213    }
214
215    /// Deserializes base64 strings (if human-readable) or byte
216    /// arrays (if not) to `Vec<u8>`.
217    fn deserialize_bytes<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
218    where
219        D: Deserializer<'de>,
220    {
221        use serde_core::de::Error;
222
223        if deserializer.is_human_readable() {
224            struct Base64Visitor;
225
226            impl<'de2> Visitor<'de2> for Base64Visitor {
227                type Value = Vec<u8>;
228
229                fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
230                    write!(f, "a base64-encoded string")
231                }
232
233                fn visit_str<E>(self, data: &str) -> Result<Self::Value, E>
234                where
235                    E: Error,
236                {
237                    base64::engine::general_purpose::STANDARD
238                        .decode(data)
239                        .map_err(Error::custom)
240                }
241            }
242
243            deserializer.deserialize_str(Base64Visitor)
244        } else {
245            struct BytesVisitor;
246
247            impl<'de2> Visitor<'de2> for BytesVisitor {
248                type Value = Vec<u8>;
249
250                fn expecting(
251                    &self,
252                    formatter: &mut fmt::Formatter,
253                ) -> fmt::Result {
254                    write!(formatter, "a byte array")
255                }
256
257                fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
258                where
259                    E: Error,
260                {
261                    Ok(v.to_vec())
262                }
263
264                fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
265                where
266                    E: Error,
267                {
268                    Ok(v)
269                }
270
271                fn visit_seq<A>(
272                    self,
273                    mut seq: A,
274                ) -> Result<Self::Value, A::Error>
275                where
276                    A: SeqAccess<'de2>,
277                {
278                    let hint = seq.size_hint().unwrap_or(0);
279                    let mut out = Vec::with_capacity(hint.min(4096));
280                    while let Some(byte) = seq.next_element()? {
281                        out.push(byte);
282                    }
283                    Ok(out)
284                }
285            }
286
287            deserializer.deserialize_bytes(BytesVisitor)
288        }
289    }
290
291    impl Base64Vec {
292        /// Serializes a byte vector as base64 if the format is
293        /// human-readable, or as raw bytes otherwise.
294        ///
295        /// Intended for use with `#[serde(with = "Base64Vec")]`.
296        ///
297        /// # Examples
298        ///
299        /// ```
300        /// use byte_wrapper::Base64Vec;
301        /// use serde::{Deserialize, Serialize};
302        ///
303        /// #[derive(Serialize, Deserialize)]
304        /// struct Blob {
305        ///     #[serde(with = "Base64Vec")]
306        ///     data: Vec<u8>,
307        /// }
308        ///
309        /// let b = Blob { data: vec![1, 2, 3] };
310        /// let json = serde_json::to_string(&b).unwrap();
311        /// assert_eq!(json, r#"{"data":"AQID"}"#);
312        /// ```
313        #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
314        pub fn serialize<S>(
315            bytes: &[u8],
316            serializer: S,
317        ) -> Result<S::Ok, S::Error>
318        where
319            S: Serializer,
320        {
321            serialize_bytes(bytes, serializer)
322        }
323
324        /// Deserializes a byte vector from base64 if the format is
325        /// human-readable, or as raw bytes otherwise.
326        ///
327        /// Intended for use with `#[serde(with = "Base64Vec")]`.
328        ///
329        /// # Examples
330        ///
331        /// ```
332        /// use byte_wrapper::Base64Vec;
333        /// use serde::{Deserialize, Serialize};
334        ///
335        /// #[derive(Serialize, Deserialize)]
336        /// struct Blob {
337        ///     #[serde(with = "Base64Vec")]
338        ///     data: Vec<u8>,
339        /// }
340        ///
341        /// let b: Blob = serde_json::from_str(r#"{"data":"AQID"}"#).unwrap();
342        /// assert_eq!(b.data, [1, 2, 3]);
343        /// ```
344        #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
345        pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
346        where
347            D: Deserializer<'de>,
348        {
349            deserialize_bytes(deserializer)
350        }
351    }
352
353    #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
354    impl serde_core::Serialize for Base64Vec {
355        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
356        where
357            S: Serializer,
358        {
359            serialize_bytes(&self.0, serializer)
360        }
361    }
362
363    #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
364    impl<'de> serde_core::Deserialize<'de> for Base64Vec {
365        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
366        where
367            D: Deserializer<'de>,
368        {
369            deserialize_bytes(deserializer).map(Self)
370        }
371    }
372}
373
374#[cfg(feature = "schemars08")]
375mod schemars_impls {
376    use super::Base64Vec;
377    use crate::schemars_util::x_rust_type_extension;
378    use alloc::string::String;
379    use schemars08::{
380        JsonSchema,
381        r#gen::SchemaGenerator,
382        schema::{InstanceType, Schema, SchemaObject},
383    };
384
385    impl JsonSchema for Base64Vec {
386        fn schema_name() -> String {
387            "Base64Vec".into()
388        }
389
390        fn is_referenceable() -> bool {
391            false
392        }
393
394        fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
395            let mut extensions = x_rust_type_extension("Base64Vec");
396            extensions.insert("contentEncoding".into(), "base64".into());
397            Schema::Object(SchemaObject {
398                instance_type: Some(InstanceType::String.into()),
399                format: Some("byte".into()),
400                extensions,
401                ..Default::default()
402            })
403        }
404    }
405}