1use std::fmt;
26use std::str::FromStr;
27
28use serde::{Deserialize, Serialize};
29
30#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
35pub struct Hash([u8; 32]);
36
37impl Hash {
38 #[inline]
44 pub const fn new(bytes: [u8; 32]) -> Self {
45 Self(bytes)
46 }
47
48 #[inline]
50 pub const fn zero() -> Self {
51 Self([0u8; 32])
52 }
53
54 #[inline]
56 pub const fn as_bytes(&self) -> &[u8; 32] {
57 &self.0
58 }
59
60 #[inline]
62 pub fn as_bytes_mut(&mut self) -> &mut [u8; 32] {
63 &mut self.0
64 }
65
66 #[inline]
68 pub fn as_slice(&self) -> &[u8] {
69 &self.0
70 }
71
72 #[inline]
74 pub fn into_inner(self) -> [u8; 32] {
75 self.0
76 }
77
78 #[inline]
82 pub fn to_vec(&self) -> Vec<u8> {
83 self.0.to_vec()
84 }
85
86 pub fn to_hex(&self) -> String {
90 hex::encode(self.0)
91 }
92
93 pub fn from_hex(s: &str) -> Result<Self, HashParseError> {
102 let s = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")).unwrap_or(s);
103 let bytes = hex::decode(s).map_err(|e| HashParseError::InvalidHex(e.to_string()))?;
104 if bytes.len() != 32 {
105 return Err(HashParseError::WrongLength {
106 expected: 32,
107 got: bytes.len(),
108 });
109 }
110 let mut arr = [0u8; 32];
111 arr.copy_from_slice(&bytes);
112 Ok(Self(arr))
113 }
114}
115
116impl AsRef<[u8]> for Hash {
121 #[inline]
122 fn as_ref(&self) -> &[u8] {
123 &self.0
124 }
125}
126
127impl AsRef<[u8; 32]> for Hash {
128 #[inline]
129 fn as_ref(&self) -> &[u8; 32] {
130 &self.0
131 }
132}
133
134impl From<[u8; 32]> for Hash {
135 #[inline]
136 fn from(bytes: [u8; 32]) -> Self {
137 Self(bytes)
138 }
139}
140
141impl From<&[u8; 32]> for Hash {
142 #[inline]
143 fn from(bytes: &[u8; 32]) -> Self {
144 Self(*bytes)
145 }
146}
147
148impl TryFrom<&[u8]> for Hash {
149 type Error = HashParseError;
150
151 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
152 if bytes.len() != 32 {
153 return Err(HashParseError::WrongLength {
154 expected: 32,
155 got: bytes.len(),
156 });
157 }
158 let mut arr = [0u8; 32];
159 arr.copy_from_slice(bytes);
160 Ok(Self(arr))
161 }
162}
163
164impl FromStr for Hash {
165 type Err = HashParseError;
166
167 fn from_str(s: &str) -> Result<Self, Self::Err> {
168 Self::from_hex(s)
169 }
170}
171
172impl fmt::Display for Hash {
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 if f.alternate() {
176 write!(f, "0x{}", self.to_hex())
177 } else {
178 write!(f, "0x{}…", &self.to_hex()[..8])
179 }
180 }
181}
182
183impl fmt::Debug for Hash {
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 write!(f, "Hash(0x{})", self.to_hex())
186 }
187}
188
189impl Default for Hash {
190 #[inline]
191 fn default() -> Self {
192 Self::zero()
193 }
194}
195
196#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
202#[allow(missing_docs)]
203pub enum HashParseError {
204 #[error("invalid hex: {0}")]
206 InvalidHex(String),
207
208 #[error("expected 32 bytes, got {got}")]
210 WrongLength { expected: usize, got: usize },
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[test]
218 fn test_hash_new() {
219 let h = Hash::new([1u8; 32]);
220 assert_eq!(h.as_bytes(), &[1u8; 32]);
221 }
222
223 #[test]
224 fn test_hash_zero() {
225 let h = Hash::zero();
226 assert_eq!(h.as_bytes(), &[0u8; 32]);
227 }
228
229 #[test]
230 fn test_hash_hex_roundtrip() {
231 let h = Hash::new([0xAB; 32]);
232 let hex = h.to_hex();
233 let parsed = Hash::from_hex(&hex).unwrap();
234 assert_eq!(h, parsed);
235 }
236
237 #[test]
238 fn test_hash_from_hex_with_prefix() {
239 let h = Hash::from_hex("0xabcdef").unwrap_err();
240 assert!(matches!(h, HashParseError::WrongLength { .. }));
241 }
242
243 #[test]
244 fn test_hash_display() {
245 let h = Hash::new([0xAB; 32]);
246 let display = format!("{}", h);
247 assert!(display.starts_with("0x"));
248 assert!(display.contains("…"));
249 }
250
251 #[test]
252 fn test_hash_display_altern() {
253 let h = Hash::new([0xAB; 32]);
254 let display = format!("{:#}", h);
255 assert_eq!(display.len(), 66); }
257
258 #[test]
259 fn test_hash_debug() {
260 let h = Hash::new([0xAB; 32]);
261 let debug = format!("{:?}", h);
262 assert!(debug.starts_with("Hash(0x"));
263 }
264
265 #[test]
266 fn test_hash_from_str() {
267 let h: Hash = "abababababababababababababababababababababababababababababababab"
268 .parse()
269 .unwrap();
270 assert_eq!(h.to_hex(), "abababababababababababababababababababababababababababababababab");
271 }
272}