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}