soar_utils/
hash.rs

1use std::path::Path;
2
3use crate::error::{HashError, HashResult};
4
5/// Calculates the checksum of a file.
6///
7/// This method reads the contents of a file and computes a checksum, which is returned as a
8/// hex-encoded string. The specific hashing algorithm depends on the implementation. The
9/// default implementation uses the `blake3` crate.
10///
11/// # Arguments
12///
13/// * `file_path` - The path to the file to calculate the checksum for.
14///
15/// # Errors
16///
17/// * [`HashError::ReadFailed`] if the file cannot be read.
18///
19/// # Example
20///
21/// ```no_run
22/// use soar_utils::error::HashResult;
23/// use soar_utils::hash::calculate_checksum;
24///
25/// fn main() -> HashResult<()> {
26///     let checksum = calculate_checksum("/path/to/file")?;
27///     println!("Checksum is {}", checksum);
28///     Ok(())
29/// }
30/// ```
31pub fn calculate_checksum<P: AsRef<Path>>(file_path: P) -> HashResult<String> {
32    let file_path = file_path.as_ref();
33    let mut hasher = blake3::Hasher::new();
34    hasher.update_mmap(file_path).map_err(|err| {
35        HashError::ReadFailed {
36            path: file_path.to_path_buf(),
37            source: err,
38        }
39    })?;
40    Ok(hasher.finalize().to_hex().to_string())
41}
42
43/// Verifies the checksum of a file against an expected value.
44///
45/// This method calculates the checksum of the given file and compares it case-insensitively
46/// against the `expected` checksum string.
47///
48/// # Arguments
49///
50/// * `file_path` - The path to the file to verify the checksum for.
51/// * `expected` - The expected checksum.
52///
53/// # Errors
54///
55/// * [`HashError::ReadFailed`] if the file cannot be read.
56///
57/// # Example
58///
59/// ```no_run
60/// use soar_utils::error::HashResult;
61/// use soar_utils::hash::verify_checksum;
62///
63/// fn main() -> HashResult<()> {
64///     let result = verify_checksum("file.dat", "1234567890abcdef")?;
65///     println!("Checksum matches: {}", result);
66///     Ok(())
67/// }
68/// ```
69pub fn verify_checksum<P: AsRef<Path>>(file_path: P, expected: &str) -> HashResult<bool> {
70    let file_path = file_path.as_ref();
71    let actual = calculate_checksum(file_path)?;
72    Ok(actual.eq_ignore_ascii_case(expected))
73}
74
75#[cfg(test)]
76mod tests {
77    use std::io::Write;
78
79    use tempfile::NamedTempFile;
80
81    use super::{calculate_checksum, verify_checksum};
82
83    #[test]
84    fn test_calculate_checksum() {
85        let mut file = NamedTempFile::new().unwrap();
86        file.write_all(b"hello world\n").unwrap();
87        let path = file.path();
88
89        let checksum = calculate_checksum(path).unwrap();
90        assert_eq!(
91            checksum,
92            "dc5a4edb8240b018124052c330270696f96771a63b45250a5c17d3000e823355"
93        );
94    }
95
96    #[test]
97    fn test_verify_checksum_valid() {
98        let mut file = NamedTempFile::new().unwrap();
99        file.write_all(b"hello world\n").unwrap();
100        let path = file.path();
101
102        let result = verify_checksum(
103            path,
104            "dc5a4edb8240b018124052c330270696f96771a63b45250a5c17d3000e823355",
105        )
106        .unwrap();
107        assert!(result);
108    }
109
110    #[test]
111    fn test_verify_checksum_invalid() {
112        let mut file = NamedTempFile::new().unwrap();
113        file.write_all(b"hello world").unwrap();
114        let path = file.path();
115
116        let result = verify_checksum(path, "invalid-checksum").unwrap();
117        assert!(!result);
118    }
119
120    #[test]
121    fn test_calculate_checksum_file_not_found() {
122        let result = calculate_checksum("/path/to/nonexistent/file");
123        assert!(result.is_err());
124    }
125
126    #[test]
127    fn test_verify_checksum_file_not_found() {
128        let result = verify_checksum("/path/to/nonexistent/file", "any-checksum");
129        assert!(result.is_err());
130    }
131
132    #[test]
133    fn test_calculate_checksum_on_directory() {
134        let dir = tempfile::tempdir().unwrap();
135        let result = calculate_checksum(dir.path());
136        assert!(result.is_err());
137    }
138}