Skip to main content

walrus_daemon/hook/system/memory/
storage.rs

1//! Storage abstraction for memory persistence.
2//!
3//! [`FsStorage`] wraps `std::fs` for production. [`MemStorage`] uses an
4//! in-memory `HashMap` for tests — no disk I/O, fully inspectable state.
5
6use anyhow::Result;
7use std::{
8    collections::HashMap,
9    path::{Path, PathBuf},
10    sync::RwLock,
11};
12
13/// Abstraction over filesystem operations used by the memory system.
14pub trait Storage: Send + Sync {
15    fn read(&self, path: &Path) -> Result<String>;
16    fn write(&self, path: &Path, content: &str) -> Result<()>;
17    fn delete(&self, path: &Path) -> Result<()>;
18    fn list(&self, dir: &Path) -> Result<Vec<PathBuf>>;
19    fn create_dir_all(&self, path: &Path) -> Result<()>;
20    fn exists(&self, path: &Path) -> bool;
21    fn rename(&self, from: &Path, to: &Path) -> Result<()>;
22}
23
24/// Production storage backed by `std::fs`.
25pub struct FsStorage;
26
27impl Storage for FsStorage {
28    fn read(&self, path: &Path) -> Result<String> {
29        Ok(std::fs::read_to_string(path)?)
30    }
31
32    fn write(&self, path: &Path, content: &str) -> Result<()> {
33        Ok(std::fs::write(path, content)?)
34    }
35
36    fn delete(&self, path: &Path) -> Result<()> {
37        Ok(std::fs::remove_file(path)?)
38    }
39
40    fn list(&self, dir: &Path) -> Result<Vec<PathBuf>> {
41        let mut paths = Vec::new();
42        for entry in std::fs::read_dir(dir)? {
43            paths.push(entry?.path());
44        }
45        Ok(paths)
46    }
47
48    fn create_dir_all(&self, path: &Path) -> Result<()> {
49        Ok(std::fs::create_dir_all(path)?)
50    }
51
52    fn exists(&self, path: &Path) -> bool {
53        path.exists()
54    }
55
56    fn rename(&self, from: &Path, to: &Path) -> Result<()> {
57        Ok(std::fs::rename(from, to)?)
58    }
59}
60
61/// In-memory storage for tests. No disk I/O.
62pub struct MemStorage {
63    files: RwLock<HashMap<PathBuf, String>>,
64}
65
66impl Default for MemStorage {
67    fn default() -> Self {
68        Self {
69            files: RwLock::new(HashMap::new()),
70        }
71    }
72}
73
74impl MemStorage {
75    pub fn new() -> Self {
76        Self::default()
77    }
78}
79
80impl Storage for MemStorage {
81    fn read(&self, path: &Path) -> Result<String> {
82        self.files
83            .read()
84            .unwrap()
85            .get(path)
86            .cloned()
87            .ok_or_else(|| anyhow::anyhow!("file not found: {}", path.display()))
88    }
89
90    fn write(&self, path: &Path, content: &str) -> Result<()> {
91        self.files
92            .write()
93            .unwrap()
94            .insert(path.to_path_buf(), content.to_owned());
95        Ok(())
96    }
97
98    fn delete(&self, path: &Path) -> Result<()> {
99        self.files
100            .write()
101            .unwrap()
102            .remove(path)
103            .ok_or_else(|| anyhow::anyhow!("file not found: {}", path.display()))?;
104        Ok(())
105    }
106
107    fn list(&self, dir: &Path) -> Result<Vec<PathBuf>> {
108        let files = self.files.read().unwrap();
109        Ok(files
110            .keys()
111            .filter(|p| p.parent() == Some(dir))
112            .cloned()
113            .collect())
114    }
115
116    fn create_dir_all(&self, _path: &Path) -> Result<()> {
117        Ok(())
118    }
119
120    fn exists(&self, path: &Path) -> bool {
121        self.files.read().unwrap().contains_key(path)
122    }
123
124    fn rename(&self, from: &Path, to: &Path) -> Result<()> {
125        let mut files = self.files.write().unwrap();
126        let content = files
127            .remove(from)
128            .ok_or_else(|| anyhow::anyhow!("file not found: {}", from.display()))?;
129        files.insert(to.to_path_buf(), content);
130        Ok(())
131    }
132}