1use serde::{de::Visitor, Deserialize, Serialize};
2use std::fmt;
3
4#[derive(Clone, Debug, PartialEq, Eq, Hash)]
26pub enum Id {
27 Number(u64),
29 String(String),
31 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 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 pub const fn is_number(&self) -> bool {
142 matches!(self, Self::Number(_))
143 }
144
145 pub const fn is_string(&self) -> bool {
147 matches!(self, Self::String(_))
148 }
149
150 pub const fn is_none(&self) -> bool {
152 matches!(self, Self::None)
153 }
154
155 pub const fn as_number(&self) -> Option<u64> {
157 match self {
158 Self::Number(n) => Some(*n),
159 _ => None,
160 }
161 }
162
163 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 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}