1use serde::{Deserialize, Deserializer, Serialize};
16use std::fmt::{self, Display};
17
18pub trait HashType: Sized {
20 fn bytes(&self) -> &[u8]
22 where
23 Self: AsRef<[u8]>,
24 {
25 self.as_ref()
26 }
27 fn from_str(v: &str) -> anyhow::Result<Self>;
29}
30
31macro_rules! hash_type_impl {
32 ($name:ty) => {
33 impl HashType for $name {
34 fn from_str(v: &str) -> anyhow::Result<Self> {
35 Ok(v.parse()?)
36 }
37 }
38 };
39}
40
41hash_type_impl!(monero::util::address::PaymentId);
42hash_type_impl!(monero::cryptonote::hash::Hash);
43
44impl HashType for Vec<u8> {
45 fn from_str(v: &str) -> anyhow::Result<Self> {
46 let v = v.strip_prefix("0x").unwrap_or(v);
47 Ok(hex::decode(v)?)
48 }
49}
50
51#[derive(Clone, Debug, Eq, PartialEq)]
53pub struct HashString<T>(pub T);
54
55impl<T> Display for HashString<T>
56where
57 T: HashType + AsRef<[u8]>,
58{
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 write!(f, "{}", hex::encode(self.0.bytes()))
61 }
62}
63
64impl<T> Serialize for HashString<T>
65where
66 T: HashType + AsRef<[u8]>,
67{
68 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
69 where
70 S: serde::ser::Serializer,
71 {
72 serializer.serialize_str(&self.to_string())
73 }
74}
75
76impl<'de, T> Deserialize<'de> for HashString<T>
77where
78 T: HashType,
79{
80 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
81 where
82 D: Deserializer<'de>,
83 {
84 let s = String::deserialize(deserializer)?;
85 Ok(Self(T::from_str(&s).map_err(serde::de::Error::custom)?))
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92 use serde_test::{assert_tokens, Token};
93
94 #[test]
95 fn trait_hash_type_for_payment_id() {
96 use monero::util::address::PaymentId;
97
98 let payment_id = PaymentId([0, 1, 2, 3, 4, 5, 6, 7]);
99
100 assert_eq!(payment_id.bytes(), &[0, 1, 2, 3, 4, 5, 6, 7]);
101
102 assert!(<PaymentId as HashType>::from_str("")
103 .unwrap_err()
104 .is::<rustc_hex::FromHexError>());
105 assert!(<PaymentId as HashType>::from_str("0x01234567")
106 .unwrap_err()
107 .is::<rustc_hex::FromHexError>());
108 assert!(<PaymentId as HashType>::from_str("0xgg")
109 .unwrap_err()
110 .is::<rustc_hex::FromHexError>());
111
112 assert_eq!(
113 <PaymentId as HashType>::from_str("0x0001020304050607").unwrap(),
114 payment_id
115 );
116 assert_eq!(
117 <PaymentId as HashType>::from_str("0001020304050607").unwrap(),
118 payment_id
119 );
120 }
121
122 #[test]
123 fn trait_hash_type_for_cryptonote_hash() {
124 use monero::cryptonote::hash::Hash;
125
126 let hash = Hash([250; 32]);
127
128 assert_eq!(hash.bytes(), [250; 32].as_slice());
129
130 assert!(<Hash as HashType>::from_str("")
131 .unwrap_err()
132 .is::<rustc_hex::FromHexError>());
133 assert!(<Hash as HashType>::from_str("0x01234567")
134 .unwrap_err()
135 .is::<rustc_hex::FromHexError>());
136 assert!(<Hash as HashType>::from_str("0xgg")
137 .unwrap_err()
138 .is::<rustc_hex::FromHexError>());
139
140 let hash_str = "fa".repeat(32);
141 assert_eq!(<Hash as HashType>::from_str(&hash_str).unwrap(), hash);
142
143 let hash_str = format!("0x{}", hash_str);
144 assert_eq!(<Hash as HashType>::from_str(&hash_str).unwrap(), hash);
145 }
146
147 #[test]
148 fn trait_hash_type_for_vec_u8() {
149 let vec_non_empty = vec![0, 1, 2, 3, 4];
150
151 assert_eq!(vec_non_empty.bytes(), &[0, 1, 2, 3, 4]);
152
153 assert_eq!(
154 <Vec<u8> as HashType>::from_str("").unwrap(),
155 Vec::<u8>::new()
156 );
157 assert!(<Vec<u8> as HashType>::from_str("0xgg")
158 .unwrap_err()
159 .is::<hex::FromHexError>());
160
161 assert_eq!(
162 <Vec<u8> as HashType>::from_str("0x0001020304").unwrap(),
163 vec_non_empty
164 );
165 assert_eq!(
166 <Vec<u8> as HashType>::from_str("0001020304").unwrap(),
167 vec_non_empty
168 );
169 }
170
171 #[test]
172 fn display_for_hash_string() {
173 let vec = vec![0, 1, 2, 3, 4];
174 let hash_string = HashString(vec);
175 assert_eq!(hash_string.to_string(), "0001020304");
176 }
177
178 #[test]
179 fn se_de_for_hash_string() {
180 let vec = vec![0, 1, 2, 3, 4];
181 let hash_string = HashString(vec);
182
183 assert_tokens(&hash_string, &[Token::Str("0001020304")]);
184 }
185}