Skip to main content

hypercall_api/directives/
json_types.rs

1use crate::error::ApiError;
2use alloy::primitives::U256;
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4
5pub const JSON_SAFE_INTEGER_MAX: u64 = 9_007_199_254_740_991;
6
7fn parse_uint_from_json_value(
8    value: serde_json::Value,
9    type_name: &'static str,
10    max: u128,
11) -> Result<u128, String> {
12    let parsed = if value.is_number() {
13        let Some(number_u64) = value.as_u64() else {
14            return Err(format!(
15                "{} must be a non-negative integer JSON number or decimal string",
16                type_name
17            ));
18        };
19        if number_u64 > JSON_SAFE_INTEGER_MAX {
20            return Err(format!(
21                "{} JSON number exceeds safe integer range; provide as decimal string",
22                type_name
23            ));
24        }
25        number_u64 as u128
26    } else if value.is_string() {
27        let raw = value.as_str().unwrap();
28        if raw.is_empty() || !raw.as_bytes().iter().all(|b| b.is_ascii_digit()) {
29            return Err(format!("{} string must match ^[0-9]+$", type_name));
30        }
31        raw.parse::<u128>()
32            .map_err(|_| format!("{} string is out of range", type_name))?
33    } else {
34        return Err(format!(
35            "{} must be a non-negative integer JSON number or decimal string",
36            type_name
37        ));
38    };
39
40    if parsed > max {
41        return Err(format!("{} is out of range", type_name));
42    }
43
44    Ok(parsed)
45}
46
47fn serialize_safe_integer<S>(value: u128, serializer: S) -> Result<S::Ok, S::Error>
48where
49    S: Serializer,
50{
51    if value <= JSON_SAFE_INTEGER_MAX as u128 {
52        serializer.serialize_u64(value as u64)
53    } else {
54        serializer.serialize_str(&value.to_string())
55    }
56}
57
58fn parse_u256_from_json_value(
59    value: serde_json::Value,
60    type_name: &'static str,
61) -> Result<U256, String> {
62    if value.is_number() {
63        let Some(number_u64) = value.as_u64() else {
64            return Err(format!(
65                "{} must be a non-negative integer JSON number or decimal string",
66                type_name
67            ));
68        };
69        if number_u64 > JSON_SAFE_INTEGER_MAX {
70            return Err(format!(
71                "{} JSON number exceeds safe integer range; provide as decimal string",
72                type_name
73            ));
74        }
75        Ok(U256::from(number_u64))
76    } else if value.is_string() {
77        let raw = value.as_str().unwrap();
78        if raw.is_empty() || !raw.as_bytes().iter().all(|b| b.is_ascii_digit()) {
79            return Err(format!("{} string must match ^[0-9]+$", type_name));
80        }
81        raw.parse::<U256>()
82            .map_err(|_| format!("{} string is out of range", type_name))
83    } else {
84        Err(format!(
85            "{} must be a non-negative integer JSON number or decimal string",
86            type_name
87        ))
88    }
89}
90
91fn serialize_safe_u256<S>(value: U256, serializer: S) -> Result<S::Ok, S::Error>
92where
93    S: Serializer,
94{
95    if value <= U256::from(JSON_SAFE_INTEGER_MAX) {
96        serializer.serialize_u64(value.to::<u64>())
97    } else {
98        serializer.serialize_str(&value.to_string())
99    }
100}
101
102macro_rules! json_uint_type {
103    ($name:ident, $inner:ty) => {
104        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
105        pub struct $name(pub $inner);
106
107        impl $name {
108            pub const fn into_inner(self) -> $inner {
109                self.0
110            }
111        }
112
113        impl Serialize for $name {
114            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
115            where
116                S: Serializer,
117            {
118                serialize_safe_integer(self.0 as u128, serializer)
119            }
120        }
121
122        impl<'de> Deserialize<'de> for $name {
123            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
124            where
125                D: Deserializer<'de>,
126            {
127                let value = serde_json::Value::deserialize(deserializer)?;
128                parse_uint_from_json_value(value, stringify!($name), <$inner>::MAX as u128)
129                    .map(|v| Self(v as $inner))
130                    .map_err(serde::de::Error::custom)
131            }
132        }
133    };
134}
135
136json_uint_type!(JsonU8, u8);
137json_uint_type!(JsonU32, u32);
138json_uint_type!(JsonU64, u64);
139json_uint_type!(JsonU128, u128);
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
142pub struct JsonU256(pub U256);
143
144impl JsonU256 {
145    pub fn into_inner(self) -> U256 {
146        self.0
147    }
148}
149
150impl Serialize for JsonU256 {
151    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
152    where
153        S: Serializer,
154    {
155        serialize_safe_u256(self.0, serializer)
156    }
157}
158
159impl<'de> Deserialize<'de> for JsonU256 {
160    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
161    where
162        D: Deserializer<'de>,
163    {
164        let value = serde_json::Value::deserialize(deserializer)?;
165        parse_u256_from_json_value(value, "JsonU256")
166            .map(Self)
167            .map_err(serde::de::Error::custom)
168    }
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
172pub struct Bytes32Hex(pub [u8; 32]);
173
174impl Bytes32Hex {
175    pub const fn into_inner(self) -> [u8; 32] {
176        self.0
177    }
178}
179
180impl Serialize for Bytes32Hex {
181    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
182    where
183        S: Serializer,
184    {
185        serializer.serialize_str(&format!("0x{}", hex::encode(self.0)))
186    }
187}
188
189impl<'de> Deserialize<'de> for Bytes32Hex {
190    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
191    where
192        D: Deserializer<'de>,
193    {
194        let raw = String::deserialize(deserializer)?;
195        let Some(hex_part) = raw.strip_prefix("0x") else {
196            return Err(serde::de::Error::custom(
197                "bytes32 must be a 0x-prefixed hex string",
198            ));
199        };
200        if hex_part.len() != 64 {
201            return Err(serde::de::Error::custom(
202                "bytes32 must be exactly 32 bytes (64 hex chars)",
203            ));
204        }
205
206        let decoded = hex::decode(hex_part)
207            .map_err(|_| serde::de::Error::custom("bytes32 contains invalid hex"))?;
208        let bytes: [u8; 32] = decoded
209            .try_into()
210            .map_err(|_| serde::de::Error::custom("bytes32 must decode to 32 bytes"))?;
211        Ok(Self(bytes))
212    }
213}
214
215#[derive(Debug, Clone)]
216pub struct SignatureHex {
217    original: String,
218    bytes: [u8; 65],
219}
220
221impl SignatureHex {
222    pub fn parse(raw: &str) -> Result<Self, ApiError> {
223        let Some(hex_part) = raw.strip_prefix("0x") else {
224            return Err(ApiError::invalid_signature(
225                "signature must be a 0x-prefixed hex string",
226            ));
227        };
228
229        let decoded = hex::decode(hex_part).map_err(|_| {
230            ApiError::invalid_signature("signature must be valid hex and 65 bytes long")
231        })?;
232
233        let bytes: [u8; 65] = decoded.try_into().map_err(|_| {
234            ApiError::invalid_signature("signature must decode to exactly 65 bytes")
235        })?;
236
237        match bytes[64] {
238            0 | 1 | 27 | 28 => {}
239            _ => {
240                return Err(ApiError::invalid_signature(
241                    "signature v must be one of 0, 1, 27, or 28",
242                ))
243            }
244        }
245
246        Ok(Self {
247            original: raw.to_string(),
248            bytes,
249        })
250    }
251
252    pub fn bytes(&self) -> &[u8; 65] {
253        &self.bytes
254    }
255
256    pub fn original(&self) -> &str {
257        &self.original
258    }
259}