use sha2::{Digest, Sha256};
use std::fmt;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct ContentDigest([u8; 32]);
impl ContentDigest {
#[must_use]
pub fn of(bytes: &[u8]) -> Self {
Self(Sha256::digest(bytes).into())
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
#[must_use]
pub fn from_hex(text: &str) -> Option<Self> {
let text = match text.strip_prefix("sha256:") {
Some(digits) => digits,
None => text,
};
if text.len() != 64 || !text.bytes().all(|byte| byte.is_ascii_hexdigit()) {
return None;
}
let mut bytes = [0_u8; 32];
for (slot, pair) in bytes.iter_mut().zip(text.as_bytes().chunks(2)) {
let digits = std::str::from_utf8(pair).ok()?;
*slot = u8::from_str_radix(digits, 16).ok()?;
}
Some(Self(bytes))
}
}
impl fmt::Display for ContentDigest {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("sha256:")?;
for byte in self.0 {
write!(formatter, "{byte:02x}")?;
}
Ok(())
}
}
impl fmt::Debug for ContentDigest {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{self}")
}
}
#[cfg(test)]
#[path = "digest_tests.rs"]
mod tests;