Skip to main content

haz_cache/
reader.rs

1//! The [`CacheReader`] handle: a read-only, filesystem-bound view
2//! onto the cache tree at `<workspace-root>/.haz/cache` per
3//! [`crate::layout`].
4//!
5//! The bound is the read-only [`Filesystem`] trait, NOT
6//! `WritableFilesystem`. The compiler enforces what `AUX-018` /
7//! `AUX-020` declare in prose: no method reachable through a
8//! [`CacheReader`] can create, remove, rename, or modify any path
9//! under the cache root, because the trait it speaks against does
10//! not expose those operations.
11//!
12//! Read-only operations on cache state live in sibling modules:
13//! [`crate::lookup`] for `lookup` / `lookup_status` (`CACHE-014`),
14//! [`crate::info`] for `info` (`AUX-017..AUX-021`).
15
16use std::path::{Path, PathBuf};
17
18use haz_domain::settings::cache::HashAlgo;
19use haz_vfs::Filesystem;
20
21use crate::layout;
22
23/// Read-only handle to the cache tree rooted at
24/// `<workspace_root>/.haz/cache`, parameterised over a
25/// [`Filesystem`] backend.
26///
27/// Pairs with [`crate::CacheWriter`]: a writer wraps a reader and
28/// adds the mutating operations under the stricter
29/// [`haz_vfs::WritableFilesystem`] bound. Consumers that only need
30/// to consult the cache (e.g. `haz cache info` per `AUX-018`, the
31/// cache-introspection step of `haz why` per `AUX-014`) take a
32/// [`CacheReader`] and gain a compile-time guarantee that no write
33/// can sneak in.
34#[derive(Debug, Clone)]
35pub struct CacheReader<Fs: Filesystem> {
36    fs: Fs,
37    workspace_root: PathBuf,
38    root: PathBuf,
39    hash_algo: HashAlgo,
40}
41
42impl<Fs: Filesystem> CacheReader<Fs> {
43    /// Construct a [`CacheReader`] handle for the workspace rooted
44    /// at `workspace_root`. The cache root is derived as
45    /// `<workspace_root>/.haz/cache` via [`layout::cache_root`].
46    ///
47    /// `hash_algo` is the active hash function for this cache
48    /// session and is used by lookup to reject entries that were
49    /// written under a different algorithm (`CACHE-016` step 3).
50    pub fn new(fs: Fs, workspace_root: &Path, hash_algo: HashAlgo) -> Self {
51        Self {
52            fs,
53            workspace_root: workspace_root.to_path_buf(),
54            root: layout::cache_root(workspace_root),
55            hash_algo,
56        }
57    }
58
59    /// The absolute path to the workspace root. Restoration
60    /// (`CACHE-019`) needs this to map workspace-anchored
61    /// `/foo/bar` paths recorded in the manifest into real
62    /// filesystem paths.
63    #[must_use]
64    pub fn workspace_root(&self) -> &Path {
65        &self.workspace_root
66    }
67
68    /// The absolute path to the cache root,
69    /// `<workspace_root>/.haz/cache`.
70    #[must_use]
71    pub fn cache_root(&self) -> &Path {
72        &self.root
73    }
74
75    /// The hash function this [`CacheReader`] was constructed with.
76    #[must_use]
77    pub fn hash_algo(&self) -> HashAlgo {
78        self.hash_algo
79    }
80
81    /// Borrow the underlying filesystem handle.
82    #[must_use]
83    pub fn fs(&self) -> &Fs {
84        &self.fs
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use std::path::Path;
91
92    use haz_domain::settings::cache::HashAlgo;
93    use haz_vfs::Filesystem;
94    use haz_vfs_testing::MemFilesystem;
95
96    use super::CacheReader;
97
98    #[test]
99    fn workspace_root_is_preserved() {
100        let fs = MemFilesystem::new();
101        let cache = CacheReader::new(fs, Path::new("/ws"), HashAlgo::Blake3);
102        assert_eq!(cache.workspace_root(), Path::new("/ws"));
103    }
104
105    #[test]
106    fn cache_010_cache_root_is_workspace_dot_haz_cache() {
107        let fs = MemFilesystem::new();
108        let cache = CacheReader::new(fs, Path::new("/ws"), HashAlgo::Blake3);
109        assert_eq!(cache.cache_root(), Path::new("/ws/.haz/cache"));
110    }
111
112    #[test]
113    fn cache_002_hash_algo_is_preserved() {
114        let fs = MemFilesystem::new();
115        let cache = CacheReader::new(fs, Path::new("/ws"), HashAlgo::Sha256);
116        assert_eq!(cache.hash_algo(), HashAlgo::Sha256);
117    }
118
119    #[test]
120    fn fs_accessor_returns_the_handle_passed_in() {
121        let mut fs = MemFilesystem::new();
122        fs.add_dir("/ws").unwrap();
123        let cache = CacheReader::new(fs, Path::new("/ws"), HashAlgo::Blake3);
124        cache.fs().metadata(Path::new("/ws")).unwrap();
125    }
126}