Skip to main content

dotm/
hash.rs

1use anyhow::{Context, Result};
2use sha2::{Digest, Sha256};
3use std::path::Path;
4
5pub fn hash_file(path: &Path) -> Result<String> {
6    let content = std::fs::read(path)
7        .with_context(|| format!("failed to read file for hashing: {}", path.display()))?;
8    Ok(hash_content(&content))
9}
10
11fn hex_encode(bytes: &[u8]) -> String {
12    bytes
13        .iter()
14        .fold(String::with_capacity(bytes.len() * 2), |mut s, b| {
15            use std::fmt::Write;
16            write!(s, "{b:02x}").unwrap();
17            s
18        })
19}
20
21pub fn hash_content(content: &[u8]) -> String {
22    let mut hasher = Sha256::new();
23    hasher.update(content);
24    hex_encode(&hasher.finalize())
25}
26
27#[cfg(test)]
28mod tests {
29    use super::*;
30    use tempfile::TempDir;
31
32    #[test]
33    fn hash_file_returns_consistent_sha256() {
34        let dir = TempDir::new().unwrap();
35        let path = dir.path().join("test.txt");
36        std::fs::write(&path, "hello world").unwrap();
37
38        let hash1 = hash_file(&path).unwrap();
39        let hash2 = hash_file(&path).unwrap();
40        assert_eq!(hash1, hash2);
41        assert_eq!(
42            hash1,
43            "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
44        );
45    }
46
47    #[test]
48    fn hash_content_matches_hash_file() {
49        let dir = TempDir::new().unwrap();
50        let path = dir.path().join("test.txt");
51        let content = "some content";
52        std::fs::write(&path, content).unwrap();
53
54        assert_eq!(hash_file(&path).unwrap(), hash_content(content.as_bytes()));
55    }
56}