serdapt_base58/
lib.rs

1// Copyright (c) 2025 Stephane Raux. Distributed under the 0BSD license.
2
3//! # Overview
4//! - [📦 crates.io](https://crates.io/crates/serdapt-base58)
5//! - [📖 Documentation](https://docs.rs/serdapt-base58)
6//! - [âš– 0BSD license](https://spdx.org/licenses/0BSD.html)
7//!
8//! Base58 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 [`Base58`] and [`Base58Array`] 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 core::{
23    fmt::{self, Display},
24    marker::PhantomData,
25};
26use serdapt::{DeserializeWith, SerializeWith};
27use serde::{de::Visitor, Deserializer, Serialize, Serializer};
28
29/// Adapter to serialize bytes as a base58 string
30///
31/// If the target type is an array, the [`Base58Array`] adapter should perform better.
32///
33/// # Example
34/// ```
35/// # extern crate alloc;
36/// # use alloc::{vec, vec::Vec};
37/// use serde::{Deserialize, Serialize};
38/// use serde_json::json;
39///
40/// #[derive(Debug, Deserialize, PartialEq, Serialize)]
41/// struct Foo(#[serde(with = "serdapt_base58::BitcoinBase58")] Vec<u8>);
42///
43/// let x = Foo(vec![9, 1, 67]);
44/// let v = serde_json::to_value(&x).unwrap();
45/// assert_eq!(v, json!("42Rx"));
46/// let x2 = serde_json::from_value::<Foo>(v).unwrap();
47/// assert_eq!(x, x2);
48/// ```
49pub struct Base58<A = Bitcoin> {
50    alphabet: PhantomData<A>,
51}
52
53/// Adapter to serialize bytes as a base58 string using the Bitcoin alphabet
54pub type BitcoinBase58 = Base58<Bitcoin>;
55
56/// Adapter to serialize bytes as a base58 string using the Monero alphabet
57pub type MoneroBase58 = Base58<Monero>;
58
59/// Adapter to serialize bytes as a base58 string using the Ripple alphabet
60pub type RippleBase58 = Base58<Ripple>;
61
62/// Adapter to serialize bytes as a base58 string using the Flickr alphabet
63pub type FlickrBase58 = Base58<Flickr>;
64
65impl<A> Base58<A> {
66    /// Serializes value with adapter
67    pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
68    where
69        T: ?Sized,
70        S: Serializer,
71        Self: SerializeWith<T>,
72    {
73        Self::serialize_with(value, serializer)
74    }
75
76    /// Deserializes value with adapter
77    pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
78    where
79        D: Deserializer<'de>,
80        Self: DeserializeWith<'de, T>,
81    {
82        Self::deserialize_with(deserializer)
83    }
84}
85
86impl<A, T> SerializeWith<T> for Base58<A>
87where
88    A: AlphabetTag,
89    T: AsRef<[u8]>,
90{
91    fn serialize_with<S: Serializer>(bytes: &T, serializer: S) -> Result<S::Ok, S::Error> {
92        Serialize::serialize(
93            &bs58::encode(bytes)
94                .with_alphabet(&A::VALUE.inner)
95                .into_string(),
96            serializer,
97        )
98    }
99}
100
101impl<'de, A, T> DeserializeWith<'de, T> for Base58<A>
102where
103    A: AlphabetTag,
104    T: TryFrom<Vec<u8>>,
105    T::Error: Display,
106{
107    fn deserialize_with<D>(deserializer: D) -> Result<T, D::Error>
108    where
109        D: Deserializer<'de>,
110    {
111        let bytes = deserializer.deserialize_str(Base58Visitor::new::<A>())?;
112        bytes.try_into().map_err(serde::de::Error::custom)
113    }
114}
115
116struct Base58Visitor {
117    alphabet: &'static bs58::Alphabet,
118}
119
120impl Base58Visitor {
121    const fn new<A>() -> Self
122    where
123        A: AlphabetTag,
124    {
125        Self {
126            alphabet: &A::VALUE.inner,
127        }
128    }
129}
130
131impl Visitor<'_> for Base58Visitor {
132    type Value = Vec<u8>;
133
134    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        f.write_str("a base58 string")
136    }
137
138    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
139    where
140        E: serde::de::Error,
141    {
142        bs58::decode(v)
143            .with_alphabet(self.alphabet)
144            .into_vec()
145            .map_err(E::custom)
146    }
147}
148
149/// Adapter to serialize a byte array as a base58 string
150///
151/// # Example
152/// ```
153/// # extern crate alloc;
154/// # use alloc::vec;
155/// use serde::{Deserialize, Serialize};
156/// use serde_json::json;
157///
158/// #[derive(Debug, Deserialize, PartialEq, Serialize)]
159/// struct Foo(#[serde(with = "serdapt_base58::BitcoinBase58Array")] [u8; 3]);
160///
161/// let x = Foo([9, 1, 67]);
162/// let v = serde_json::to_value(&x).unwrap();
163/// assert_eq!(v, json!("42Rx"));
164/// let x2 = serde_json::from_value::<Foo>(v).unwrap();
165/// assert_eq!(x, x2);
166/// ```
167pub struct Base58Array<A = Bitcoin> {
168    alphabet: PhantomData<A>,
169}
170
171/// Adapter to serialize a byte array as a base58 string using the Bitcoin alphabet
172pub type BitcoinBase58Array = Base58Array<Bitcoin>;
173
174/// Adapter to serialize a byte array as a base58 string using the Monero alphabet
175pub type MoneroBase58Array = Base58Array<Monero>;
176
177/// Adapter to serialize a byte array as a base58 string using the Ripple alphabet
178pub type RippleBase58Array = Base58Array<Ripple>;
179
180/// Adapter to serialize a byte array as a base58 string using the Flickr alphabet
181pub type FlickrBase58Array = Base58Array<Flickr>;
182
183impl<A> Base58Array<A> {
184    /// Serializes value with adapter
185    pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
186    where
187        T: ?Sized,
188        S: Serializer,
189        Self: SerializeWith<T>,
190    {
191        Self::serialize_with(value, serializer)
192    }
193
194    /// Deserializes value with adapter
195    pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
196    where
197        D: Deserializer<'de>,
198        Self: DeserializeWith<'de, T>,
199    {
200        Self::deserialize_with(deserializer)
201    }
202}
203
204impl<A, T> SerializeWith<T> for Base58Array<A>
205where
206    A: AlphabetTag,
207    T: AsRef<[u8]>,
208{
209    fn serialize_with<S: Serializer>(bytes: &T, serializer: S) -> Result<S::Ok, S::Error> {
210        Serialize::serialize(
211            &bs58::encode(bytes)
212                .with_alphabet(&A::VALUE.inner)
213                .into_string(),
214            serializer,
215        )
216    }
217}
218
219impl<'de, A, const N: usize> DeserializeWith<'de, [u8; N]> for Base58Array<A>
220where
221    A: AlphabetTag,
222{
223    fn deserialize_with<D>(deserializer: D) -> Result<[u8; N], D::Error>
224    where
225        D: Deserializer<'de>,
226    {
227        deserializer.deserialize_str(Base58ArrayVisitor::new::<A>())
228    }
229}
230
231struct Base58ArrayVisitor<const N: usize> {
232    alphabet: &'static bs58::Alphabet,
233}
234
235impl<const N: usize> Base58ArrayVisitor<N> {
236    const fn new<A>() -> Self
237    where
238        A: AlphabetTag,
239    {
240        Self {
241            alphabet: &A::VALUE.inner,
242        }
243    }
244}
245
246impl<const N: usize> Visitor<'_> for Base58ArrayVisitor<N> {
247    type Value = [u8; N];
248
249    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250        write!(f, "a base58 string encoding {N} bytes")
251    }
252
253    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
254    where
255        E: serde::de::Error,
256    {
257        let mut out = [0u8; N];
258        let n = bs58::decode(v)
259            .with_alphabet(self.alphabet)
260            .onto(&mut out[..])
261            .map_err(|e| match e {
262                bs58::decode::Error::BufferTooSmall => E::invalid_length(N + 1, &self),
263                _ => E::custom(e),
264            })?;
265        Some(out)
266            .filter(|_| n == N)
267            .ok_or_else(|| serde::de::Error::invalid_length(n, &self))
268    }
269}
270
271/// Types representing a base58 alphabet
272pub trait AlphabetTag {
273    /// Alphabet instance
274    const VALUE: Alphabet;
275}
276
277/// Alphabet definition
278#[derive(Debug)]
279pub struct Alphabet {
280    inner: bs58::Alphabet,
281}
282
283impl Alphabet {
284    /// Constructs a base58 alphabet using the provided characters
285    ///
286    /// Panics if the character set is invalid (e.g. duplicate values).
287    pub const fn new(chars: &[u8; 58]) -> Self {
288        Self {
289            inner: bs58::Alphabet::new_unwrap(chars),
290        }
291    }
292}
293
294/// Adapter argument to use the Bitcoin base58 alphabet
295#[derive(Debug)]
296pub struct Bitcoin;
297
298impl AlphabetTag for Bitcoin {
299    const VALUE: Alphabet = Alphabet {
300        inner: *bs58::Alphabet::BITCOIN,
301    };
302}
303
304/// Adapter argument to use the Monero base58 alphabet
305#[derive(Debug)]
306pub struct Monero;
307
308impl AlphabetTag for Monero {
309    const VALUE: Alphabet = Alphabet {
310        inner: *bs58::Alphabet::MONERO,
311    };
312}
313
314/// Adapter argument to use the Ripple base58 alphabet
315#[derive(Debug)]
316pub struct Ripple;
317
318impl AlphabetTag for Ripple {
319    const VALUE: Alphabet = Alphabet {
320        inner: *bs58::Alphabet::RIPPLE,
321    };
322}
323
324/// Adapter argument to use the Flickr base58 alphabet
325#[derive(Debug)]
326pub struct Flickr;
327
328impl AlphabetTag for Flickr {
329    const VALUE: Alphabet = Alphabet {
330        inner: *bs58::Alphabet::FLICKR,
331    };
332}