Skip to main content

gobby_code/index/
hasher.rs

1//! Content hashing for incremental indexing.
2//! Ports logic from src/gobby/code_index/hasher.py.
3
4use std::path::Path;
5
6/// SHA-256 hash of entire file contents.
7pub fn file_content_hash(path: &Path) -> anyhow::Result<String> {
8    Ok(gobby_core::indexing::file_content_hash(path)?)
9}
10
11/// SHA-256 hash of a byte slice (symbol source).
12pub fn symbol_content_hash(source: &[u8], start: usize, end: usize) -> anyhow::Result<String> {
13    let slice = source.get(start..end).ok_or_else(|| {
14        anyhow::anyhow!(
15            "invalid byte range {}..{} for source len {}",
16            start,
17            end,
18            source.len()
19        )
20    })?;
21    Ok(gobby_core::indexing::content_hash(slice))
22}
23
24#[cfg(test)]
25mod tests {
26    use super::*;
27
28    #[test]
29    fn file_content_hash_delegates_to_gobby_core() {
30        let tmp = tempfile::NamedTempFile::new().expect("tempfile");
31        std::fs::write(tmp.path(), b"hash me\n").expect("write file");
32
33        let actual = file_content_hash(tmp.path()).expect("hash via wrapper");
34        let expected =
35            gobby_core::indexing::file_content_hash(tmp.path()).expect("hash via gobby-core");
36        assert_eq!(actual, expected);
37
38        let source = include_str!("hasher.rs");
39        let delegate = ["gobby_core", "::indexing::file_content_hash"].concat();
40        let local_buffer = format!("let mut buf = [0u8; {}]", 64 * 1024);
41        assert!(source.contains(&delegate));
42        assert!(!source.contains(&local_buffer));
43    }
44}