clnrm_core/cache/hash.rs
1//! SHA-256 file hashing for cache invalidation
2//!
3//! Provides content-based hashing for detecting file changes.
4//! Uses SHA-256 for cryptographic strength and collision resistance.
5
6use crate::error::{CleanroomError, Result};
7use sha2::{Digest, Sha256};
8use std::fs;
9use std::path::Path;
10use tracing::debug;
11
12/// Hash content using SHA-256 and return hex string
13///
14/// Core Team Compliance:
15/// - Proper error handling with Result<String, CleanroomError>
16/// - No unwrap() or expect() calls
17/// - Efficient for small-to-medium sized files
18///
19/// # Arguments
20/// * `content` - String content to hash (typically rendered TOML)
21///
22/// # Returns
23/// Hex-encoded SHA-256 hash (64 characters)
24///
25/// # Performance
26/// - Hash calculation: <50ms per file for typical TOML files
27/// - Memory efficient: processes content in-place
28pub fn hash_content(content: &str) -> Result<String> {
29 let mut hasher = Sha256::new();
30 hasher.update(content.as_bytes());
31 let result = hasher.finalize();
32 let hex = format!("{:x}", result);
33
34 debug!("Hashed content ({} bytes) -> {}", content.len(), &hex[..16]);
35 Ok(hex)
36}
37
38/// Hash a file's content using SHA-256
39///
40/// Reads file from disk and computes hash.
41/// For cache management, prefer `hash_content` with rendered content.
42///
43/// # Arguments
44/// * `path` - Path to file to hash
45///
46/// # Returns
47/// Hex-encoded SHA-256 hash (64 characters)
48///
49/// # Errors
50/// - File read errors
51/// - Invalid file path
52pub fn hash_file(path: &Path) -> Result<String> {
53 let content = fs::read_to_string(path).map_err(|e| {
54 CleanroomError::io_error(format!(
55 "Failed to read file '{}' for hashing: {}",
56 path.display(),
57 e
58 ))
59 })?;
60
61 hash_content(&content)
62}
63
64/// Compute hash from multiple content parts (for composite hashing)
65///
66/// Useful for hashing configuration that depends on multiple files
67/// or sections. Parts are concatenated before hashing.
68///
69/// # Arguments
70/// * `parts` - Content parts to hash together
71///
72/// # Returns
73/// Hex-encoded SHA-256 hash of combined content
74pub fn hash_parts(parts: &[&str]) -> Result<String> {
75 let combined = parts.join("");
76 hash_content(&combined)
77}
78
79/// Verify if content matches expected hash
80///
81/// # Arguments
82/// * `content` - Content to verify
83/// * `expected_hash` - Expected hex-encoded SHA-256 hash
84///
85/// # Returns
86/// true if hashes match, false otherwise
87pub fn verify_hash(content: &str, expected_hash: &str) -> Result<bool> {
88 let actual_hash = hash_content(content)?;
89 Ok(actual_hash == expected_hash)
90}