alloy_json_rpc/
common.rs

1use serde::{de::Visitor, Deserialize, Serialize};
2use std::fmt;
3
4/// A JSON-RPC 2.0 ID object. This may be a number, a string, or null.
5///
6/// ### Ordering
7///
8/// This type implements [`PartialOrd`], [`Ord`], [`PartialEq`], and [`Eq`] so
9/// that it can be used as a key in a [`BTreeMap`] or an item in a
10/// [`BTreeSet`]. The ordering is as follows:
11///
12/// 1. Numbers are less than strings.
13/// 2. Strings are less than null.
14/// 3. Null is equal to null.
15///
16/// ### Hash
17///
18/// This type implements [`Hash`] so that it can be used as a key in a
19/// [`HashMap`] or an item in a [`HashSet`].
20///
21/// [`BTreeMap`]: std::collections::BTreeMap
22/// [`BTreeSet`]: std::collections::BTreeSet
23/// [`HashMap`]: std::collections::HashMap
24/// [`HashSet`]: std::collections::HashSet
25#[derive(Clone, Debug, PartialEq, Eq, Hash)]
26pub enum Id {
27    /// A number.
28    Number(u64),
29    /// A string.
30    String(String),
31    /// Null.
32    None,
33}
34
35impl From<u64> for Id {
36    fn from(value: u64) -> Self {
37        Self::Number(value)
38    }
39}
40
41impl From<String> for Id {
42    fn from(value: String) -> Self {
43        Self::String(value)
44    }
45}
46
47impl fmt::Display for Id {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        match self {
50            Self::Number(n) => write!(f, "{n}"),
51            Self::String(s) => f.write_str(s),
52            Self::None => f.write_str("null"),
53        }
54    }
55}
56
57impl Serialize for Id {
58    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
59        match self {
60            Self::Number(n) => serializer.serialize_u64(*n),
61            Self::String(s) => serializer.serialize_str(s),
62            Self::None => serializer.serialize_none(),
63        }
64    }
65}
66
67impl<'de> Deserialize<'de> for Id {
68    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
69    where
70        D: serde::Deserializer<'de>,
71    {
72        struct IdVisitor;
73
74        impl Visitor<'_> for IdVisitor {
75            type Value = Id;
76
77            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
78                write!(formatter, "a string, a number, or null")
79            }
80
81            fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
82            where
83                E: serde::de::Error,
84            {
85                Ok(v.into())
86            }
87
88            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
89            where
90                E: serde::de::Error,
91            {
92                Ok(v.to_owned().into())
93            }
94
95            fn visit_none<E>(self) -> Result<Self::Value, E>
96            where
97                E: serde::de::Error,
98            {
99                Ok(Id::None)
100            }
101
102            fn visit_unit<E>(self) -> Result<Self::Value, E>
103            where
104                E: serde::de::Error,
105            {
106                Ok(Id::None)
107            }
108        }
109
110        deserializer.deserialize_any(IdVisitor)
111    }
112}
113
114impl PartialOrd for Id {
115    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
116        Some(self.cmp(other))
117    }
118}
119
120impl Ord for Id {
121    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
122        // numbers < strings
123        // strings < null
124        // null == null
125        match (self, other) {
126            (Self::Number(a), Self::Number(b)) => a.cmp(b),
127            (Self::Number(_), _) => std::cmp::Ordering::Less,
128
129            (Self::String(_), Self::Number(_)) => std::cmp::Ordering::Greater,
130            (Self::String(a), Self::String(b)) => a.cmp(b),
131            (Self::String(_), Self::None) => std::cmp::Ordering::Less,
132
133            (Self::None, Self::None) => std::cmp::Ordering::Equal,
134            (Self::None, _) => std::cmp::Ordering::Greater,
135        }
136    }
137}
138
139impl Id {
140    /// Returns `true` if the ID is a number.
141    pub const fn is_number(&self) -> bool {
142        matches!(self, Self::Number(_))
143    }
144
145    /// Returns `true` if the ID is a string.
146    pub const fn is_string(&self) -> bool {
147        matches!(self, Self::String(_))
148    }
149
150    /// Returns `true` if the ID is `None`.
151    pub const fn is_none(&self) -> bool {
152        matches!(self, Self::None)
153    }
154
155    /// Returns the ID as a number, if it is one.
156    pub const fn as_number(&self) -> Option<u64> {
157        match self {
158            Self::Number(n) => Some(*n),
159            _ => None,
160        }
161    }
162
163    /// Returns the ID as a string, if it is one.
164    pub fn as_string(&self) -> Option<&str> {
165        match self {
166            Self::String(s) => Some(s),
167            _ => None,
168        }
169    }
170}
171
172#[cfg(test)]
173mod test {
174    use super::*;
175    use std::collections::{BTreeSet, HashSet};
176
177    #[derive(Debug, PartialEq, Serialize, Deserialize)]
178    struct TestCase {
179        id: Id,
180    }
181
182    #[test]
183    fn it_serializes_and_deserializes() {
184        let cases = [
185            (TestCase { id: Id::Number(1) }, r#"{"id":1}"#),
186            (TestCase { id: Id::String("foo".to_string()) }, r#"{"id":"foo"}"#),
187            (TestCase { id: Id::None }, r#"{"id":null}"#),
188        ];
189        for (case, expected) in cases {
190            let serialized = serde_json::to_string(&case).unwrap();
191            assert_eq!(serialized, expected);
192
193            let deserialized: TestCase = serde_json::from_str(expected).unwrap();
194            assert_eq!(deserialized, case);
195        }
196    }
197
198    #[test]
199    fn test_is_methods() {
200        let id_number = Id::Number(42);
201        let id_string = Id::String("test_string".to_string());
202        let id_none = Id::None;
203
204        assert!(id_number.is_number());
205        assert!(!id_number.is_string());
206        assert!(!id_number.is_none());
207
208        assert!(!id_string.is_number());
209        assert!(id_string.is_string());
210        assert!(!id_string.is_none());
211
212        assert!(!id_none.is_number());
213        assert!(!id_none.is_string());
214        assert!(id_none.is_none());
215    }
216
217    #[test]
218    fn test_as_methods() {
219        let id_number = Id::Number(42);
220        let id_string = Id::String("test_string".to_string());
221        let id_none = Id::None;
222
223        assert_eq!(id_number.as_number(), Some(42));
224        assert_eq!(id_string.as_number(), None);
225        assert_eq!(id_none.as_number(), None);
226
227        assert_eq!(id_number.as_string(), None);
228        assert_eq!(id_string.as_string(), Some("test_string"));
229        assert_eq!(id_none.as_string(), None);
230    }
231
232    #[test]
233    fn test_ordering() {
234        let id_number = Id::Number(42);
235        let id_string = Id::String("test_string".to_string());
236        let id_none = Id::None;
237
238        assert!(id_number < id_string);
239        assert!(id_string < id_none);
240        assert!(id_none == Id::None);
241    }
242
243    #[test]
244    fn test_serialization_deserialization_edge_cases() {
245        // Edge cases for large numbers, empty strings, and None.
246        let cases = [
247            (TestCase { id: Id::Number(u64::MAX) }, r#"{"id":18446744073709551615}"#),
248            (TestCase { id: Id::String("".to_string()) }, r#"{"id":""}"#),
249            (TestCase { id: Id::None }, r#"{"id":null}"#),
250        ];
251        for (case, expected) in cases {
252            let serialized = serde_json::to_string(&case).unwrap();
253            assert_eq!(serialized, expected);
254
255            let deserialized: TestCase = serde_json::from_str(expected).unwrap();
256            assert_eq!(deserialized, case);
257        }
258    }
259
260    #[test]
261    fn test_partial_eq_and_hash() {
262        let id1 = Id::Number(42);
263        let id2 = Id::String("foo".to_string());
264        let id3 = Id::None;
265
266        let mut hash_set = HashSet::new();
267        let mut btree_set = BTreeSet::new();
268
269        hash_set.insert(id1.clone());
270        hash_set.insert(id2.clone());
271        hash_set.insert(id3.clone());
272
273        btree_set.insert(id1);
274        btree_set.insert(id2);
275        btree_set.insert(id3);
276
277        assert_eq!(hash_set.len(), 3);
278        assert_eq!(btree_set.len(), 3);
279
280        assert!(hash_set.contains(&Id::Number(42)));
281        assert!(btree_set.contains(&Id::String("foo".to_string())));
282    }
283}