pub mod fs;
use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use sha2::{Digest as Sha2Digest, Sha256};
use thiserror::Error;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Digest([u8; 32]);
impl Digest {
pub fn compute(data: &[u8]) -> Self {
let hash = Sha256::digest(data);
let mut bytes = [0u8; 32];
bytes.copy_from_slice(&hash);
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn to_hex(&self) -> String {
hex::encode(self.0)
}
}
impl fmt::Display for Digest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.to_hex())
}
}
impl fmt::Debug for Digest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Digest({})", &self.to_hex()[..12])
}
}
impl FromStr for Digest {
type Err = CasError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let bytes = hex::decode(s).map_err(|_| CasError::InvalidDigest(s.to_string()))?;
if bytes.len() != 32 {
return Err(CasError::InvalidDigest(s.to_string()));
}
let mut arr = [0u8; 32];
arr.copy_from_slice(&bytes);
Ok(Self(arr))
}
}
#[derive(Debug, Error)]
pub enum CasError {
#[error("blob not found: {0}")]
NotFound(Digest),
#[error("invalid digest hex: {0}")]
InvalidDigest(String),
#[error("io error: {0}")]
Io(#[from] std::io::Error),
}
pub type Result<T> = std::result::Result<T, CasError>;
pub trait CasStore: Send + Sync {
fn put(&self, data: &[u8]) -> Result<Digest>;
fn get(&self, digest: &Digest) -> Result<Vec<u8>>;
fn exists(&self, digest: &Digest) -> Result<bool>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn digest_display_fromstr_roundtrip() {
let d = Digest::compute(b"hello world");
let hex = d.to_string();
assert_eq!(hex.len(), 64);
let parsed: Digest = hex.parse().unwrap();
assert_eq!(d, parsed);
}
#[test]
fn digest_fromstr_invalid_hex() {
assert!("not-valid-hex".parse::<Digest>().is_err());
}
#[test]
fn digest_fromstr_wrong_length() {
assert!("abcd".parse::<Digest>().is_err());
}
#[test]
fn digest_deterministic() {
let a = Digest::compute(b"test data");
let b = Digest::compute(b"test data");
assert_eq!(a, b);
}
#[test]
fn digest_different_data_different_hash() {
let a = Digest::compute(b"data a");
let b = Digest::compute(b"data b");
assert_ne!(a, b);
}
}