defuse_serde_utils/
hex.rs

1pub use serde_with::formats::{Format, Lowercase, Uppercase};
2
3use derive_more::From;
4use near_sdk::serde::{Deserialize, Serialize};
5use serde_with::serde_as;
6
7/// Helper type to implement `#[derive(Serialize, Deserialize)]`,
8/// as `#[near_bindgen]` doesn't support `#[serde(...)]` attributes on method arguments
9#[cfg_attr(
10    all(feature = "abi", not(target_arch = "wasm32")),
11    serde_as(schemars = true),
12    derive(::near_sdk::schemars::JsonSchema),
13    schemars(crate = "::near_sdk::schemars", transparent)
14)]
15#[cfg_attr(
16    not(all(feature = "abi", not(target_arch = "wasm32"))),
17    serde_as(schemars = false)
18)]
19#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, From)]
20#[serde(
21    crate = "::near_sdk::serde",
22    bound(serialize = "T: AsRef<[u8]>", deserialize = "T: TryFrom<Vec<u8>>")
23)]
24pub struct AsHex<T>(#[serde_as(as = "::serde_with::hex::Hex")] pub T);
25
26impl<T> AsHex<T> {
27    #[inline]
28    pub fn into_inner(self) -> T {
29        self.0
30    }
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36    use near_sdk::serde_json;
37
38    #[test]
39    fn serialize_vec() {
40        let val = AsHex(vec![0xde, 0xad, 0xbe, 0xef]);
41        let json = serde_json::to_string(&val).unwrap();
42        assert_eq!(json, r#""deadbeef""#);
43    }
44
45    #[test]
46    fn deserialize_vec() {
47        let val: AsHex<Vec<u8>> = serde_json::from_str(r#""deadbeef""#).unwrap();
48        assert_eq!(val.0, vec![0xde, 0xad, 0xbe, 0xef]);
49    }
50
51    #[test]
52    fn roundtrip_vec() {
53        let original = vec![0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef];
54        let wrapped = AsHex(original.clone());
55        let json = serde_json::to_string(&wrapped).unwrap();
56        let recovered: AsHex<Vec<u8>> = serde_json::from_str(&json).unwrap();
57        assert_eq!(recovered.0, original);
58    }
59
60    #[test]
61    fn serialize_fixed_array() {
62        let val = AsHex([0xff, 0x00, 0xab]);
63        let json = serde_json::to_string(&val).unwrap();
64        assert_eq!(json, r#""ff00ab""#);
65    }
66
67    #[test]
68    fn deserialize_fixed_array() {
69        let val: AsHex<[u8; 3]> = serde_json::from_str(r#""ff00ab""#).unwrap();
70        assert_eq!(val.0, [0xff, 0x00, 0xab]);
71    }
72
73    #[test]
74    fn empty_bytes() {
75        let val = AsHex(Vec::<u8>::new());
76        let json = serde_json::to_string(&val).unwrap();
77        assert_eq!(json, r#""""#);
78
79        let recovered: AsHex<Vec<u8>> = serde_json::from_str(&json).unwrap();
80        assert!(recovered.0.is_empty());
81    }
82
83    #[test]
84    fn into_inner() {
85        let data = vec![1, 2, 3];
86        let wrapped = AsHex(data.clone());
87        assert_eq!(wrapped.into_inner(), data);
88    }
89
90    #[test]
91    fn from_impl() {
92        let data = vec![1, 2, 3];
93        let wrapped: AsHex<Vec<u8>> = data.clone().into();
94        assert_eq!(wrapped.0, data);
95    }
96
97    #[test]
98    fn deserialize_uppercase_input() {
99        let val: AsHex<Vec<u8>> = serde_json::from_str(r#""DEADBEEF""#).unwrap();
100        assert_eq!(val.0, vec![0xde, 0xad, 0xbe, 0xef]);
101    }
102
103    #[test]
104    fn deserialize_mixed_case_input() {
105        let val: AsHex<Vec<u8>> = serde_json::from_str(r#""DeAdBeEf""#).unwrap();
106        assert_eq!(val.0, vec![0xde, 0xad, 0xbe, 0xef]);
107    }
108
109    #[test]
110    fn deserialize_invalid_hex() {
111        let result = serde_json::from_str::<AsHex<Vec<u8>>>(r#""xyz""#);
112        assert!(result.is_err());
113    }
114
115    #[test]
116    fn deserialize_odd_length() {
117        let result = serde_json::from_str::<AsHex<Vec<u8>>>(r#""abc""#);
118        assert!(result.is_err());
119    }
120
121    #[test]
122    fn deserialize_wrong_length_for_fixed_array() {
123        let result = serde_json::from_str::<AsHex<[u8; 4]>>(r#""deadbeefaa""#);
124        assert!(result.is_err());
125    }
126}