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}