ruint/support/
serde.rs

1//! Support for the [`serde`](https://crates.io/crates/serde) crate.
2
3#![cfg(feature = "serde")]
4#![cfg_attr(docsrs, doc(cfg(feature = "serde")))]
5
6use crate::{nbytes, Bits, Uint};
7use core::{
8    fmt::{Formatter, Result as FmtResult, Write},
9    str,
10};
11use serde::{
12    de::{Error, Unexpected, Visitor},
13    Deserialize, Deserializer, Serialize, Serializer,
14};
15
16#[allow(unused_imports)]
17use alloc::string::String;
18
19/// Canonical serialization for all human-readable instances of `Uint<0, 0>`,
20/// and minimal human-readable `Uint<BITS, LIMBS>::ZERO` for any bit size.
21const ZERO_STR: &str = "0x0";
22
23impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
24    fn serialize_human_full<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
25        if BITS == 0 {
26            return s.serialize_str(ZERO_STR);
27        }
28
29        let mut result = String::with_capacity(2 + nbytes(BITS) * 2);
30        result.push_str("0x");
31
32        self.as_le_bytes()
33            .iter()
34            .rev()
35            .try_for_each(|byte| write!(result, "{byte:02x}"))
36            .unwrap();
37
38        s.serialize_str(&result)
39    }
40
41    fn serialize_human_minimal<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
42        if self.is_zero() {
43            return s.serialize_str(ZERO_STR);
44        }
45
46        s.serialize_str(&format!("{self:#x}"))
47    }
48
49    fn serialize_binary<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
50        s.serialize_bytes(&self.to_be_bytes_vec())
51    }
52}
53
54/// Serialize a [`Uint`] value.
55///
56/// For human readable formats a `0x` prefixed lower case hex string is used.
57/// For binary formats a byte array is used. Leading zeros are included.
58impl<const BITS: usize, const LIMBS: usize> Serialize for Uint<BITS, LIMBS> {
59    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
60        if serializer.is_human_readable() {
61            self.serialize_human_minimal(serializer)
62        } else {
63            self.serialize_binary(serializer)
64        }
65    }
66}
67
68/// Deserialize human readable hex strings or byte arrays into [`Uint`].
69///
70/// Hex strings can be upper/lower/mixed case, have an optional `0x` prefix, and
71/// can be any length. They are interpreted big-endian.
72impl<'de, const BITS: usize, const LIMBS: usize> Deserialize<'de> for Uint<BITS, LIMBS> {
73    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
74        if deserializer.is_human_readable() {
75            deserializer.deserialize_any(HrVisitor)
76        } else {
77            deserializer.deserialize_bytes(ByteVisitor)
78        }
79    }
80}
81
82impl<const BITS: usize, const LIMBS: usize> Serialize for Bits<BITS, LIMBS> {
83    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
84        if serializer.is_human_readable() {
85            self.as_uint().serialize_human_full(serializer)
86        } else {
87            self.as_uint().serialize_binary(serializer)
88        }
89    }
90}
91
92impl<'de, const BITS: usize, const LIMBS: usize> Deserialize<'de> for Bits<BITS, LIMBS> {
93    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
94        Uint::deserialize(deserializer).map(Self::from)
95    }
96}
97
98/// Serde Visitor for human readable formats.
99///
100/// Accepts either a primitive number, a decimal or a hexadecimal string.
101struct HrVisitor<const BITS: usize, const LIMBS: usize>;
102
103impl<const BITS: usize, const LIMBS: usize> Visitor<'_> for HrVisitor<BITS, LIMBS> {
104    type Value = Uint<BITS, LIMBS>;
105
106    fn expecting(&self, formatter: &mut Formatter) -> FmtResult {
107        write!(formatter, "a {} byte hex string", nbytes(BITS))
108    }
109
110    fn visit_u64<E: Error>(self, v: u64) -> Result<Self::Value, E> {
111        Uint::try_from(v).map_err(|_| Error::invalid_value(Unexpected::Unsigned(v), &self))
112    }
113
114    fn visit_u128<E: Error>(self, v: u128) -> Result<Self::Value, E> {
115        // `Unexpected::Unsigned` cannot contain a `u128`
116        Uint::try_from(v).map_err(Error::custom)
117    }
118
119    fn visit_str<E: Error>(self, value: &str) -> Result<Self::Value, E> {
120        // Shortcut for common case
121        if value == ZERO_STR {
122            return Ok(Uint::<BITS, LIMBS>::ZERO);
123        }
124        // `ZERO_STR` is the only valid serialization of `Uint<0, 0>`, so if we
125        // have not shortcut, we are in an error case
126        if BITS == 0 {
127            return Err(Error::invalid_value(Unexpected::Str(value), &self));
128        }
129
130        value
131            .parse()
132            .map_err(|_| Error::invalid_value(Unexpected::Str(value), &self))
133    }
134}
135
136/// Serde Visitor for non-human readable formats
137struct ByteVisitor<const BITS: usize, const LIMBS: usize>;
138
139impl<const BITS: usize, const LIMBS: usize> Visitor<'_> for ByteVisitor<BITS, LIMBS> {
140    type Value = Uint<BITS, LIMBS>;
141
142    fn expecting(&self, formatter: &mut Formatter) -> FmtResult {
143        write!(formatter, "{BITS} bits of binary data in big endian order")
144    }
145
146    fn visit_bytes<E: Error>(self, value: &[u8]) -> Result<Self::Value, E> {
147        if value.len() != nbytes(BITS) {
148            return Err(Error::invalid_length(value.len(), &self));
149        }
150        Uint::try_from_be_slice(value).ok_or_else(|| {
151            Error::invalid_value(
152                Unexpected::Other(&format!("too large for Uint<{BITS}>")),
153                &self,
154            )
155        })
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162    use crate::{const_for, nlimbs};
163    use proptest::proptest;
164
165    #[allow(unused_imports)]
166    use alloc::vec::Vec;
167
168    #[test]
169    fn test_serde_human_readable() {
170        const_for!(BITS in SIZES {
171            const LIMBS: usize = nlimbs(BITS);
172            proptest!(|(value: Uint<BITS, LIMBS>)| {
173                let serialized = serde_json::to_string(&value).unwrap();
174                let deserialized = serde_json::from_str(&serialized).unwrap();
175                assert_eq!(value, deserialized);
176            });
177            proptest!(|(value: Bits<BITS, LIMBS>)| {
178                let serialized = serde_json::to_string(&value).unwrap();
179                let deserialized = serde_json::from_str(&serialized).unwrap();
180                assert_eq!(value, deserialized);
181            });
182        });
183    }
184
185    #[test]
186    fn test_human_readable_de() {
187        let jason = r#"[
188            1,
189            "0x1",
190            "0o1",
191            "0b1"
192        ]"#;
193        let numbers: Vec<Uint<1, 1>> = serde_json::from_str(jason).unwrap();
194        uint! {
195            assert_eq!(numbers, vec![1_U1, 1_U1, 1_U1, 1_U1]);
196        }
197
198        let jason = r#"[
199            "",
200            "0x",
201            "0o",
202            "0b"
203        ]"#;
204        let numbers: Vec<Uint<1, 1>> = serde_json::from_str(jason).unwrap();
205        uint! {
206            assert_eq!(numbers, vec![0_U1, 0_U1, 0_U1, 0_U1]);
207        }
208    }
209
210    #[test]
211    fn test_serde_machine_readable() {
212        const_for!(BITS in SIZES {
213            const LIMBS: usize = nlimbs(BITS);
214            proptest!(|(value: Uint<BITS, LIMBS>)| {
215                let serialized = bincode::serialize(&value).unwrap();
216                let deserialized = bincode::deserialize(&serialized[..]).unwrap();
217                assert_eq!(value, deserialized);
218            });
219            proptest!(|(value: Bits<BITS, LIMBS>)| {
220                let serialized = bincode::serialize(&value).unwrap();
221                let deserialized = bincode::deserialize(&serialized[..]).unwrap();
222                assert_eq!(value, deserialized);
223            });
224        });
225    }
226
227    #[test]
228    fn test_serde_invalid_size_error() {
229        // Test that if we add a character to a value that is already the max length for
230        // the given number of bits, we get an error.
231        const_for!(BITS in SIZES {
232            const LIMBS: usize = nlimbs(BITS);
233            let value = Uint::<BITS, LIMBS>::MAX;
234            let mut serialized = serde_json::to_string(&value).unwrap();
235
236            // ensure format of serialized value is correct ("0x...")
237            assert_eq!(&serialized[..3], "\"0x");
238            // last character should be a quote
239            assert_eq!(&serialized[serialized.len() - 1..], "\"");
240
241            // strip the last character, add a zero, and finish with a quote
242            serialized.pop();
243            serialized.push('0');
244            serialized.push('"');
245            let deserialized = serde_json::from_str::<Uint<BITS, LIMBS>>(&serialized);
246            assert!(deserialized.is_err(), "{BITS} {serialized}");
247        });
248    }
249}