dig_network_block/
serde_hex.rs1use serde::{Deserialize, Deserializer, Serializer};
10use thiserror::Error;
11
12#[derive(Debug, Error)]
14pub enum HexSerdeError {
15 #[error("missing 0x prefix")]
17 MissingPrefix,
18
19 #[error("invalid hex encoding: {0}")]
21 InvalidHex(String),
22
23 #[error("length mismatch: expected {expected} bytes, got {actual} bytes")]
25 LengthMismatch { expected: usize, actual: usize },
26}
27
28fn strip_0x(s: &str) -> Result<&str, HexSerdeError> {
29 if let Some(rest) = s.strip_prefix("0x") {
30 Ok(rest)
31 } else {
32 Err(HexSerdeError::MissingPrefix)
33 }
34}
35
36fn encode_lower_hex_prefixed(bytes: &[u8]) -> String {
37 let mut out = String::with_capacity(2 + bytes.len() * 2);
38 out.push_str("0x");
39 out.push_str(&hex::encode(bytes));
40 out
41}
42
43pub mod hex_vec {
45 use super::*;
46
47 pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
49 where
50 S: Serializer,
51 {
52 let s = encode_lower_hex_prefixed(bytes);
53 serializer.serialize_str(&s)
54 }
55
56 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
58 where
59 D: Deserializer<'de>,
60 {
61 let s: String = String::deserialize(deserializer)?;
62 let hex_part = strip_0x(&s).map_err(|e| serde::de::Error::custom(e.to_string()))?;
63 let bytes = hex::decode(hex_part).map_err(|e| {
64 serde::de::Error::custom(HexSerdeError::InvalidHex(e.to_string()).to_string())
65 })?;
66 Ok(bytes)
67 }
68}
69
70pub mod hex32 {
72 use super::*;
73
74 pub fn serialize<S>(bytes: &[u8; 32], serializer: S) -> Result<S::Ok, S::Error>
76 where
77 S: Serializer,
78 {
79 let s = encode_lower_hex_prefixed(bytes);
80 serializer.serialize_str(&s)
81 }
82
83 pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error>
85 where
86 D: Deserializer<'de>,
87 {
88 let s: String = String::deserialize(deserializer)?;
89 let hex_part = strip_0x(&s).map_err(|e| serde::de::Error::custom(e.to_string()))?;
90 let bytes = hex::decode(hex_part).map_err(|e| {
91 serde::de::Error::custom(HexSerdeError::InvalidHex(e.to_string()).to_string())
92 })?;
93 if bytes.len() != 32 {
94 return Err(serde::de::Error::custom(
95 HexSerdeError::LengthMismatch {
96 expected: 32,
97 actual: bytes.len(),
98 }
99 .to_string(),
100 ));
101 }
102 let mut arr = [0u8; 32];
103 arr.copy_from_slice(&bytes);
104 Ok(arr)
105 }
106}
107
108pub mod hex48 {
110 use super::*;
111
112 pub fn serialize<S>(bytes: &[u8; 48], serializer: S) -> Result<S::Ok, S::Error>
114 where
115 S: Serializer,
116 {
117 let s = encode_lower_hex_prefixed(bytes);
118 serializer.serialize_str(&s)
119 }
120
121 pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; 48], D::Error>
123 where
124 D: Deserializer<'de>,
125 {
126 let s: String = String::deserialize(deserializer)?;
127 let hex_part = strip_0x(&s).map_err(|e| serde::de::Error::custom(e.to_string()))?;
128 let bytes = hex::decode(hex_part).map_err(|e| {
129 serde::de::Error::custom(HexSerdeError::InvalidHex(e.to_string()).to_string())
130 })?;
131 if bytes.len() != 48 {
132 return Err(serde::de::Error::custom(
133 HexSerdeError::LengthMismatch {
134 expected: 48,
135 actual: bytes.len(),
136 }
137 .to_string(),
138 ));
139 }
140 let mut arr = [0u8; 48];
141 arr.copy_from_slice(&bytes);
142 Ok(arr)
143 }
144}
145
146#[cfg(test)]
147mod tests {
148
149 use serde::{Deserialize, Serialize};
150
151 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
152 struct VecWrap(#[serde(with = "crate::serde_hex::hex_vec")] Vec<u8>);
153
154 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
155 struct Arr32Wrap(#[serde(with = "crate::serde_hex::hex32")] [u8; 32]);
156
157 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
158 struct Arr48Wrap(#[serde(with = "crate::serde_hex::hex48")] [u8; 48]);
159
160 #[test]
161 fn vec_round_trip() {
162 let v = VecWrap(vec![0x00, 0x01, 0xaa, 0xff]);
163 let s = serde_json::to_string(&v).unwrap();
164 assert_eq!(s, "\"0x0001aaff\"");
166 let back: VecWrap = serde_json::from_str(&s).unwrap();
167 assert_eq!(back, v);
168 }
169
170 #[test]
171 fn arr32_round_trip() {
172 let mut a = [0u8; 32];
173 a[0] = 0xde;
174 a[31] = 0xad;
175 let w = Arr32Wrap(a);
176 let s = serde_json::to_string(&w).unwrap();
177 let v: serde_json::Value = serde_json::from_str(&s).unwrap();
178 let s_hex = v.as_str().unwrap();
179 assert!(s_hex.starts_with("0x"));
180 assert_eq!(s_hex.len(), 2 + 64);
181 let back: Arr32Wrap = serde_json::from_str(&s).unwrap();
182 assert_eq!(back, w);
183 }
184
185 #[test]
186 fn arr48_round_trip() {
187 let mut a = [0u8; 48];
188 a[0] = 0x12;
189 a[47] = 0x34;
190 let w = Arr48Wrap(a);
191 let s = serde_json::to_string(&w).unwrap();
192 let v: serde_json::Value = serde_json::from_str(&s).unwrap();
193 let s_hex = v.as_str().unwrap();
194 assert!(s_hex.starts_with("0x"));
195 assert_eq!(s_hex.len(), 2 + 96);
196 let back: Arr48Wrap = serde_json::from_str(&s).unwrap();
197 assert_eq!(back, w);
198 }
199
200 #[test]
201 fn vec_rejects_missing_prefix() {
202 let s = "\"deadbeef\""; let err = serde_json::from_str::<VecWrap>(s).unwrap_err();
204 let msg = err.to_string();
205 assert!(msg.contains("missing 0x prefix"));
206 }
207
208 #[test]
209 fn arr32_wrong_length_rejected() {
210 let s = format!("\"0x{}\"", "00".repeat(31));
212 let err = serde_json::from_str::<Arr32Wrap>(&s).unwrap_err();
213 let msg = err.to_string();
214 assert!(msg.contains("length mismatch"));
215 }
216
217 #[test]
218 fn arr48_wrong_length_rejected() {
219 let s = format!("\"0x{}\"", "ff".repeat(49));
221 let err = serde_json::from_str::<Arr48Wrap>(&s).unwrap_err();
222 let msg = err.to_string();
223 assert!(msg.contains("length mismatch"));
224 }
225
226 #[test]
227 fn invalid_hex_char_rejected() {
228 let s = "\"0xzz\""; let err = serde_json::from_str::<VecWrap>(s).unwrap_err();
230 let msg = err.to_string();
231 assert!(msg.contains("invalid hex encoding"));
232 }
233}