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::{Token, assert_tokens};
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!(
103 <PaymentId as HashType>::from_str("")
104 .unwrap_err()
105 .is::<rustc_hex::FromHexError>()
106 );
107 assert!(
108 <PaymentId as HashType>::from_str("0x01234567")
109 .unwrap_err()
110 .is::<rustc_hex::FromHexError>()
111 );
112 assert!(
113 <PaymentId as HashType>::from_str("0xgg")
114 .unwrap_err()
115 .is::<rustc_hex::FromHexError>()
116 );
117
118 assert_eq!(
119 <PaymentId as HashType>::from_str("0x0001020304050607").unwrap(),
120 payment_id
121 );
122 assert_eq!(
123 <PaymentId as HashType>::from_str("0001020304050607").unwrap(),
124 payment_id
125 );
126 }
127
128 #[test]
129 fn trait_hash_type_for_cryptonote_hash() {
130 use monero::cryptonote::hash::Hash;
131
132 let hash = Hash([250; 32]);
133
134 assert_eq!(hash.bytes(), [250; 32].as_slice());
135
136 assert!(
137 <Hash as HashType>::from_str("")
138 .unwrap_err()
139 .is::<rustc_hex::FromHexError>()
140 );
141 assert!(
142 <Hash as HashType>::from_str("0x01234567")
143 .unwrap_err()
144 .is::<rustc_hex::FromHexError>()
145 );
146 assert!(
147 <Hash as HashType>::from_str("0xgg")
148 .unwrap_err()
149 .is::<rustc_hex::FromHexError>()
150 );
151
152 let hash_str = "fa".repeat(32);
153 assert_eq!(<Hash as HashType>::from_str(&hash_str).unwrap(), hash);
154
155 let hash_str = format!("0x{}", hash_str);
156 assert_eq!(<Hash as HashType>::from_str(&hash_str).unwrap(), hash);
157 }
158
159 #[test]
160 fn trait_hash_type_for_vec_u8() {
161 let vec_non_empty = vec![0, 1, 2, 3, 4];
162
163 assert_eq!(vec_non_empty.bytes(), &[0, 1, 2, 3, 4]);
164
165 assert_eq!(
166 <Vec<u8> as HashType>::from_str("").unwrap(),
167 Vec::<u8>::new()
168 );
169 assert!(
170 <Vec<u8> as HashType>::from_str("0xgg")
171 .unwrap_err()
172 .is::<hex::FromHexError>()
173 );
174
175 assert_eq!(
176 <Vec<u8> as HashType>::from_str("0x0001020304").unwrap(),
177 vec_non_empty
178 );
179 assert_eq!(
180 <Vec<u8> as HashType>::from_str("0001020304").unwrap(),
181 vec_non_empty
182 );
183 }
184
185 #[test]
186 fn display_for_hash_string() {
187 let vec = vec![0, 1, 2, 3, 4];
188 let hash_string = HashString(vec);
189 assert_eq!(hash_string.to_string(), "0001020304");
190 }
191
192 #[test]
193 fn se_de_for_hash_string() {
194 let vec = vec![0, 1, 2, 3, 4];
195 let hash_string = HashString(vec);
196
197 assert_tokens(&hash_string, &[Token::Str("0001020304")]);
198 }
199}