smart_config/de/
alloy_impl.rs1use 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 #[cfg_attr(docsrs, doc(cfg(feature = "alloy")))]
18 impl WellKnown for alloy::primitives::aliases::$ty {
19 type Deserializer = Qualified<Serde![str]>;
20 const DE: Self::Deserializer = HASH_DE;
21 }
22
23 #[cfg_attr(docsrs, doc(cfg(feature = "alloy")))]
24 impl WellKnownOption for alloy::primitives::aliases::$ty {}
25 )+
26 };
27}
28
29impl_well_known_hash!(B64, B128, B160, B256, B512);
30
31#[cfg_attr(docsrs, doc(cfg(feature = "alloy")))]
33impl WellKnown for alloy::primitives::Address {
34 type Deserializer = Qualified<Serde![str]>;
35 const DE: Self::Deserializer = HASH_DE;
36}
37
38#[cfg_attr(docsrs, doc(cfg(feature = "alloy")))]
39impl WellKnownOption for alloy::primitives::Address {}
40
41#[derive(Debug)]
44pub struct HexUintDeserializer;
45
46impl<T: Serialize + DeserializeOwned> DeserializeParam<T> for HexUintDeserializer {
48 const EXPECTING: BasicTypes = BasicTypes::STRING;
49
50 fn describe(&self, description: &mut TypeDescription) {
51 description.set_details("0x-prefixed hex number");
52 }
53
54 fn deserialize_param(
55 &self,
56 ctx: DeserializeContext<'_>,
57 param: &'static ParamMetadata,
58 ) -> Result<T, ErrorWithOrigin> {
59 let deserializer = ctx.current_value_deserializer(param.name)?;
60 if let Value::String(s) = deserializer.value() {
61 if !s.expose().starts_with("0x") {
62 return Err(deserializer.invalid_type("0x-prefixed hex number"));
63 }
64 }
65 T::deserialize(deserializer)
66 }
67
68 fn serialize_param(&self, param: &T) -> serde_json::Value {
69 serde_json::to_value(param).expect("failed serializing value")
70 }
71}
72
73macro_rules! impl_well_known_uint {
74 ($($ty:ident),+) => {
75 $(
76 #[cfg_attr(docsrs, doc(cfg(feature = "primitive-types")))]
79 impl WellKnown for alloy::primitives::$ty {
80 type Deserializer = HexUintDeserializer;
81 const DE: Self::Deserializer = HexUintDeserializer;
82 }
83
84 #[cfg_attr(docsrs, doc(cfg(feature = "primitive-types")))]
85 impl WellKnownOption for alloy::primitives::$ty {}
86 )+
87 };
88}
89
90impl_well_known_uint!(U8, U16, U32, U64, U128, U160, U256, U512);
91
92#[cfg(test)]
93mod tests {
94 use std::collections::HashMap;
95
96 use alloy::primitives::{Address, B256, U128, U160, U256};
97 use smart_config_derive::{DescribeConfig, DeserializeConfig};
98
99 use crate::{
100 config,
101 testing::{test, test_complete},
102 };
103
104 #[derive(Debug, PartialEq, DescribeConfig, DeserializeConfig)]
105 #[config(crate = crate)]
106 struct TestConfig {
107 int: U128,
108 #[config(default)]
109 hash: Option<B256>,
110 #[config(default)]
111 addresses: Vec<Address>,
112 #[config(default)]
113 balances: HashMap<Address, U256>,
114 }
115
116 #[test]
117 fn deserializing_values() {
118 let json = config!(
119 "int": "0x123",
120 "hash": "fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe",
121 "addresses": [
122 "0x0000000000000000000000000000000000001234",
123 "1212121212121212121212121212121212121212",
124 ],
125 "balances": HashMap::from([
126 ("0x0000000000000000000000000000000000004321", "0x3"),
127 ])
128 );
129 let config = test_complete::<TestConfig>(json).unwrap();
130 assert_eq!(config.int, U128::from(0x123));
131 assert_eq!(config.hash, Some(B256::repeat_byte(0xfe)));
132 assert_eq!(
133 config.addresses,
134 [
135 Address::from(U160::from(0x1234)),
136 Address::repeat_byte(0x12)
137 ]
138 );
139 assert_eq!(
140 config.balances,
141 HashMap::from([(Address::from(U160::from(0x4321)), U256::from(3))])
142 );
143 }
144
145 #[test]
146 fn uint_prefix_error() {
147 let json = config!("int": "123");
148 let err = test::<TestConfig>(json).unwrap_err();
149 assert_eq!(err.len(), 1);
150 let err = err.first();
151 assert_eq!(err.path(), "int");
152 let inner = err.inner().to_string();
153 assert!(
154 inner.contains("invalid type") && inner.contains("0x-prefixed hex number"),
155 "{inner}"
156 );
157 }
158}