Skip to main content

fiber_json_types/
serde_utils.rs

1//! Serde utilities for hex serialization of types used in JSON RPC.
2//!
3//! These are self-contained copies of the helpers from fiber-types, so that
4//! fiber-json-types can be compiled without depending on fiber-types.
5
6use molecule::prelude::Entity;
7use schemars::{JsonSchema, Schema, SchemaGenerator};
8use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
9use serde_with::{serde_as, serde_conv, DeserializeAs, SerializeAs};
10
11pub fn from_hex<'de, D, E>(deserializer: D) -> Result<E, D::Error>
12where
13    D: Deserializer<'de>,
14    E: TryFrom<Vec<u8>>,
15    E::Error: core::fmt::Debug,
16{
17    String::deserialize(deserializer)
18        .and_then(|string| {
19            let hex_str = string
20                .strip_prefix("0x")
21                .or_else(|| string.strip_prefix("0X"))
22                .unwrap_or(&string);
23            hex::decode(hex_str).map_err(|err| {
24                Error::custom(format!(
25                    "failed to decode hex string {}: {:?}",
26                    &string, err
27                ))
28            })
29        })
30        .and_then(|vec| {
31            vec.try_into().map_err(|err| {
32                Error::custom(format!("failed to convert vector into type: {:?}", err))
33            })
34        })
35}
36
37fn to_hex_with_prefix<E, S>(e: E, serializer: S, with_prefix: bool) -> Result<S::Ok, S::Error>
38where
39    E: AsRef<[u8]>,
40    S: Serializer,
41{
42    let hex_str = hex::encode(e.as_ref());
43    let prefix = if with_prefix { "0x" } else { "" };
44    serializer.serialize_str(&format!("{}{}", prefix, hex_str))
45}
46
47pub struct SliceHex;
48
49impl<T> SerializeAs<T> for SliceHex
50where
51    T: AsRef<[u8]>,
52{
53    fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
54    where
55        S: Serializer,
56    {
57        to_hex_with_prefix(source, serializer, true)
58    }
59}
60
61impl<'de, T> DeserializeAs<'de, T> for SliceHex
62where
63    T: TryFrom<Vec<u8>>,
64    T::Error: core::fmt::Debug,
65{
66    fn deserialize_as<D>(deserializer: D) -> Result<T, D::Error>
67    where
68        D: Deserializer<'de>,
69    {
70        from_hex(deserializer)
71    }
72}
73
74pub struct EntityHex;
75
76impl<T> SerializeAs<T> for EntityHex
77where
78    T: Entity,
79{
80    fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
81    where
82        S: Serializer,
83    {
84        to_hex_with_prefix(source.as_slice(), serializer, true)
85    }
86}
87
88impl<'de, T> DeserializeAs<'de, T> for EntityHex
89where
90    T: Entity,
91{
92    fn deserialize_as<D>(deserializer: D) -> Result<T, D::Error>
93    where
94        D: Deserializer<'de>,
95    {
96        let v: Vec<u8> = from_hex(deserializer)?;
97        T::from_slice(&v).map_err(Error::custom)
98    }
99}
100
101macro_rules! uint_as_hex {
102    ($name:ident, $ty:ty) => {
103        serde_conv!(
104            pub $name,
105            $ty,
106            |u: &$ty| format!("0x{:x}", u),
107            |hex: String| -> Result<$ty, String> {
108                let bytes = hex.as_bytes();
109                if bytes.len() < 3 || &bytes[..2] != b"0x" {
110                    return Err(format!("uint hex string does not start with 0x: {}", hex));
111                }
112                if bytes.len() > 3 && &bytes[2..3] == b"0" {
113                    return Err(format!(
114                        "uint hex string starts with redundant leading zeros: {}",
115                        hex
116                    ));
117                };
118                <$ty>::from_str_radix(&hex[2..], 16)
119                    .map_err(|err| format!("failed to parse uint hex {}: {:?}", hex, err))
120            }
121        );
122    };
123}
124
125uint_as_hex!(U128Hex, u128);
126uint_as_hex!(U64Hex, u64);
127uint_as_hex!(U32Hex, u32);
128
129/// A u32 wrapper that serializes/deserializes as a hex string ("0x...").
130/// Unlike `U32Hex` (a serde_conv helper for use with `#[serde_as]`), this type
131/// implements Serialize/Deserialize directly, making it suitable for use inside
132/// adjacently-tagged enums and other contexts where serde_as doesn't apply.
133#[derive(Copy, Clone, Debug, PartialEq, Eq)]
134pub struct HexU32(pub u32);
135
136impl serde::Serialize for HexU32 {
137    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
138        serializer.serialize_str(&format!("0x{:x}", self.0))
139    }
140}
141
142impl<'de> serde::Deserialize<'de> for HexU32 {
143    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
144        let hex: String = String::deserialize(deserializer)?;
145        let bytes = hex.as_bytes();
146        if bytes.len() < 3 || &bytes[..2] != b"0x" {
147            return Err(D::Error::custom(format!(
148                "uint hex string does not start with 0x: {}",
149                hex
150            )));
151        }
152        if bytes.len() > 3 && &bytes[2..3] == b"0" {
153            return Err(D::Error::custom(format!(
154                "uint hex string starts with redundant leading zeros: {}",
155                hex
156            )));
157        }
158        u32::from_str_radix(&hex[2..], 16)
159            .map(HexU32)
160            .map_err(|err| D::Error::custom(format!("failed to parse uint hex {}: {:?}", hex, err)))
161    }
162}
163
164impl From<u32> for HexU32 {
165    fn from(v: u32) -> Self {
166        HexU32(v)
167    }
168}
169
170impl From<HexU32> for u32 {
171    fn from(v: HexU32) -> Self {
172        v.0
173    }
174}
175
176impl JsonSchema for HexU32 {
177    fn schema_name() -> std::borrow::Cow<'static, str> {
178        "HexU32".into()
179    }
180
181    fn json_schema(generator: &mut SchemaGenerator) -> Schema {
182        let mut schema = String::json_schema(generator);
183        schema.insert("pattern".into(), "^0x(0|[1-9a-fA-F][0-9a-fA-F]*)$".into());
184        schema
185    }
186}
187
188pub struct SliceHexNoPrefix;
189
190impl<T> SerializeAs<T> for SliceHexNoPrefix
191where
192    T: AsRef<[u8]>,
193{
194    fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
195    where
196        S: Serializer,
197    {
198        to_hex_with_prefix(source, serializer, false)
199    }
200}
201
202impl<'de, T> DeserializeAs<'de, T> for SliceHexNoPrefix
203where
204    T: TryFrom<Vec<u8>>,
205    T::Error: core::fmt::Debug,
206{
207    fn deserialize_as<D>(deserializer: D) -> Result<T, D::Error>
208    where
209        D: Deserializer<'de>,
210    {
211        from_hex(deserializer)
212    }
213}
214
215/// A compressed public key (33 bytes), serialized as hex without `0x` prefix.
216///
217/// On deserialization, only hex format and 33-byte length are checked (no secp256k1 validation).
218/// Both `0x`-prefixed and non-prefixed hex strings are accepted on input.
219/// Cryptographic validation is left to the RPC layer's conversion to internal `Pubkey`.
220#[serde_as]
221#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
222pub struct Pubkey(#[serde_as(as = "SliceHexNoPrefix")] pub [u8; 33]);
223
224impl Pubkey {
225    /// Create a `Pubkey` from a 33-byte slice (no cryptographic validation).
226    pub fn from_slice(bytes: &[u8]) -> Result<Self, String> {
227        if bytes.len() != 33 {
228            return Err(format!(
229                "invalid pubkey length: expected 33 bytes, got {}",
230                bytes.len()
231            ));
232        }
233        let mut arr = [0u8; 33];
234        arr.copy_from_slice(bytes);
235        Ok(Pubkey(arr))
236    }
237
238    /// Return the underlying 33 bytes.
239    pub fn as_bytes(&self) -> &[u8; 33] {
240        &self.0
241    }
242}
243
244impl core::fmt::Debug for Pubkey {
245    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
246        write!(f, "Pubkey({})", hex::encode(self.0))
247    }
248}
249
250impl core::fmt::Display for Pubkey {
251    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
252        write!(f, "{}", hex::encode(self.0))
253    }
254}
255
256impl core::str::FromStr for Pubkey {
257    type Err = String;
258
259    fn from_str(s: &str) -> Result<Self, Self::Err> {
260        let hex_str = s
261            .strip_prefix("0x")
262            .or_else(|| s.strip_prefix("0X"))
263            .unwrap_or(s);
264        let bytes =
265            hex::decode(hex_str).map_err(|e| format!("invalid pubkey hex '{}': {}", s, e))?;
266        Pubkey::from_slice(&bytes)
267    }
268}
269
270impl JsonSchema for Pubkey {
271    fn schema_name() -> std::borrow::Cow<'static, str> {
272        "Pubkey".into()
273    }
274
275    fn json_schema(generator: &mut SchemaGenerator) -> Schema {
276        let mut schema = String::json_schema(generator);
277        schema.insert("pattern".into(), "^[0-9a-fA-F]{66}$".into());
278        schema
279    }
280}
281
282/// A private key byte array (32 bytes), serialized as hex without `0x` prefix.
283///
284/// On deserialization, only hex format and 32-byte length are checked.
285/// Both `0x`-prefixed and non-prefixed hex strings are accepted on input.
286/// Cryptographic validation is left to the RPC layer's conversion to internal `Privkey`.
287#[serde_as]
288#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
289pub struct Privkey(#[serde_as(as = "SliceHexNoPrefix")] pub [u8; 32]);
290
291impl Privkey {
292    /// Create a `Privkey` from a 32-byte slice.
293    pub fn from_slice(bytes: &[u8]) -> Result<Self, String> {
294        if bytes.len() != 32 {
295            return Err(format!(
296                "invalid privkey length: expected 32 bytes, got {}",
297                bytes.len()
298            ));
299        }
300        let mut arr = [0u8; 32];
301        arr.copy_from_slice(bytes);
302        Ok(Privkey(arr))
303    }
304
305    /// Return the underlying 32 bytes.
306    pub fn as_bytes(&self) -> &[u8; 32] {
307        &self.0
308    }
309}
310
311impl From<[u8; 32]> for Privkey {
312    fn from(value: [u8; 32]) -> Self {
313        Self(value)
314    }
315}
316
317impl From<Privkey> for [u8; 32] {
318    fn from(value: Privkey) -> Self {
319        value.0
320    }
321}
322
323impl core::fmt::Debug for Privkey {
324    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
325        write!(f, "Privkey(<redacted>)")
326    }
327}
328
329impl core::fmt::Display for Privkey {
330    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
331        write!(f, "{}", hex::encode(self.0))
332    }
333}
334
335impl core::str::FromStr for Privkey {
336    type Err = String;
337
338    fn from_str(s: &str) -> Result<Self, Self::Err> {
339        let hex_str = s
340            .strip_prefix("0x")
341            .or_else(|| s.strip_prefix("0X"))
342            .unwrap_or(s);
343        let bytes =
344            hex::decode(hex_str).map_err(|e| format!("invalid privkey hex '{}': {}", s, e))?;
345        Privkey::from_slice(&bytes)
346    }
347}
348
349impl JsonSchema for Privkey {
350    fn schema_name() -> std::borrow::Cow<'static, str> {
351        "Privkey".into()
352    }
353
354    fn json_schema(generator: &mut SchemaGenerator) -> Schema {
355        let mut schema = String::json_schema(generator);
356        schema.insert("pattern".into(), "^[0-9a-fA-F]{64}$".into());
357        schema
358    }
359}
360
361/// A 256-bit hash (32 bytes), serialized as `0x`-prefixed hex string.
362///
363/// On deserialization, both `0x`-prefixed and non-prefixed hex strings are accepted.
364/// No domain-specific validation is performed — the only check is hex format and 32-byte length.
365#[serde_as]
366#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
367pub struct Hash256(#[serde_as(as = "SliceHex")] pub [u8; 32]);
368
369impl Hash256 {
370    /// Create a `Hash256` from a 32-byte slice.
371    pub fn from_slice(bytes: &[u8]) -> Result<Self, String> {
372        if bytes.len() != 32 {
373            return Err(format!(
374                "invalid hash256 length: expected 32 bytes, got {}",
375                bytes.len()
376            ));
377        }
378        let mut arr = [0u8; 32];
379        arr.copy_from_slice(bytes);
380        Ok(Hash256(arr))
381    }
382
383    /// Return the underlying 32 bytes.
384    pub fn as_bytes(&self) -> &[u8; 32] {
385        &self.0
386    }
387}
388
389impl core::fmt::Debug for Hash256 {
390    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
391        write!(f, "Hash256(0x{})", hex::encode(self.0))
392    }
393}
394
395impl core::fmt::Display for Hash256 {
396    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
397        write!(f, "0x{}", hex::encode(self.0))
398    }
399}
400
401impl core::str::FromStr for Hash256 {
402    type Err = String;
403
404    fn from_str(s: &str) -> Result<Self, Self::Err> {
405        let hex_str = s
406            .strip_prefix("0x")
407            .or_else(|| s.strip_prefix("0X"))
408            .unwrap_or(s);
409        let bytes =
410            hex::decode(hex_str).map_err(|e| format!("invalid hash256 hex '{}': {}", s, e))?;
411        Hash256::from_slice(&bytes)
412    }
413}
414
415impl JsonSchema for Hash256 {
416    fn schema_name() -> std::borrow::Cow<'static, str> {
417        "Hash256".into()
418    }
419
420    fn json_schema(generator: &mut SchemaGenerator) -> Schema {
421        let mut schema = String::json_schema(generator);
422        schema.insert("pattern".into(), "^0x[0-9a-fA-F]{64}$".into());
423        schema
424    }
425}
426
427/// Module for hex serialization of Duration
428pub mod duration_hex {
429    use core::time::Duration;
430    use serde::{Deserialize, Deserializer, Serializer};
431
432    pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
433    where
434        S: Serializer,
435    {
436        let nanos = duration.as_secs();
437        serializer.serialize_str(&format!("0x{:x}", nanos))
438    }
439
440    pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
441    where
442        D: Deserializer<'de>,
443    {
444        let hex_str = String::deserialize(deserializer)?;
445        let seconds = u64::from_str_radix(&hex_str[2..], 16).map_err(|err| {
446            serde::de::Error::custom(format!(
447                "failed to parse duration hex {}: {:?}",
448                hex_str, err
449            ))
450        })?;
451
452        Ok(Duration::from_secs(seconds))
453    }
454}
455
456/// Macro to define flags types that serialize to SCREAMING_SNAKE_CASE strings.
457/// For single flag, returns the flag name in SCREAMING_SNAKE_CASE (e.g., "OUR_INIT_SENT").
458/// For multiple flags, returns pipe-separated names (e.g., "OUR_INIT_SENT | THEIR_INIT_SENT").
459#[macro_export]
460macro_rules! define_rpc_flags {
461    (
462        $(#[$struct_meta:meta])*
463        pub struct $name:ident($ty:ty) {
464            $($(#[$flag_meta:meta])* const $flag_name:ident = $flag_value:expr;)*
465        }
466    ) => {
467        $(#[$struct_meta])*
468        pub struct $name(pub $ty);
469
470        impl $name {
471            $(pub const $flag_name: $ty = $flag_value;)*
472
473            #[allow(clippy::wrong_self_convention)]
474            fn to_strings(self) -> Vec<String> {
475                let mut names = Vec::new();
476                $(
477                    if self.0 & Self::$flag_name != 0 {
478                        names.push(stringify!($flag_name).to_string());
479                    }
480                )*
481                names
482            }
483
484            fn from_string(s: &str) -> Option<Self> {
485                let mut flags: $ty = 0;
486                for name in s.split('|') {
487                    let name = name.trim();
488                    match name {
489                        $(stringify!($flag_name) => flags |= Self::$flag_name,)*
490                        _ => return None,
491                    }
492                }
493                Some($name(flags))
494            }
495        }
496
497        impl serde::Serialize for $name {
498            fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
499                let names = self.clone().to_strings();
500                if names.is_empty() {
501                    serializer.serialize_str("")
502                } else {
503                    serializer.serialize_str(&names.join("|"))
504                }
505            }
506        }
507
508        impl<'de> serde::Deserialize<'de> for $name {
509            fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
510                let s = String::deserialize(deserializer)?;
511                if s.is_empty() {
512                    Ok($name(0))
513                } else {
514                    $name::from_string(&s)
515                        .ok_or_else(|| serde::de::Error::custom(format!("Invalid {}: {}", stringify!($name), s)))
516                }
517            }
518        }
519
520        impl From<$ty> for $name {
521            fn from(v: $ty) -> Self {
522                $name(v)
523            }
524        }
525
526        impl From<$name> for $ty {
527            fn from(v: $name) -> Self {
528                v.0
529            }
530        }
531
532        impl schemars::JsonSchema for $name {
533            fn schema_name() -> std::borrow::Cow<'static, str> {
534                stringify!($name).into()
535            }
536
537            fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
538                let flag_names: Vec<String> = vec![
539                    $(stringify!($flag_name).to_string(),)*
540                ];
541                let single = flag_names.join("|");
542                let pattern = format!("^(({single})(\\s*\\|\\s*({single}))*)?$");
543                schemars::json_schema!({
544                    "type": "string",
545                    "pattern": pattern,
546                })
547            }
548        }
549    };
550}