serde_utils/
quoted_int.rs

1//! Formats some integer types using quotes.
2//!
3//! E.g., `1` serializes as `"1"`.
4//!
5//! Quotes can be optional during decoding.
6
7use alloy_primitives::U256;
8use serde::{Deserializer, Serializer};
9use serde_derive::{Deserialize, Serialize};
10use std::convert::TryFrom;
11use std::marker::PhantomData;
12
13macro_rules! define_mod {
14    ($int: ty) => {
15        /// Serde support for deserializing quoted integers.
16        ///
17        /// Configurable so that quotes are either required or optional.
18        pub struct QuotedIntVisitor<T> {
19            require_quotes: bool,
20            _phantom: PhantomData<T>,
21        }
22
23        impl<'a, T> serde::de::Visitor<'a> for QuotedIntVisitor<T>
24        where
25            T: From<$int> + Into<$int> + Copy + TryFrom<u64>,
26        {
27            type Value = T;
28
29            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
30                if self.require_quotes {
31                    write!(formatter, "a quoted integer")
32                } else {
33                    write!(formatter, "a quoted or unquoted integer")
34                }
35            }
36
37            fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
38            where
39                E: serde::de::Error,
40            {
41                s.parse::<$int>()
42                    .map(T::from)
43                    .map_err(serde::de::Error::custom)
44            }
45
46            fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
47            where
48                E: serde::de::Error,
49            {
50                if self.require_quotes {
51                    Err(serde::de::Error::custom(
52                        "received unquoted integer when quotes are required",
53                    ))
54                } else {
55                    T::try_from(v).map_err(|_| serde::de::Error::custom("invalid integer"))
56                }
57            }
58        }
59
60        /// Compositional wrapper type that allows quotes or no quotes.
61        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
62        #[serde(transparent)]
63        pub struct MaybeQuoted<T>
64        where
65            T: From<$int> + Into<$int> + Copy + TryFrom<u64>,
66        {
67            #[serde(with = "self")]
68            pub value: T,
69        }
70
71        /// Wrapper type for requiring quotes on a `$int`-like type.
72        ///
73        /// Unlike using `serde(with = "quoted_$int::require_quotes")` this is composable, and can be nested
74        /// inside types like `Option`, `Result` and `Vec`.
75        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
76        #[serde(transparent)]
77        pub struct Quoted<T>
78        where
79            T: From<$int> + Into<$int> + Copy + TryFrom<u64>,
80        {
81            #[serde(with = "require_quotes")]
82            pub value: T,
83        }
84
85        /// Serialize with quotes.
86        pub fn serialize<S, T>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
87        where
88            S: Serializer,
89            T: From<$int> + Into<$int> + Copy,
90        {
91            let v: $int = (*value).into();
92            serializer.serialize_str(&format!("{}", v))
93        }
94
95        /// Deserialize with or without quotes.
96        pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
97        where
98            D: Deserializer<'de>,
99            T: From<$int> + Into<$int> + Copy + TryFrom<u64>,
100        {
101            deserializer.deserialize_any(QuotedIntVisitor {
102                require_quotes: false,
103                _phantom: PhantomData,
104            })
105        }
106
107        /// Requires quotes when deserializing.
108        ///
109        /// Usage: `#[serde(with = "quoted_u64::require_quotes")]`.
110        pub mod require_quotes {
111            pub use super::serialize;
112            use super::*;
113
114            pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
115            where
116                D: Deserializer<'de>,
117                T: From<$int> + Into<$int> + Copy + TryFrom<u64>,
118            {
119                deserializer.deserialize_any(QuotedIntVisitor {
120                    require_quotes: true,
121                    _phantom: PhantomData,
122                })
123            }
124        }
125
126        #[cfg(test)]
127        mod test {
128            use super::*;
129
130            #[test]
131            fn require_quotes() {
132                let x = serde_json::from_str::<Quoted<$int>>("\"8\"").unwrap();
133                assert_eq!(x.value, 8);
134                serde_json::from_str::<Quoted<$int>>("8").unwrap_err();
135            }
136        }
137    };
138}
139
140pub mod quoted_u8 {
141    use super::*;
142
143    define_mod!(u8);
144}
145
146pub mod quoted_u32 {
147    use super::*;
148
149    define_mod!(u32);
150}
151
152pub mod quoted_u64 {
153    use super::*;
154
155    define_mod!(u64);
156}
157
158pub mod quoted_i64 {
159    use super::*;
160
161    define_mod!(i64);
162}
163
164pub mod quoted_u256 {
165    use super::*;
166
167    struct U256Visitor;
168
169    impl<'de> serde::de::Visitor<'de> for U256Visitor {
170        type Value = U256;
171
172        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
173            formatter.write_str("a quoted U256 integer")
174        }
175
176        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
177        where
178            E: serde::de::Error,
179        {
180            U256::from_str_radix(v, 10).map_err(serde::de::Error::custom)
181        }
182    }
183
184    /// Serialize with quotes.
185    pub fn serialize<S>(value: &U256, serializer: S) -> Result<S::Ok, S::Error>
186    where
187        S: Serializer,
188    {
189        serializer.serialize_str(&format!("{}", value))
190    }
191
192    /// Deserialize with quotes.
193    pub fn deserialize<'de, D>(deserializer: D) -> Result<U256, D::Error>
194    where
195        D: Deserializer<'de>,
196    {
197        deserializer.deserialize_str(U256Visitor)
198    }
199}
200
201#[cfg(test)]
202mod test {
203    use super::*;
204
205    #[derive(Debug, PartialEq, Serialize, Deserialize)]
206    #[serde(transparent)]
207    struct WrappedU256(#[serde(with = "quoted_u256")] U256);
208
209    #[test]
210    fn u256_with_quotes() {
211        assert_eq!(
212            &serde_json::to_string(&WrappedU256(U256::from(1))).unwrap(),
213            "\"1\""
214        );
215        assert_eq!(
216            serde_json::from_str::<WrappedU256>("\"1\"").unwrap(),
217            WrappedU256(U256::from(1))
218        );
219    }
220
221    #[test]
222    fn u256_without_quotes() {
223        serde_json::from_str::<WrappedU256>("1").unwrap_err();
224    }
225
226    #[derive(Debug, PartialEq, Serialize, Deserialize)]
227    #[serde(transparent)]
228    struct WrappedI64(#[serde(with = "quoted_i64")] i64);
229
230    #[test]
231    fn negative_i64_with_quotes() {
232        assert_eq!(
233            serde_json::from_str::<WrappedI64>("\"-200\"").unwrap().0,
234            -200
235        );
236        assert_eq!(
237            serde_json::to_string(&WrappedI64(-12_500)).unwrap(),
238            "\"-12500\""
239        );
240    }
241
242    // It would be OK if this worked, but we don't need it to (i64s should always be quoted).
243    #[test]
244    fn negative_i64_without_quotes() {
245        serde_json::from_str::<WrappedI64>("-200").unwrap_err();
246    }
247}