Skip to main content

fedimint_core/encoding/
as_hex.rs

1//! Serde implementations using hex-encoded encodables
2//!
3//! Oftentimes it's convenient to de/serialize consensus encodable data
4//! as using consensus encoding (wrapped in hex encoding).
5//!
6//! If you have just a field use just:
7//!
8//! ```norust
9//! #[serde(with = "::fedimint_core::encoding::as_hex")] EncodableType,
10//! ```
11//!
12//! If you want to do it for the whole `struct`, use
13//! [`crate::serde_as_encodable_hex`] macro.
14
15use serde::Deserialize;
16
17use super::{Decodable, Encodable};
18use crate::module::registry::ModuleRegistry;
19
20pub fn serialize<T, S>(t: &T, ser: S) -> Result<S::Ok, S::Error>
21where
22    T: Encodable,
23    S: serde::Serializer,
24{
25    ser.serialize_str(&t.consensus_encode_to_hex())
26}
27
28pub fn deserialize<'de, T: Decodable, D>(de: D) -> Result<T, D::Error>
29where
30    D: serde::de::Deserializer<'de>,
31{
32    Decodable::consensus_decode_hex(&String::deserialize(de)?, &ModuleRegistry::default()).map_err(
33        |e| {
34            serde::de::Error::custom(format!(
35                "decodable deserialization failed: {}",
36                crate::util::FmtCompact::fmt_compact(&e)
37            ))
38        },
39    )
40}
41
42#[macro_export]
43macro_rules! serialize_as_encodable_hex {
44    ($name:ident) => {
45        impl Serialize for $name {
46            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
47            where
48                S: serde::Serializer,
49            {
50                use $crate::Encodable;
51                serializer.serialize_str(&self.consensus_encode_to_hex().map_err(|e| {
52                    serde::ser::Error::custom(format!("encodable serialization failed: {e:#}"))
53                })?)
54            }
55        }
56    };
57}
58
59#[macro_export]
60macro_rules! deserialize_as_encodable_hex {
61    ($name:ident) => {
62        impl<'de> Deserialize<'de> for $name {
63            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
64            where
65                D: serde::Deserializer<'de>,
66            {
67                $crate::Decodable::consensus_decode_hex(
68                    &String::deserialize(deserializer)?,
69                    &Default::default(),
70                )
71                .map_err(|e| {
72                    serde::de::Error::custom(format!(
73                        "decodable deserialization failed: {}",
74                        $crate::util::FmtCompact::fmt_compact(&e)
75                    ))
76                })
77            }
78        }
79    };
80}
81
82#[macro_export]
83macro_rules! serde_as_encodable_hex {
84    ($name:ident) => {
85        $crate::serialize_as_encodable_hex!($name);
86        $crate::deserialize_as_encodable_hex!($name);
87    };
88}
89
90#[cfg(test)]
91mod tests {
92    use fedimint_derive::{Decodable, Encodable};
93
94    use crate::encoding::Decodable as _;
95    use crate::module::registry::ModuleRegistry;
96    use crate::util::FmtCompact as _;
97
98    #[derive(Debug, Encodable, Decodable, Eq, PartialEq)]
99    struct TestStruct {
100        vec: Vec<u8>,
101        num: u32,
102    }
103
104    #[derive(Debug, serde::Deserialize)]
105    struct Wrapper {
106        #[serde(with = "crate::encoding::as_hex", rename = "inner")]
107        _inner: TestStruct,
108    }
109
110    #[test_log::test]
111    fn deserialize_reports_the_whole_decode_chain() {
112        // `vec` decodes as one element (7); `num` then has no bytes left, so this
113        // fails deep inside the derived decoder, not at the hex-parsing layer.
114        let err = serde_json::from_str::<Wrapper>(r#"{"inner":"0107"}"#)
115            .expect_err("payload is truncated");
116
117        let expected = TestStruct::consensus_decode_hex("0107", &ModuleRegistry::default())
118            .expect_err("payload is truncated");
119
120        assert!(
121            err.to_string().starts_with(&format!(
122                "decodable deserialization failed: {}",
123                expected.fmt_compact()
124            )),
125            "{err}"
126        );
127        assert_ne!(
128            expected.fmt_compact().to_string(),
129            expected.to_string(),
130            "the decode error has more than one layer and the message shows all of them"
131        );
132    }
133}