molframe_core/contract/
digest.rs1use sha2::{Digest, Sha256};
11use std::fmt;
12
13#[derive(Clone, Copy, PartialEq, Eq, Hash)]
15pub struct ContentDigest([u8; 32]);
16
17impl ContentDigest {
18 #[must_use]
20 pub fn of(bytes: &[u8]) -> Self {
21 Self(Sha256::digest(bytes).into())
22 }
23
24 #[must_use]
26 pub const fn as_bytes(&self) -> &[u8; 32] {
27 &self.0
28 }
29
30 #[must_use]
32 pub fn from_hex(text: &str) -> Option<Self> {
33 let text = match text.strip_prefix("sha256:") {
34 Some(digits) => digits,
35 None => text,
36 };
37 if text.len() != 64 || !text.bytes().all(|byte| byte.is_ascii_hexdigit()) {
38 return None;
39 }
40 let mut bytes = [0_u8; 32];
41 for (slot, pair) in bytes.iter_mut().zip(text.as_bytes().chunks(2)) {
42 let digits = std::str::from_utf8(pair).ok()?;
43 *slot = u8::from_str_radix(digits, 16).ok()?;
44 }
45 Some(Self(bytes))
46 }
47}
48
49impl fmt::Display for ContentDigest {
50 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
51 formatter.write_str("sha256:")?;
52 for byte in self.0 {
53 write!(formatter, "{byte:02x}")?;
54 }
55 Ok(())
56 }
57}
58
59impl fmt::Debug for ContentDigest {
60 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
61 write!(formatter, "{self}")
62 }
63}
64
65#[cfg(test)]
66#[path = "digest_tests.rs"]
67mod tests;