Skip to main content

blockworx_doc/
hash.rs

1//! A simple wrapper for the blake3 hasher
2
3use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
4use std::{collections::BTreeMap, marker::PhantomData};
5
6/// The kind is a compile-time tag and must not reach the wire, so only
7/// the digest is encoded — as hex text in a format a human reads (the
8/// log, the projection, the clipboard), where a 32-element array of
9/// numbers would be unreadable and ungreppable, and as the bare bytes in
10/// a binary one.
11#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
12pub struct Hash<K: HashKind>([u8; 32], PhantomData<K>);
13
14impl<K: HashKind> Serialize for Hash<K> {
15    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
16        if serializer.is_human_readable() {
17            serializer.collect_str(self)
18        } else {
19            self.0.serialize(serializer)
20        }
21    }
22}
23
24impl<'de, K: HashKind> Deserialize<'de> for Hash<K> {
25    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
26        if deserializer.is_human_readable() {
27            let hex = String::deserialize(deserializer)?;
28            blake3::Hash::from_hex(&hex)
29                .map(|hash| Self(*hash.as_bytes(), PhantomData))
30                .map_err(de::Error::custom)
31        } else {
32            <[u8; 32]>::deserialize(deserializer).map(|bytes| Self(bytes, PhantomData))
33        }
34    }
35}
36
37impl<K: HashKind> std::fmt::Debug for Hash<K> {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        write!(f, "{:x?}", self.0)
40    }
41}
42
43impl<K: HashKind> std::fmt::Display for Hash<K> {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        for byte in self.0 {
46            write!(f, "{byte:02x}")?;
47        }
48        Ok(())
49    }
50}
51
52impl<K: HashKind> Hash<K> {
53    /// The hash of a byte string in one call — what content addressing
54    /// asks for everywhere the payload is already in hand.
55    pub fn of(bytes: &[u8]) -> Self {
56        let mut hasher = Hasher::<K>::new();
57        hasher.update(bytes);
58        hasher.finalize()
59    }
60
61    /// The digest itself, for a durable form that spells hashes its own
62    /// way — the log writes them as hex text, where the derived
63    /// `Serialize` would write an array of 32 numbers.
64    pub const fn bytes(&self) -> [u8; 32] {
65        self.0
66    }
67
68    /// The hash back from the text [`Display`](std::fmt::Display) writes,
69    /// or `None` for text that is not one.
70    pub fn from_hex(text: &str) -> Option<Self> {
71        blake3::Hash::from_hex(text)
72            .ok()
73            .map(|hash| Self(*hash.as_bytes(), PhantomData))
74    }
75}
76
77pub trait HashKind {}
78
79pub struct Hasher<K: HashKind>(blake3::Hasher, PhantomData<K>);
80
81impl<K: HashKind> Default for Hasher<K> {
82    fn default() -> Self {
83        Self::new()
84    }
85}
86
87impl<K: HashKind> Hasher<K> {
88    pub fn new() -> Self {
89        Self(blake3::Hasher::new(), PhantomData)
90    }
91    pub fn update(&mut self, input: &[u8]) -> &mut Self {
92        self.0.update(input);
93        self
94    }
95    pub fn finalize(&self) -> Hash<K> {
96        Hash(self.0.finalize().into(), PhantomData)
97    }
98}
99
100/// Lets a serializer stream into the hasher without buffering the payload.
101impl<K: HashKind> std::io::Write for Hasher<K> {
102    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
103        self.0.update(buf);
104        Ok(buf.len())
105    }
106    fn flush(&mut self) -> std::io::Result<()> {
107        Ok(())
108    }
109}
110
111pub type HashedMap<K, T> = BTreeMap<Hash<K>, T>;
112
113#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
114pub struct AssetKind;
115
116impl HashKind for AssetKind {}
117
118pub type AssetHash = Hash<AssetKind>;
119
120#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
121pub struct DocKind;
122
123impl HashKind for DocKind {}
124
125pub type DocHash = Hash<DocKind>;