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