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}