1use std::convert::TryFrom;
4use std::fmt;
5use std::hash::Hash as StdHash;
6use std::str::FromStr;
7
8use arrayvec::ArrayVec;
9use bamboo_rs_core_ed25519_yasmf::yasmf_hash::new_blake3;
10use serde::{Deserialize, Serialize};
11use serde_bytes::ByteBuf;
12use yasmf_hash::{YasmfHash, BLAKE3_HASH_SIZE, MAX_YAMF_HASH_SIZE};
13
14use crate::hash::error::HashError;
15use crate::hash::HashId;
16use crate::serde::deserialize_hex;
17use crate::{Human, Validate};
18
19pub const HASH_SIZE: usize = BLAKE3_HASH_SIZE;
21
22pub type Blake3ArrayVec = ArrayVec<[u8; HASH_SIZE]>;
24
25#[derive(Clone, Debug, Ord, PartialOrd, PartialEq, Eq, StdHash)]
32pub struct Hash(String);
33
34impl Hash {
35 pub fn new(value: &str) -> Result<Self, HashError> {
37 let hash = Self(String::from(value));
38 hash.validate()?;
39 Ok(hash)
40 }
41
42 pub fn new_from_bytes(value: &[u8]) -> Self {
44 let blake3_hash = new_blake3(value);
46
47 let mut bytes = Vec::new();
49 blake3_hash
50 .encode_write(&mut bytes)
51 .unwrap();
53
54 let hex_str = hex::encode(&bytes);
56
57 Self(hex_str)
58 }
59
60 pub fn to_bytes(&self) -> Vec<u8> {
62 hex::decode(&self.0).unwrap()
64 }
65
66 pub fn as_str(&self) -> &str {
68 &self.0
69 }
70}
71
72impl Validate for Hash {
73 type Error = HashError;
74
75 fn validate(&self) -> Result<(), Self::Error> {
76 match hex::decode(&self.0) {
78 Ok(bytes) => {
79 if bytes.len() != HASH_SIZE + 2 {
81 return Err(HashError::InvalidLength(bytes.len(), HASH_SIZE + 2));
82 }
83
84 match YasmfHash::<&[u8]>::decode(&bytes) {
86 Ok((YasmfHash::Blake3(_), _)) => Ok(()),
87 _ => Err(HashError::DecodingFailed),
88 }
89 }
90 Err(_) => Err(HashError::InvalidHexEncoding),
91 }
92 }
93}
94
95impl fmt::Display for Hash {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 write!(f, "{}", self.as_str())
98 }
99}
100
101impl Human for Hash {
102 fn display(&self) -> String {
114 let offset = MAX_YAMF_HASH_SIZE * 2 - 6;
115 format!("<Hash {}>", &self.as_str()[offset..])
116 }
117}
118
119impl Serialize for Hash {
120 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
121 where
122 S: serde::Serializer,
123 {
124 if serializer.is_human_readable() {
125 self.as_str().serialize(serializer)
126 } else {
127 ByteBuf::from(self.to_bytes()).serialize(serializer)
128 }
129 }
130}
131
132impl<'de> Deserialize<'de> for Hash {
133 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
134 where
135 D: serde::Deserializer<'de>,
136 {
137 let hash_bytes = deserialize_hex(deserializer)?;
139
140 let hash_str = hex::encode(hash_bytes);
142 Hash::new(&hash_str).map_err(|err: HashError| serde::de::Error::custom(err.to_string()))
143 }
144}
145
146impl<T: core::borrow::Borrow<[u8]> + Clone> From<&YasmfHash<T>> for Hash {
148 fn from(yasmf_hash: &YasmfHash<T>) -> Self {
149 let mut out = [0u8; MAX_YAMF_HASH_SIZE];
150 let _ = yasmf_hash.encode(&mut out).unwrap();
152 Self::new(&hex::encode(out)).unwrap()
154 }
155}
156
157impl From<&Hash> for YasmfHash<Blake3ArrayVec> {
161 fn from(hash: &Hash) -> YasmfHash<Blake3ArrayVec> {
162 let bytes = hash.to_bytes();
163 let yasmf_hash = YasmfHash::<Blake3ArrayVec>::decode_owned(&bytes).unwrap();
164 yasmf_hash.0
165 }
166}
167
168impl TryFrom<&str> for Hash {
170 type Error = HashError;
171
172 fn try_from(str: &str) -> Result<Self, Self::Error> {
173 Self::new(str)
174 }
175}
176
177impl FromStr for Hash {
179 type Err = HashError;
180
181 fn from_str(s: &str) -> Result<Self, Self::Err> {
182 Self::new(s)
183 }
184}
185
186impl TryFrom<String> for Hash {
188 type Error = HashError;
189
190 fn try_from(str: String) -> Result<Self, Self::Error> {
191 Self::new(&str)
192 }
193}
194
195impl<T: HashId> From<T> for Hash {
196 fn from(hash_id: T) -> Hash {
197 hash_id.as_hash().to_owned()
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use std::collections::HashMap;
204 use std::convert::{TryFrom, TryInto};
205
206 use ciborium::cbor;
207 use serde_bytes::ByteBuf;
208 use yasmf_hash::YasmfHash;
209
210 use crate::serde::{deserialize_into, serialize_from, serialize_value};
211 use crate::Human;
212
213 use super::{Blake3ArrayVec, Hash};
214
215 #[test]
216 fn validate() {
217 assert!(Hash::new("abcdefg").is_err());
218 assert!(Hash::new("112233445566ff").is_err());
219 assert!(
220 Hash::new("01234567812345678123456781234567812345678123456781234567812345678").is_err()
221 );
222 assert!(
223 Hash::new("0020b177ec1bf26dfb3b7010d473e6d44713b29b765b99c6e60ecbfae742de496543")
224 .is_ok()
225 );
226 }
227
228 #[test]
229 fn new_from_bytes() {
230 assert_eq!(
231 Hash::new_from_bytes(&[1, 2, 3]),
232 Hash::new("0020b177ec1bf26dfb3b7010d473e6d44713b29b765b99c6e60ecbfae742de496543")
233 .unwrap()
234 );
235 }
236
237 #[test]
238 fn convert_yasmf() {
239 let hash = Hash::new_from_bytes(&[1, 2, 3]);
240 let yasmf_hash = Into::<YasmfHash<Blake3ArrayVec>>::into(&hash);
241 let hash_restored = Into::<Hash>::into(&yasmf_hash);
242 assert_eq!(hash, hash_restored);
243 }
244
245 #[test]
246 fn it_hashes() {
247 let hash = Hash::new_from_bytes(&[1, 2, 3]);
248 let mut hash_map = HashMap::new();
249 let key_value = "Value identified by a hash".to_string();
250 hash_map.insert(&hash, key_value.clone());
251 let key_value_retrieved = hash_map.get(&hash).unwrap().to_owned();
252 assert_eq!(key_value, key_value_retrieved)
253 }
254
255 #[test]
256 fn from_string() {
257 let hash_str = "0020b177ec1bf26dfb3b7010d473e6d44713b29b765b99c6e60ecbfae742de496543";
258
259 let hash_from_str: Hash = hash_str.try_into().unwrap();
261 assert_eq!(hash_str, hash_from_str.as_str());
262
263 let hash_from_parse: Hash = hash_str.parse().unwrap();
265 assert_eq!(hash_str, hash_from_parse.as_str());
266
267 let hash_from_string = Hash::try_from(String::from(hash_str)).unwrap();
269 assert_eq!(hash_str, hash_from_string.as_str());
270 }
271
272 #[test]
273 fn string_representation() {
274 let hash_str = "0020b177ec1bf26dfb3b7010d473e6d44713b29b765b99c6e60ecbfae742de496543";
275 let hash = Hash::new(hash_str).unwrap();
276
277 assert_eq!(hash_str, hash.as_str());
278 assert_eq!(hash_str, hash.to_string());
279 assert_eq!(hash_str, format!("{}", hash));
280 }
281
282 #[test]
283 fn short_representation() {
284 let hash_str = "0020b177ec1bf26dfb3b7010d473e6d44713b29b765b99c6e60ecbfae742de496543";
285 let hash = Hash::new(hash_str).unwrap();
286
287 assert_eq!(hash.display(), "<Hash 496543>");
288 }
289
290 #[test]
291 fn serialize() {
292 let hash_str = "0020cfb0fa37f36d082faad3886a9ffbcc2813b7afe90f0609a556d425f1a76ec805";
293 let hash = Hash::new(hash_str).unwrap();
294 let bytes = serialize_from(hash.clone());
295 assert_eq!(
296 bytes,
297 vec![
298 88, 34, 0, 32, 207, 176, 250, 55, 243, 109, 8, 47, 170, 211, 136, 106, 159, 251,
299 204, 40, 19, 183, 175, 233, 15, 6, 9, 165, 86, 212, 37, 241, 167, 110, 200, 5
300 ]
301 );
302
303 let human_readable_cbor = cbor!(hash).unwrap();
305 assert!(human_readable_cbor.is_text());
306 assert_eq!(human_readable_cbor.as_text().unwrap(), hash_str)
307 }
308
309 #[test]
310 fn deserialize() {
311 let hash_bytes = [
313 0, 32, 207, 176, 250, 55, 243, 109, 8, 47, 170, 211, 136, 106, 159, 251, 204, 40, 19,
314 183, 175, 233, 15, 6, 9, 165, 86, 212, 37, 241, 167, 110, 200, 5,
315 ];
316 let hash: Hash =
317 deserialize_into(&serialize_value(cbor!(ByteBuf::from(hash_bytes)))).unwrap();
318 let hash_str = "0020cfb0fa37f36d082faad3886a9ffbcc2813b7afe90f0609a556d425f1a76ec805";
319 assert_eq!(Hash::new(hash_str).unwrap(), hash);
320
321 let invalid_hash = deserialize_into::<Hash>(&serialize_value(cbor!("1234")));
323 assert!(invalid_hash.is_err());
324 let invalid_hash = deserialize_into::<Hash>(&serialize_value(cbor!("xyz".as_bytes())));
325 assert!(invalid_hash.is_err(), "{:#?}", invalid_hash);
326 }
327
328 #[test]
329 fn deserialize_human_readable() {
330 let hash_str = "0020cfb0fa37f36d082faad3886a9ffbcc2813b7afe90f0609a556d425f1a76ec805";
331
332 #[derive(serde::Deserialize, Debug, PartialEq)]
333 struct Test {
334 hash: Hash,
335 }
336
337 let json = format!(
339 r#"
340 {{
341 "hash": "{hash_str}"
342 }}
343 "#
344 );
345
346 let result: Test = serde_json::from_str(&json).unwrap();
347 assert_eq!(
348 Test {
349 hash: Hash::new(hash_str).unwrap()
350 },
351 result
352 );
353 }
354}