use serde::{de::DeserializeOwned, Serialize};
use crate::{
de::{DeserializeContext, DeserializeParam, Qualified, Serde, WellKnown, WellKnownOption},
error::ErrorWithOrigin,
metadata::{BasicTypes, ParamMetadata, TypeDescription},
value::Value,
};
const HASH_DE: Qualified<Serde![str]> =
Qualified::new(Serde![str], "hex string with optional 0x prefix");
macro_rules! impl_well_known_hash {
($($ty:ident),+) => {
$(
#[cfg_attr(docsrs, doc(cfg(feature = "primitive-types")))]
impl WellKnown for primitive_types::$ty {
type Deserializer = Qualified<Serde![str]>;
const DE: Self::Deserializer = HASH_DE;
}
#[cfg_attr(docsrs, doc(cfg(feature = "primitive-types")))]
impl WellKnownOption for primitive_types::$ty {}
)+
};
}
impl_well_known_hash!(H128, H160, H256, H384, H512, H768);
#[derive(Debug)]
pub struct HexUintDeserializer;
impl<T: Serialize + DeserializeOwned> DeserializeParam<T> for HexUintDeserializer {
const EXPECTING: BasicTypes = BasicTypes::STRING;
fn describe(&self, description: &mut TypeDescription) {
description.set_details("0x-prefixed hex number");
}
fn deserialize_param(
&self,
ctx: DeserializeContext<'_>,
param: &'static ParamMetadata,
) -> Result<T, ErrorWithOrigin> {
let deserializer = ctx.current_value_deserializer(param.name)?;
if let Value::String(s) = deserializer.value() {
if !s.expose().starts_with("0x") {
return Err(deserializer.invalid_type("0x-prefixed hex number"));
}
}
T::deserialize(deserializer)
}
fn serialize_param(&self, param: &T) -> serde_json::Value {
serde_json::to_value(param).expect("failed serializing value")
}
}
macro_rules! impl_well_known_uint {
($($ty:ident),+) => {
$(
#[cfg_attr(docsrs, doc(cfg(feature = "primitive-types")))]
impl WellKnown for primitive_types::$ty {
type Deserializer = HexUintDeserializer;
const DE: Self::Deserializer = HexUintDeserializer;
}
#[cfg_attr(docsrs, doc(cfg(feature = "primitive-types")))]
impl WellKnownOption for primitive_types::$ty {}
)+
};
}
impl_well_known_uint!(U128, U256, U512);
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use primitive_types::{H160 as Address, H256, U128, U256};
use smart_config_derive::{DescribeConfig, DeserializeConfig};
use crate::{
config,
testing::{test, test_complete},
};
#[derive(Debug, PartialEq, DescribeConfig, DeserializeConfig)]
#[config(crate = crate)]
struct TestConfig {
int: U128,
#[config(default)]
hash: Option<H256>,
#[config(default)]
addresses: Vec<Address>,
#[config(default)]
balances: HashMap<Address, U256>,
}
#[test]
fn deserializing_values() {
let json = config!(
"int": "0x123",
"hash": "fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe",
"addresses": [
"0x0000000000000000000000000000000000001234",
"1212121212121212121212121212121212121212",
],
"balances": HashMap::from([
("0x0000000000000000000000000000000000004321", "0x3"),
])
);
let config = test_complete::<TestConfig>(json).unwrap();
assert_eq!(config.int, U128::from(0x123));
assert_eq!(config.hash, Some(H256::repeat_byte(0xfe)));
assert_eq!(
config.addresses,
[Address::from_low_u64_be(0x1234), Address::repeat_byte(0x12)]
);
assert_eq!(
config.balances,
HashMap::from([(Address::from_low_u64_be(0x4321), U256::from(3))])
);
}
#[test]
fn uint_prefix_error() {
let json = config!("int": "123");
let err = test::<TestConfig>(json).unwrap_err();
assert_eq!(err.len(), 1);
let err = err.first();
assert_eq!(err.path(), "int");
let inner = err.inner().to_string();
assert!(
inner.contains("invalid type") && inner.contains("0x-prefixed hex number"),
"{inner}"
);
}
}