serdapt_base64/
lib.rs

1// Copyright (c) 2024 Stephane Raux. Distributed under the 0BSD license.
2
3//! # Overview
4//! - [📦 crates.io](https://crates.io/crates/serdapt-base64)
5//! - [📖 Documentation](https://docs.rs/serdapt-base64)
6//! - [âš– 0BSD license](https://spdx.org/licenses/0BSD.html)
7//!
8//! Base64 adapter for `#[serde(with = ...)]`. See [`serdapt`](https://docs.rs/serdapt) for more
9//! information on how to use such adapters.
10//!
11//! The documentation for [`Base64`] and [`Base64Array`] provides examples.
12//!
13//! # Contribute
14//! All contributions shall be licensed under the [0BSD license](https://spdx.org/licenses/0BSD.html).
15
16#![deny(missing_docs)]
17#![no_std]
18
19extern crate alloc;
20
21use alloc::vec::Vec;
22use base64::Engine;
23use core::{
24    fmt::{self, Display},
25    marker::PhantomData,
26};
27use serdapt::{DeserializeWith, SerializeWith};
28use serde::{de::Visitor, Deserializer, Serialize, Serializer};
29
30/// Adapter to serialize bytes as a base64 string
31///
32/// If the target type is an array, the [`Base64Array`] adapter should perform better.
33///
34/// # Example
35/// ```
36/// # extern crate alloc;
37/// # use alloc::{vec, vec::Vec};
38/// use serde::{Deserialize, Serialize};
39/// use serde_json::json;
40///
41/// #[derive(Debug, Deserialize, PartialEq, Serialize)]
42/// struct Foo(#[serde(with = "serdapt_base64::StdBase64")] Vec<u8>);
43///
44/// let x = Foo(vec![9, 1, 67]);
45/// let v = serde_json::to_value(&x).unwrap();
46/// assert_eq!(v, json!("CQFD"));
47/// let x2 = serde_json::from_value::<Foo>(v).unwrap();
48/// assert_eq!(x, x2);
49/// ```
50pub struct Base64<A = Standard> {
51    alphabet: PhantomData<A>,
52}
53
54/// Adapter to serialize bytes as a base64 string using the standard alphabet
55pub type StdBase64 = Base64;
56
57impl<A> Base64<A> {
58    /// Serializes value with adapter
59    pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
60    where
61        T: ?Sized,
62        S: Serializer,
63        Self: SerializeWith<T>,
64    {
65        Self::serialize_with(value, serializer)
66    }
67
68    /// Deserializes value with adapter
69    pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
70    where
71        D: Deserializer<'de>,
72        Self: DeserializeWith<'de, T>,
73    {
74        Self::deserialize_with(deserializer)
75    }
76}
77
78impl<A, T> SerializeWith<T> for Base64<A>
79where
80    A: AlphabetTag,
81    T: AsRef<[u8]>,
82{
83    fn serialize_with<S: Serializer>(bytes: &T, serializer: S) -> Result<S::Ok, S::Error> {
84        Serialize::serialize(&A::VALUE.inner.encode(bytes), serializer)
85    }
86}
87
88impl<'de, A, T> DeserializeWith<'de, T> for Base64<A>
89where
90    A: AlphabetTag,
91    T: TryFrom<Vec<u8>>,
92    T::Error: Display,
93{
94    fn deserialize_with<D>(deserializer: D) -> Result<T, D::Error>
95    where
96        D: Deserializer<'de>,
97    {
98        let bytes = deserializer.deserialize_str(Base64Visitor::new::<A>())?;
99        bytes.try_into().map_err(serde::de::Error::custom)
100    }
101}
102
103struct Base64Visitor {
104    engine: &'static base64::engine::GeneralPurpose,
105}
106
107impl Base64Visitor {
108    const fn new<A>() -> Self
109    where
110        A: AlphabetTag,
111    {
112        Self {
113            engine: &A::VALUE.inner,
114        }
115    }
116}
117
118impl Visitor<'_> for Base64Visitor {
119    type Value = Vec<u8>;
120
121    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        f.write_str("a base64 string")
123    }
124
125    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
126    where
127        E: serde::de::Error,
128    {
129        self.engine.decode(v).map_err(serde::de::Error::custom)
130    }
131}
132
133/// Adapter to serialize a byte array as a base64 string
134///
135/// # Example
136/// ```
137/// # extern crate alloc;
138/// # use alloc::vec;
139/// use serde::{Deserialize, Serialize};
140/// use serde_json::json;
141///
142/// #[derive(Debug, Deserialize, PartialEq, Serialize)]
143/// struct Foo(#[serde(with = "serdapt_base64::StdBase64Array")] [u8; 3]);
144///
145/// let x = Foo([9, 1, 67]);
146/// let v = serde_json::to_value(&x).unwrap();
147/// assert_eq!(v, json!("CQFD"));
148/// let x2 = serde_json::from_value::<Foo>(v).unwrap();
149/// assert_eq!(x, x2);
150/// ```
151pub struct Base64Array<A = Standard> {
152    alphabet: PhantomData<A>,
153}
154
155/// Adapter to serialize a byte array as a base64 string using the standard alphabet
156pub type StdBase64Array = Base64Array;
157
158impl<A> Base64Array<A> {
159    /// Serializes value with adapter
160    pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
161    where
162        T: ?Sized,
163        S: Serializer,
164        Self: SerializeWith<T>,
165    {
166        Self::serialize_with(value, serializer)
167    }
168
169    /// Deserializes value with adapter
170    pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
171    where
172        D: Deserializer<'de>,
173        Self: DeserializeWith<'de, T>,
174    {
175        Self::deserialize_with(deserializer)
176    }
177}
178
179impl<A, T> SerializeWith<T> for Base64Array<A>
180where
181    A: AlphabetTag,
182    T: AsRef<[u8]>,
183{
184    fn serialize_with<S: Serializer>(bytes: &T, serializer: S) -> Result<S::Ok, S::Error> {
185        Serialize::serialize(&A::VALUE.inner.encode(bytes), serializer)
186    }
187}
188
189impl<'de, A, const N: usize> DeserializeWith<'de, [u8; N]> for Base64Array<A>
190where
191    A: AlphabetTag,
192{
193    fn deserialize_with<D>(deserializer: D) -> Result<[u8; N], D::Error>
194    where
195        D: Deserializer<'de>,
196    {
197        deserializer.deserialize_str(Base64ArrayVisitor::<N>::new::<A>())
198    }
199}
200
201struct Base64ArrayVisitor<const N: usize> {
202    engine: &'static base64::engine::GeneralPurpose,
203}
204
205impl<const N: usize> Base64ArrayVisitor<N> {
206    const fn new<A>() -> Self
207    where
208        A: AlphabetTag,
209    {
210        Self {
211            engine: &A::VALUE.inner,
212        }
213    }
214}
215
216impl<const N: usize> Visitor<'_> for Base64ArrayVisitor<N> {
217    type Value = [u8; N];
218
219    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220        write!(f, "a base64 string encoding {N} bytes")
221    }
222
223    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
224    where
225        E: serde::de::Error,
226    {
227        let mut out = [0u8; N];
228        let n = self.engine.decode_slice(v, &mut out).map_err(|e| match e {
229            base64::DecodeSliceError::DecodeError(_) => serde::de::Error::custom(e),
230            base64::DecodeSliceError::OutputSliceTooSmall => {
231                serde::de::Error::invalid_length(N + 1, &self)
232            }
233        })?;
234        Some(out)
235            .filter(|_| n == N)
236            .ok_or_else(|| serde::de::Error::invalid_length(n, &self))
237    }
238}
239
240/// Types representing a base64 alphabet
241pub trait AlphabetTag {
242    /// Alphabet instance
243    const VALUE: Alphabet;
244}
245
246/// Alphabet definition
247#[derive(Debug)]
248pub struct Alphabet {
249    inner: base64::engine::GeneralPurpose,
250}
251
252impl Alphabet {
253    /// Constructs a base64 alphabet using the provided characters
254    ///
255    /// Panics if the character set is invalid (e.g. if it contains '=').
256    pub const fn new(chars: &[u8; 64], padding: PaddingStrategy) -> Alphabet {
257        Self {
258            inner: engine(chars, padding),
259        }
260    }
261}
262
263/// Adapter argument to use the standard base64 alphabet with required padding
264#[derive(Debug)]
265pub struct Standard;
266
267impl AlphabetTag for Standard {
268    const VALUE: Alphabet = Alphabet {
269        inner: base64::engine::general_purpose::STANDARD,
270    };
271}
272
273/// Adapter argument to use the URL-safe base64 alphabet with required padding
274#[derive(Debug)]
275pub struct UrlSafe;
276
277impl AlphabetTag for UrlSafe {
278    const VALUE: Alphabet = Alphabet {
279        inner: base64::engine::general_purpose::URL_SAFE,
280    };
281}
282
283const fn engine(chars: &[u8; 64], padding: PaddingStrategy) -> base64::engine::GeneralPurpose {
284    let s = match core::str::from_utf8(chars) {
285        Ok(s) => s,
286        Err(_) => panic!("Invalid base64 alphabet"),
287    };
288    let alphabet = match base64::alphabet::Alphabet::new(s) {
289        Ok(a) => a,
290        Err(_) => panic!("Invalid base64 alphabet"),
291    };
292    base64::engine::GeneralPurpose::new(
293        &alphabet,
294        base64::engine::GeneralPurposeConfig::new()
295            .with_encode_padding(padding.write())
296            .with_decode_padding_mode(padding.padding_mode()),
297    )
298}
299
300/// Ways to handle padding
301#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
302#[non_exhaustive]
303pub enum PaddingStrategy {
304    /// Padding is not written and is ignored if read
305    Ignored,
306    /// Padding is required
307    Required,
308    /// Padding is disallowed
309    None,
310}
311
312impl PaddingStrategy {
313    const fn padding_mode(self) -> base64::engine::DecodePaddingMode {
314        match self {
315            Self::Ignored => base64::engine::DecodePaddingMode::Indifferent,
316            Self::Required => base64::engine::DecodePaddingMode::RequireCanonical,
317            Self::None => base64::engine::DecodePaddingMode::RequireNone,
318        }
319    }
320
321    const fn write(self) -> bool {
322        match self {
323            Self::Required => true,
324            Self::Ignored | Self::None => false,
325        }
326    }
327}