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
103 .strip_prefix("0x")
104 .or_else(|| s.strip_prefix("0X"))
105 .unwrap_or(s);
106 let bytes = hex::decode(s).map_err(|e| HashParseError::InvalidHex(e.to_string()))?;
107 if bytes.len() != 32 {
108 return Err(HashParseError::WrongLength {
109 expected: 32,
110 got: bytes.len(),
111 });
112 }
113 let mut arr = [0u8; 32];
114 arr.copy_from_slice(&bytes);
115 Ok(Self(arr))
116 }
117}
118
119impl AsRef<[u8]> for Hash {
124 #[inline]
125 fn as_ref(&self) -> &[u8] {
126 &self.0
127 }
128}
129
130impl AsRef<[u8; 32]> for Hash {
131 #[inline]
132 fn as_ref(&self) -> &[u8; 32] {
133 &self.0
134 }
135}
136
137impl From<[u8; 32]> for Hash {
138 #[inline]
139 fn from(bytes: [u8; 32]) -> Self {
140 Self(bytes)
141 }
142}
143
144impl From<&[u8; 32]> for Hash {
145 #[inline]
146 fn from(bytes: &[u8; 32]) -> Self {
147 Self(*bytes)
148 }
149}
150
151impl TryFrom<&[u8]> for Hash {
152 type Error = HashParseError;
153
154 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
155 if bytes.len() != 32 {
156 return Err(HashParseError::WrongLength {
157 expected: 32,
158 got: bytes.len(),
159 });
160 }
161 let mut arr = [0u8; 32];
162 arr.copy_from_slice(bytes);
163 Ok(Self(arr))
164 }
165}
166
167impl FromStr for Hash {
168 type Err = HashParseError;
169
170 fn from_str(s: &str) -> Result<Self, Self::Err> {
171 Self::from_hex(s)
172 }
173}
174
175impl fmt::Display for Hash {
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 if f.alternate() {
179 write!(f, "0x{}", self.to_hex())
180 } else {
181 write!(f, "0x{}…", &self.to_hex()[..8])
182 }
183 }
184}
185
186impl fmt::Debug for Hash {
187 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188 write!(f, "Hash(0x{})", self.to_hex())
189 }
190}
191
192impl Default for Hash {
193 #[inline]
194 fn default() -> Self {
195 Self::zero()
196 }
197}
198
199#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
205#[allow(missing_docs)]
206pub enum HashParseError {
207 #[error("invalid hex: {0}")]
209 InvalidHex(String),
210
211 #[error("expected 32 bytes, got {got}")]
213 WrongLength { expected: usize, got: usize },
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn test_hash_new() {
222 let h = Hash::new([1u8; 32]);
223 assert_eq!(h.as_bytes(), &[1u8; 32]);
224 }
225
226 #[test]
227 fn test_hash_zero() {
228 let h = Hash::zero();
229 assert_eq!(h.as_bytes(), &[0u8; 32]);
230 }
231
232 #[test]
233 fn test_hash_hex_roundtrip() {
234 let h = Hash::new([0xAB; 32]);
235 let hex = h.to_hex();
236 let parsed = Hash::from_hex(&hex).unwrap();
237 assert_eq!(h, parsed);
238 }
239
240 #[test]
241 fn test_hash_from_hex_with_prefix() {
242 let h = Hash::from_hex("0xabcdef").unwrap_err();
243 assert!(matches!(h, HashParseError::WrongLength { .. }));
244 }
245
246 #[test]
247 fn test_hash_display() {
248 let h = Hash::new([0xAB; 32]);
249 let display = format!("{}", h);
250 assert!(display.starts_with("0x"));
251 assert!(display.contains("…"));
252 }
253
254 #[test]
255 fn test_hash_display_altern() {
256 let h = Hash::new([0xAB; 32]);
257 let display = format!("{:#}", h);
258 assert_eq!(display.len(), 66); }
260
261 #[test]
262 fn test_hash_debug() {
263 let h = Hash::new([0xAB; 32]);
264 let debug = format!("{:?}", h);
265 assert!(debug.starts_with("Hash(0x"));
266 }
267
268 #[test]
269 fn test_hash_from_str() {
270 let h: Hash = "abababababababababababababababababababababababababababababababab"
271 .parse()
272 .unwrap();
273 assert_eq!(
274 h.to_hex(),
275 "abababababababababababababababababababababababababababababababab"
276 );
277 }
278}