smart_config/de/
primitive_types_impl.rs

1use serde::{Serialize, de::DeserializeOwned};
2
3use crate::{
4    de::{DeserializeContext, DeserializeParam, Qualified, Serde, WellKnown, WellKnownOption},
5    error::ErrorWithOrigin,
6    metadata::{BasicTypes, ParamMetadata, TypeDescription},
7    value::Value,
8};
9
10const HASH_DE: Qualified<Serde![str]> =
11    Qualified::new(Serde![str], "hex string with optional 0x prefix");
12
13macro_rules! impl_well_known_hash {
14    ($($ty:ident),+) => {
15        $(
16        /// Accepts a hex string with an optional `0x` prefix.
17        #[cfg_attr(docsrs, doc(cfg(feature = "primitive-types")))]
18        impl WellKnown for primitive_types::$ty {
19            type Deserializer = Qualified<Serde![str]>;
20            const DE: Self::Deserializer = HASH_DE;
21        }
22
23        #[cfg_attr(docsrs, doc(cfg(feature = "primitive-types")))]
24        impl WellKnownOption for primitive_types::$ty {}
25        )+
26    };
27}
28
29impl_well_known_hash!(H128, H160, H256, H384, H512, H768);
30
31/// Hex deserializer enforcing a `0x` prefix. This prefix is not required by `U*` deserializers,
32/// but the value may be ambiguous otherwise (e.g., `34` being equal to 0x34, not decimal 34).
33#[derive(Debug)]
34pub struct HexUintDeserializer;
35
36// This implementation is overly general, but since the struct is private, it's OK.
37impl<T: Serialize + DeserializeOwned> DeserializeParam<T> for HexUintDeserializer {
38    const EXPECTING: BasicTypes = BasicTypes::STRING;
39
40    fn describe(&self, description: &mut TypeDescription) {
41        description.set_details("0x-prefixed hex number");
42    }
43
44    fn deserialize_param(
45        &self,
46        ctx: DeserializeContext<'_>,
47        param: &'static ParamMetadata,
48    ) -> Result<T, ErrorWithOrigin> {
49        let deserializer = ctx.current_value_deserializer(param.name)?;
50        if let Value::String(s) = deserializer.value() {
51            if !s.expose().starts_with("0x") {
52                return Err(deserializer.invalid_type("0x-prefixed hex number"));
53            }
54        }
55        T::deserialize(deserializer)
56    }
57
58    fn serialize_param(&self, param: &T) -> serde_json::Value {
59        serde_json::to_value(param).expect("failed serializing value")
60    }
61}
62
63macro_rules! impl_well_known_uint {
64    ($($ty:ident),+) => {
65        $(
66        /// Accepts a hex string with an **mandatory** `0x` prefix. This prefix is required to clearly signal hex encoding
67        /// so that `"34"` doesn't get mistaken for decimal 34.
68        #[cfg_attr(docsrs, doc(cfg(feature = "primitive-types")))]
69        impl WellKnown for primitive_types::$ty {
70            type Deserializer = HexUintDeserializer;
71            const DE: Self::Deserializer = HexUintDeserializer;
72        }
73
74        #[cfg_attr(docsrs, doc(cfg(feature = "primitive-types")))]
75        impl WellKnownOption for primitive_types::$ty {}
76        )+
77    };
78}
79
80impl_well_known_uint!(U128, U256, U512);
81
82#[cfg(test)]
83mod tests {
84    use std::collections::HashMap;
85
86    use primitive_types::{H160 as Address, H256, U128, U256};
87    use smart_config_derive::{DescribeConfig, DeserializeConfig};
88
89    use crate::{
90        config,
91        testing::{test, test_complete},
92    };
93
94    #[derive(Debug, PartialEq, DescribeConfig, DeserializeConfig)]
95    #[config(crate = crate)]
96    struct TestConfig {
97        int: U128,
98        #[config(default)]
99        hash: Option<H256>,
100        #[config(default)]
101        addresses: Vec<Address>,
102        #[config(default)]
103        balances: HashMap<Address, U256>,
104    }
105
106    #[test]
107    fn deserializing_values() {
108        let json = config!(
109            "int": "0x123",
110            "hash": "fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe",
111            "addresses": [
112                "0x0000000000000000000000000000000000001234",
113                "1212121212121212121212121212121212121212",
114            ],
115            "balances": HashMap::from([
116                ("0x0000000000000000000000000000000000004321", "0x3"),
117            ])
118        );
119        let config = test_complete::<TestConfig>(json).unwrap();
120        assert_eq!(config.int, U128::from(0x123));
121        assert_eq!(config.hash, Some(H256::repeat_byte(0xfe)));
122        assert_eq!(
123            config.addresses,
124            [Address::from_low_u64_be(0x1234), Address::repeat_byte(0x12)]
125        );
126        assert_eq!(
127            config.balances,
128            HashMap::from([(Address::from_low_u64_be(0x4321), U256::from(3))])
129        );
130    }
131
132    #[test]
133    fn uint_prefix_error() {
134        let json = config!("int": "123");
135        let err = test::<TestConfig>(json).unwrap_err();
136        assert_eq!(err.len(), 1);
137        let err = err.first();
138        assert_eq!(err.path(), "int");
139        let inner = err.inner().to_string();
140        assert!(
141            inner.contains("invalid type") && inner.contains("0x-prefixed hex number"),
142            "{inner}"
143        );
144    }
145}