serde_utils/
u64_hex_be.rs

1//! Formats `u64` as a 0x-prefixed, big-endian hex string.
2//!
3//! E.g., `0` serializes as `"0x0000000000000000"`.
4
5use serde::de::{self, Error, Visitor};
6use serde::{Deserializer, Serializer};
7use std::fmt;
8
9const BYTES_LEN: usize = 8;
10
11pub struct QuantityVisitor;
12impl<'de> Visitor<'de> for QuantityVisitor {
13    type Value = Vec<u8>;
14
15    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
16        formatter.write_str("a hex string")
17    }
18
19    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
20    where
21        E: de::Error,
22    {
23        if !value.starts_with("0x") {
24            return Err(de::Error::custom("must start with 0x"));
25        }
26
27        let stripped = value.trim_start_matches("0x");
28
29        if stripped.is_empty() {
30            Err(de::Error::custom(format!(
31                "quantity cannot be {}",
32                stripped
33            )))
34        } else if stripped == "0" {
35            Ok(vec![0])
36        } else if stripped.starts_with('0') {
37            Err(de::Error::custom("cannot have leading zero"))
38        } else if stripped.len() % 2 != 0 {
39            hex::decode(format!("0{}", stripped))
40                .map_err(|e| de::Error::custom(format!("invalid hex ({:?})", e)))
41        } else {
42            hex::decode(stripped).map_err(|e| de::Error::custom(format!("invalid hex ({:?})", e)))
43        }
44    }
45}
46
47pub fn serialize<S>(num: &u64, serializer: S) -> Result<S::Ok, S::Error>
48where
49    S: Serializer,
50{
51    let raw = hex::encode(num.to_be_bytes());
52    let trimmed = raw.trim_start_matches('0');
53
54    let hex = if trimmed.is_empty() { "0" } else { trimmed };
55
56    serializer.serialize_str(&format!("0x{}", &hex))
57}
58
59pub fn deserialize<'de, D>(deserializer: D) -> Result<u64, D::Error>
60where
61    D: Deserializer<'de>,
62{
63    let decoded = deserializer.deserialize_str(QuantityVisitor)?;
64
65    // TODO: this is not strict about byte length like other methods.
66    if decoded.len() > BYTES_LEN {
67        return Err(D::Error::custom(format!(
68            "expected max {} bytes for array, got {}",
69            BYTES_LEN,
70            decoded.len()
71        )));
72    }
73
74    let mut array = [0; BYTES_LEN];
75    array[BYTES_LEN - decoded.len()..].copy_from_slice(&decoded);
76    Ok(u64::from_be_bytes(array))
77}
78
79#[cfg(test)]
80mod test {
81    use serde::{Deserialize, Serialize};
82    use serde_json;
83
84    #[derive(Debug, PartialEq, Serialize, Deserialize)]
85    #[serde(transparent)]
86    struct Wrapper {
87        #[serde(with = "super")]
88        val: u64,
89    }
90
91    #[test]
92    fn encoding() {
93        assert_eq!(
94            &serde_json::to_string(&Wrapper { val: 0 }).unwrap(),
95            "\"0x0\""
96        );
97        assert_eq!(
98            &serde_json::to_string(&Wrapper { val: 1 }).unwrap(),
99            "\"0x1\""
100        );
101        assert_eq!(
102            &serde_json::to_string(&Wrapper { val: 256 }).unwrap(),
103            "\"0x100\""
104        );
105        assert_eq!(
106            &serde_json::to_string(&Wrapper { val: 65 }).unwrap(),
107            "\"0x41\""
108        );
109        assert_eq!(
110            &serde_json::to_string(&Wrapper { val: 1024 }).unwrap(),
111            "\"0x400\""
112        );
113    }
114
115    #[test]
116    fn decoding() {
117        assert_eq!(
118            serde_json::from_str::<Wrapper>("\"0x0\"").unwrap(),
119            Wrapper { val: 0 },
120        );
121        assert_eq!(
122            serde_json::from_str::<Wrapper>("\"0x41\"").unwrap(),
123            Wrapper { val: 65 },
124        );
125        assert_eq!(
126            serde_json::from_str::<Wrapper>("\"0x400\"").unwrap(),
127            Wrapper { val: 1024 },
128        );
129        serde_json::from_str::<Wrapper>("\"0x\"").unwrap_err();
130        serde_json::from_str::<Wrapper>("\"0x0400\"").unwrap_err();
131        serde_json::from_str::<Wrapper>("\"400\"").unwrap_err();
132        serde_json::from_str::<Wrapper>("\"ff\"").unwrap_err();
133    }
134}