1use std::fmt;
19use std::str::FromStr;
20
21#[cfg(feature = "arbitrary")]
22use arbitrary::Arbitrary;
23use thiserror::Error;
24
25use crate::traits::{OperationId, ShortFormat};
26
27pub const HASH_LEN: usize = blake3::KEY_LEN;
29
30#[derive(Clone, Copy, PartialEq, Eq, Hash)]
32pub struct Hash(blake3::Hash);
33
34impl Hash {
35 pub fn digest(buf: impl AsRef<[u8]>) -> Self {
37 Self(blake3::hash(buf.as_ref()))
38 }
39
40 pub const fn from_bytes(bytes: [u8; HASH_LEN]) -> Self {
42 Self(blake3::Hash::from_bytes(bytes))
43 }
44
45 pub fn as_bytes(&self) -> &[u8; HASH_LEN] {
47 self.0.as_bytes()
48 }
49
50 pub fn to_hex(&self) -> String {
52 self.0.to_hex().to_string()
53 }
54}
55
56impl AsRef<[u8]> for Hash {
57 fn as_ref(&self) -> &[u8] {
58 self.0.as_bytes()
59 }
60}
61
62impl From<Hash> for blake3::Hash {
63 fn from(value: Hash) -> Self {
64 value.0
65 }
66}
67
68impl From<blake3::Hash> for Hash {
69 fn from(value: blake3::Hash) -> Self {
70 Self(value)
71 }
72}
73
74impl From<[u8; HASH_LEN]> for Hash {
75 fn from(value: [u8; HASH_LEN]) -> Self {
76 Self(blake3::Hash::from(value))
77 }
78}
79
80impl From<Hash> for [u8; HASH_LEN] {
81 fn from(value: Hash) -> Self {
82 *value.as_bytes()
83 }
84}
85
86impl From<&[u8; HASH_LEN]> for Hash {
87 fn from(value: &[u8; HASH_LEN]) -> Self {
88 Self(blake3::Hash::from(*value))
89 }
90}
91
92impl TryFrom<&[u8]> for Hash {
93 type Error = HashError;
94
95 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
96 let value_len = value.len();
97
98 let checked_value: [u8; HASH_LEN] = value
99 .try_into()
100 .map_err(|_| HashError::InvalidLength(value_len, HASH_LEN))?;
101
102 Ok(Self(blake3::Hash::from(checked_value)))
103 }
104}
105
106impl From<&Hash> for [u8; 32] {
107 fn from(value: &Hash) -> Self {
108 *value.as_bytes()
109 }
110}
111
112impl FromStr for Hash {
113 type Err = HashError;
114
115 fn from_str(value: &str) -> Result<Self, Self::Err> {
116 Self::try_from(hex::decode(value)?.as_slice())
117 }
118}
119
120#[allow(clippy::non_canonical_partial_ord_impl)]
121impl PartialOrd for Hash {
122 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
123 Some(self.0.as_bytes().cmp(other.0.as_bytes()))
124 }
125}
126
127impl Ord for Hash {
128 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
129 self.0.as_bytes().cmp(other.0.as_bytes())
130 }
131}
132
133impl fmt::Display for Hash {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 write!(f, "{}", self.to_hex())
136 }
137}
138
139impl fmt::Debug for Hash {
140 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141 f.debug_tuple("Hash").field(self.0.as_bytes()).finish()
142 }
143}
144
145#[cfg(feature = "arbitrary")]
146impl<'a> Arbitrary<'a> for Hash {
147 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
148 let bytes = <[u8; HASH_LEN] as Arbitrary>::arbitrary(u)?;
149 Ok(Hash::from_bytes(bytes))
150 }
151}
152
153impl ShortFormat for Hash {
154 fn fmt_short(&self) -> String {
155 self.to_hex()[0..5].to_string()
156 }
157}
158
159impl OperationId for Hash {}
160
161#[derive(Error, Debug)]
163pub enum HashError {
164 #[error("invalid hash length {0} bytes, expected {1} bytes")]
166 InvalidLength(usize, usize),
167
168 #[error("invalid hex encoding in hash string")]
170 InvalidHexEncoding(#[from] hex::FromHexError),
171}
172
173#[cfg(test)]
174mod tests {
175 use super::{Hash, HashError};
176
177 #[test]
178 fn hashing() {
179 let hash = Hash::digest([1, 2, 3]);
180
181 assert_eq!(
182 hash.as_bytes(),
183 &[
184 177, 119, 236, 27, 242, 109, 251, 59, 112, 16, 212, 115, 230, 212, 71, 19, 178,
185 155, 118, 91, 153, 198, 230, 14, 203, 250, 231, 66, 222, 73, 101, 67
186 ]
187 );
188 }
189
190 #[test]
191 fn invalid_length() {
192 let bytes = vec![254, 100, 4, 7];
193 let result: Result<Hash, HashError> = bytes.as_slice().try_into();
194 matches!(result, Err(HashError::InvalidLength(4, 32)));
195 }
196
197 #[test]
198 fn invalid_hex_encoding() {
199 let hex = "notreallyahexstring";
200 let result: Result<Hash, HashError> = hex.parse();
201 matches!(
202 result,
203 Err(HashError::InvalidHexEncoding(
204 hex::FromHexError::InvalidHexCharacter { c: 'n', index: 0 }
205 ))
206 );
207 }
208}