openzeppelin_relayer/utils/serde/
u64_deserializer.rs

1//! Serde utilities for u64 values.
2//!
3//! Provides a tolerant deserializer (string / integer / float) and a
4//! string serializer so large u64 values survive a Redis/Lua `cjson`
5//! round-trip. See `super::safe_float` for why the float form is tolerated and
6//! why values beyond 2^53 are rejected rather than silently truncated.
7
8use std::fmt;
9
10use serde::{de, Deserializer, Serializer};
11
12use super::safe_float::f64_to_safe_integer;
13
14#[derive(Debug)]
15struct U64Visitor;
16
17impl de::Visitor<'_> for U64Visitor {
18    type Value = u64;
19
20    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
21        formatter.write_str("a string containing a u64 number or a u64 integer")
22    }
23
24    // Handle string inputs like "340282366920938463463374607431768211455"
25    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
26    where
27        E: de::Error,
28    {
29        value.parse::<u64>().map_err(de::Error::custom)
30    }
31
32    // Handle u64 inputs
33    #[allow(clippy::unnecessary_cast)]
34    fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
35    where
36        E: de::Error,
37    {
38        Ok(value)
39    }
40
41    // Handle i64 inputs
42    fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
43    where
44        E: de::Error,
45    {
46        if value < 0 {
47            Err(de::Error::custom(
48                "negative value cannot be converted to u64",
49            ))
50        } else {
51            Ok(value as u64)
52        }
53    }
54
55    // Corrupted form left in Redis by a Lua cjson round-trip (e.g. 123.0).
56    // Values beyond 2^53 are rejected because precision was already lost.
57    fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
58    where
59        E: de::Error,
60    {
61        let n = f64_to_safe_integer::<E>(value)?;
62        u64::try_from(n).map_err(de::Error::custom)
63    }
64}
65
66pub fn deserialize_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
67where
68    D: Deserializer<'de>,
69{
70    deserializer.deserialize_any(U64Visitor)
71}
72
73/// Serialize a u64 as a JSON string so it survives a Redis/Lua `cjson`
74/// round-trip.
75pub fn serialize_u64<S>(value: &u64, serializer: S) -> Result<S::Ok, S::Error>
76where
77    S: Serializer,
78{
79    serializer.serialize_str(&value.to_string())
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85    use serde::de::value::{
86        Error as ValueError, I64Deserializer, StringDeserializer, U64Deserializer,
87    };
88
89    #[test]
90    fn test_deserialize_from_string() {
91        let input = "12345";
92        let deserializer = StringDeserializer::<ValueError>::new(input.to_string());
93        let result = deserialize_u64(deserializer);
94        assert!(result.is_ok());
95        assert_eq!(result.unwrap(), 12345);
96    }
97
98    #[test]
99    fn test_deserialize_from_string_max_u64() {
100        let input = "18446744073709551615"; // u64::MAX
101        let deserializer = StringDeserializer::<ValueError>::new(input.to_string());
102        let result = deserialize_u64(deserializer);
103        assert!(result.is_ok());
104        assert_eq!(result.unwrap(), u64::MAX);
105    }
106
107    #[test]
108    fn test_deserialize_from_invalid_string() {
109        let input = "not a number";
110        let deserializer = StringDeserializer::<ValueError>::new(input.to_string());
111        let result = deserialize_u64(deserializer);
112        assert!(result.is_err());
113    }
114
115    #[test]
116    fn test_deserialize_from_u64() {
117        let input: u64 = 54321;
118        let deserializer = U64Deserializer::<ValueError>::new(input);
119        let result = deserialize_u64(deserializer);
120        assert!(result.is_ok());
121        assert_eq!(result.unwrap(), 54321);
122    }
123
124    #[test]
125    fn test_deserialize_from_i64_positive() {
126        let input: i64 = 9876;
127        let deserializer = I64Deserializer::<ValueError>::new(input);
128        let result = deserialize_u64(deserializer);
129        assert!(result.is_ok());
130        assert_eq!(result.unwrap(), 9876);
131    }
132
133    #[test]
134    fn test_deserialize_from_i64_negative() {
135        let input: i64 = -123;
136        let deserializer = I64Deserializer::<ValueError>::new(input);
137        let result = deserialize_u64(deserializer);
138        assert!(result.is_err());
139    }
140
141    #[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
142    struct Wrapper {
143        #[serde(serialize_with = "serialize_u64", deserialize_with = "deserialize_u64")]
144        id: u64,
145    }
146
147    #[test]
148    fn serializes_as_string() {
149        let json = serde_json::to_string(&Wrapper {
150            id: 18446744073709551615,
151        })
152        .unwrap();
153        assert_eq!(json, r#"{"id":"18446744073709551615"}"#);
154    }
155
156    #[test]
157    fn deserializes_from_string_integer_and_corrupted_float() {
158        let from_str: Wrapper = serde_json::from_str(r#"{"id":"1099511627776"}"#).unwrap();
159        assert_eq!(from_str.id, 1099511627776);
160        let from_int: Wrapper = serde_json::from_str(r#"{"id":1099511627776}"#).unwrap();
161        assert_eq!(from_int.id, 1099511627776);
162        let from_float: Wrapper = serde_json::from_str(r#"{"id":1099511627776.0}"#).unwrap();
163        assert_eq!(from_float.id, 1099511627776);
164    }
165
166    #[test]
167    fn rejects_corrupted_float_beyond_safe_range() {
168        assert!(serde_json::from_str::<Wrapper>(r#"{"id":9007199254740993.0}"#).is_err());
169    }
170}