hypercall_api/directives/
json_types.rs1use 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}