Skip to main content

haz_cache/
writer.rs

1//! The [`CacheWriter`] handle: a writable, filesystem-bound view
2//! onto the cache tree at `<workspace-root>/.haz/cache` per
3//! [`crate::layout`].
4//!
5//! Pairs with [`crate::CacheReader`]. The writer holds a reader as
6//! its inner state; lookup, schema introspection, and any other
7//! read-only operation are reached via [`CacheWriter::reader`]
8//! rather than being re-exposed here. The split keeps the read-only
9//! contract of `AUX-018` / `AUX-020` enforceable at the type level:
10//! a future commit attempting to publish a write through a
11//! read-only path no longer compiles, because the [`CacheReader`]
12//! it would have to flow through does not implement
13//! [`haz_vfs::WritableFilesystem`].
14//!
15//! Write-side operations live in sibling modules:
16//! [`crate::store`] for [`CacheWriter::store`] (`CACHE-017`),
17//! [`crate::restore`] for [`CacheWriter::restore`] (`CACHE-019`),
18//! [`crate::clean`] for [`CacheWriter::clear`] / [`CacheWriter::clean`]
19//! (`CACHE-021`, `CACHE-022`, `AUX-022..AUX-027`).
20
21use std::path::Path;
22
23use haz_domain::settings::cache::HashAlgo;
24use haz_vfs::WritableFilesystem;
25
26use crate::reader::CacheReader;
27
28/// Writable handle to the cache tree rooted at
29/// `<workspace_root>/.haz/cache`, parameterised over a
30/// [`WritableFilesystem`] backend.
31///
32/// Wraps a [`CacheReader`]; reads against the same cache flow
33/// through [`Self::reader`] so the read-only contract of the
34/// reader's surface is preserved.
35#[derive(Debug, Clone)]
36pub struct CacheWriter<Fs: WritableFilesystem> {
37    reader: CacheReader<Fs>,
38}
39
40impl<Fs: WritableFilesystem> CacheWriter<Fs> {
41    /// Construct a [`CacheWriter`] handle for the workspace rooted
42    /// at `workspace_root`. The cache root is derived as
43    /// `<workspace_root>/.haz/cache` via
44    /// [`crate::layout::cache_root`].
45    ///
46    /// `hash_algo` is the active hash function for this cache
47    /// session and is used by lookup to reject entries that were
48    /// written under a different algorithm (`CACHE-016` step 3).
49    pub fn new(fs: Fs, workspace_root: &Path, hash_algo: HashAlgo) -> Self {
50        Self {
51            reader: CacheReader::new(fs, workspace_root, hash_algo),
52        }
53    }
54
55    /// Borrow the inner [`CacheReader`] for read-only operations
56    /// (lookup, manifest introspection, info).
57    #[must_use]
58    pub fn reader(&self) -> &CacheReader<Fs> {
59        &self.reader
60    }
61
62    /// The absolute path to the workspace root. Restoration
63    /// (`CACHE-019`) needs this to map workspace-anchored
64    /// `/foo/bar` paths recorded in the manifest into real
65    /// filesystem paths.
66    #[must_use]
67    pub fn workspace_root(&self) -> &Path {
68        self.reader.workspace_root()
69    }
70
71    /// The absolute path to the cache root,
72    /// `<workspace_root>/.haz/cache`.
73    #[must_use]
74    pub fn cache_root(&self) -> &Path {
75        self.reader.cache_root()
76    }
77
78    /// The hash function this [`CacheWriter`] was constructed with.
79    #[must_use]
80    pub fn hash_algo(&self) -> HashAlgo {
81        self.reader.hash_algo()
82    }
83
84    /// Borrow the underlying filesystem handle.
85    #[must_use]
86    pub fn fs(&self) -> &Fs {
87        self.reader.fs()
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use std::path::Path;
94
95    use haz_domain::settings::cache::HashAlgo;
96    use haz_vfs::Filesystem;
97    use haz_vfs_testing::MemFilesystem;
98
99    use super::CacheWriter;
100
101    #[test]
102    fn workspace_root_is_preserved() {
103        let fs = MemFilesystem::new();
104        let cache = CacheWriter::new(fs, Path::new("/ws"), HashAlgo::Blake3);
105        assert_eq!(cache.workspace_root(), Path::new("/ws"));
106    }
107
108    #[test]
109    fn cache_010_cache_root_is_workspace_dot_haz_cache() {
110        let fs = MemFilesystem::new();
111        let cache = CacheWriter::new(fs, Path::new("/ws"), HashAlgo::Blake3);
112        assert_eq!(cache.cache_root(), Path::new("/ws/.haz/cache"));
113    }
114
115    #[test]
116    fn cache_002_hash_algo_is_preserved() {
117        let fs = MemFilesystem::new();
118        let cache = CacheWriter::new(fs, Path::new("/ws"), HashAlgo::Sha256);
119        assert_eq!(cache.hash_algo(), HashAlgo::Sha256);
120    }
121
122    #[test]
123    fn fs_accessor_returns_the_handle_passed_in() {
124        let mut fs = MemFilesystem::new();
125        fs.add_dir("/ws").unwrap();
126        let cache = CacheWriter::new(fs, Path::new("/ws"), HashAlgo::Blake3);
127        cache.fs().metadata(Path::new("/ws")).unwrap();
128    }
129
130    #[test]
131    fn reader_accessor_borrows_an_inner_reader_with_the_same_state() {
132        let fs = MemFilesystem::new();
133        let cache = CacheWriter::new(fs, Path::new("/ws"), HashAlgo::Blake3);
134        let reader = cache.reader();
135        assert_eq!(reader.workspace_root(), Path::new("/ws"));
136        assert_eq!(reader.cache_root(), Path::new("/ws/.haz/cache"));
137        assert_eq!(reader.hash_algo(), HashAlgo::Blake3);
138    }
139}