Skip to main content

crabtalk_runtime/memory/
storage.rs

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