Skip to main content

tj_core/
project_hash.rs

1use anyhow::Context;
2use sha2::{Digest, Sha256};
3use std::path::Path;
4
5pub fn from_path(p: impl AsRef<Path>) -> anyhow::Result<String> {
6    let canonical = dunce::canonicalize(p.as_ref())
7        .with_context(|| format!("canonicalize {:?}", p.as_ref()))?;
8    let bytes = canonical.as_os_str().as_encoded_bytes();
9    let mut h = Sha256::new();
10    h.update(bytes);
11    let digest = h.finalize();
12    let hex: String = digest.iter().take(8).map(|b| format!("{b:02x}")).collect();
13    debug_assert_eq!(hex.len(), 16);
14    Ok(hex)
15}
16
17#[cfg(test)]
18mod tests {
19    use super::*;
20    use tempfile::TempDir;
21
22    #[test]
23    fn same_path_yields_same_hash() {
24        let d = TempDir::new().unwrap();
25        let a = from_path(d.path()).unwrap();
26        let b = from_path(d.path()).unwrap();
27        assert_eq!(a, b);
28        assert_eq!(a.len(), 16, "16 hex chars expected, got: {a}");
29    }
30
31    #[test]
32    fn different_paths_yield_different_hashes() {
33        let d1 = TempDir::new().unwrap();
34        let d2 = TempDir::new().unwrap();
35        let a = from_path(d1.path()).unwrap();
36        let b = from_path(d2.path()).unwrap();
37        assert_ne!(a, b);
38    }
39}