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}