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}