Skip to main content

haz_cache/
cache.rs

1//! The [`Cache`] handle: a filesystem-bound view onto the cache
2//! tree at `<workspace-root>/.haz/cache` per [`crate::layout`].
3//!
4//! The struct is the entry point for every cache operation
5//! (lookup, store, restore, invalidation). It owns a
6//! [`WritableFilesystem`] handle, the absolute cache root, the
7//! workspace root (needed by restoration to map workspace-anchored
8//! paths to real filesystem paths), and the active [`HashAlgo`]
9//! so that callers do not need to pass these on every call.
10//!
11//! Construction is split from the per-operation methods in
12//! sibling modules ([`crate::lookup`], [`crate::store`],
13//! [`crate::restore`]) so each can be reviewed in isolation.
14
15use std::path::{Path, PathBuf};
16
17use haz_domain::settings::cache::HashAlgo;
18use haz_vfs::WritableFilesystem;
19
20use crate::layout;
21
22/// Handle to the cache tree rooted at
23/// `<workspace_root>/.haz/cache`, parameterised over a
24/// [`WritableFilesystem`] backend.
25///
26/// The bound is [`WritableFilesystem`] (not the read-only
27/// [`Filesystem`](haz_vfs::Filesystem)) because all per-operation
28/// methods other than lookup mutate the tree. Carrying the
29/// stricter bound on the struct, rather than narrowing it per
30/// method, keeps the public signatures uniform.
31#[derive(Debug, Clone)]
32pub struct Cache<Fs: WritableFilesystem> {
33    fs: Fs,
34    workspace_root: PathBuf,
35    root: PathBuf,
36    hash_algo: HashAlgo,
37}
38
39impl<Fs: WritableFilesystem> Cache<Fs> {
40    /// Construct a [`Cache`] handle for the workspace rooted at
41    /// `workspace_root`. The cache root is derived as
42    /// `<workspace_root>/.haz/cache` via [`layout::cache_root`].
43    ///
44    /// `hash_algo` is the active hash function for this cache
45    /// session and is used by lookup to reject entries that were
46    /// written under a different algorithm (`CACHE-016` step 3).
47    pub fn new(fs: Fs, workspace_root: &Path, hash_algo: HashAlgo) -> Self {
48        Self {
49            fs,
50            workspace_root: workspace_root.to_path_buf(),
51            root: layout::cache_root(workspace_root),
52            hash_algo,
53        }
54    }
55
56    /// The absolute path to the workspace root. Restoration
57    /// (`CACHE-019`) needs this to map workspace-anchored
58    /// `/foo/bar` paths recorded in the manifest into real
59    /// filesystem paths.
60    #[must_use]
61    pub fn workspace_root(&self) -> &Path {
62        &self.workspace_root
63    }
64
65    /// The absolute path to the cache root,
66    /// `<workspace_root>/.haz/cache`.
67    #[must_use]
68    pub fn cache_root(&self) -> &Path {
69        &self.root
70    }
71
72    /// The hash function this [`Cache`] was constructed with.
73    #[must_use]
74    pub fn hash_algo(&self) -> HashAlgo {
75        self.hash_algo
76    }
77
78    /// Borrow the underlying filesystem handle. Useful for tests
79    /// and for callers that need to inspect cache-adjacent paths
80    /// through the same backend.
81    #[must_use]
82    pub fn fs(&self) -> &Fs {
83        &self.fs
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use std::path::Path;
90
91    use haz_domain::settings::cache::HashAlgo;
92    use haz_vfs::{Filesystem, MemFilesystem};
93
94    use crate::cache::Cache;
95
96    #[test]
97    fn workspace_root_is_preserved() {
98        let fs = MemFilesystem::new();
99        let cache = Cache::new(fs, Path::new("/ws"), HashAlgo::Blake3);
100        assert_eq!(cache.workspace_root(), Path::new("/ws"));
101    }
102
103    #[test]
104    fn cache_010_cache_root_is_workspace_dot_haz_cache() {
105        let fs = MemFilesystem::new();
106        let cache = Cache::new(fs, Path::new("/ws"), HashAlgo::Blake3);
107        assert_eq!(cache.cache_root(), Path::new("/ws/.haz/cache"));
108    }
109
110    #[test]
111    fn cache_002_hash_algo_is_preserved() {
112        let fs = MemFilesystem::new();
113        let cache = Cache::new(fs, Path::new("/ws"), HashAlgo::Sha256);
114        assert_eq!(cache.hash_algo(), HashAlgo::Sha256);
115    }
116
117    #[test]
118    fn fs_accessor_returns_the_handle_passed_in() {
119        let mut fs = MemFilesystem::new();
120        fs.add_dir("/ws").unwrap();
121        let cache = Cache::new(fs, Path::new("/ws"), HashAlgo::Blake3);
122        // The handle is reachable; metadata on a known directory
123        // succeeds, demonstrating it is the same handle.
124        cache.fs().metadata(Path::new("/ws")).unwrap();
125    }
126}