1use std::cell::RefCell;
13use std::collections::{BTreeMap, BTreeSet};
14use std::path::{Path, PathBuf};
15
16use crate::error::SessionError;
17
18pub trait Fs {
29 fn create_dir_all(&self, path: &Path) -> Result<(), SessionError>;
31
32 fn exists(&self, path: &Path) -> bool;
34
35 fn read(&self, path: &Path) -> Result<Vec<u8>, SessionError>;
37
38 fn write(&self, path: &Path, data: &[u8]) -> Result<(), SessionError>;
42
43 fn append(&self, path: &Path, data: &[u8]) -> Result<(), SessionError>;
46
47 fn read_dir(&self, path: &Path) -> Result<Vec<PathBuf>, SessionError>;
50
51 fn rename(&self, from: &Path, to: &Path) -> Result<(), SessionError>;
53
54 fn remove(&self, path: &Path) -> Result<(), SessionError>;
60}
61
62pub struct OsFs;
66
67impl Fs for OsFs {
68 fn create_dir_all(&self, path: &Path) -> Result<(), SessionError> {
69 std::fs::create_dir_all(path)?;
70 Ok(())
71 }
72
73 fn exists(&self, path: &Path) -> bool {
74 path.exists()
75 }
76
77 fn read(&self, path: &Path) -> Result<Vec<u8>, SessionError> {
78 let bytes = std::fs::read(path)?;
79 Ok(bytes)
80 }
81
82 fn write(&self, path: &Path, data: &[u8]) -> Result<(), SessionError> {
83 std::fs::write(path, data)?;
84 Ok(())
85 }
86
87 fn append(&self, path: &Path, data: &[u8]) -> Result<(), SessionError> {
88 use std::io::Write as _;
89 let mut f = std::fs::OpenOptions::new()
90 .create(true)
91 .append(true)
92 .open(path)?;
93 f.write_all(data)?;
94 Ok(())
95 }
96
97 fn read_dir(&self, path: &Path) -> Result<Vec<PathBuf>, SessionError> {
98 let mut entries: Vec<PathBuf> = std::fs::read_dir(path)?
99 .map(|res| res.map(|e| e.path()))
100 .collect::<Result<Vec<_>, _>>()?;
101 entries.sort();
102 Ok(entries)
103 }
104
105 fn rename(&self, from: &Path, to: &Path) -> Result<(), SessionError> {
106 std::fs::rename(from, to)?;
107 Ok(())
108 }
109
110 fn remove(&self, path: &Path) -> Result<(), SessionError> {
111 match std::fs::remove_file(path) {
112 Ok(()) => Ok(()),
113 Err(e) if e.kind() == std::io::ErrorKind::IsADirectory => {
117 std::fs::remove_dir_all(path)?;
118 Ok(())
119 }
120 Err(e) => Err(e.into()),
121 }
122 }
123}
124
125pub struct MemFs {
140 inner: RefCell<MemFsInner>,
141}
142
143struct MemFsInner {
144 files: BTreeMap<PathBuf, Vec<u8>>,
145 dirs: BTreeSet<PathBuf>,
146}
147
148impl MemFs {
149 pub fn new() -> Self {
150 Self {
151 inner: RefCell::new(MemFsInner {
152 files: BTreeMap::new(),
153 dirs: BTreeSet::new(),
154 }),
155 }
156 }
157}
158
159impl Default for MemFs {
160 fn default() -> Self {
161 Self::new()
162 }
163}
164
165impl Fs for MemFs {
166 fn create_dir_all(&self, path: &Path) -> Result<(), SessionError> {
167 let mut inner = self.inner.borrow_mut();
168 let mut current = path.to_path_buf();
170 loop {
171 let next = current
172 .parent()
173 .filter(|&p| p != current.as_path())
174 .map(|p| p.to_path_buf());
175 inner.dirs.insert(current);
176 match next {
177 Some(p) => current = p,
178 None => break,
179 }
180 }
181 Ok(())
182 }
183
184 fn exists(&self, path: &Path) -> bool {
185 let inner = self.inner.borrow();
186 inner.files.contains_key(path) || inner.dirs.contains(path)
187 }
188
189 fn read(&self, path: &Path) -> Result<Vec<u8>, SessionError> {
190 let inner = self.inner.borrow();
191 inner
192 .files
193 .get(path)
194 .cloned()
195 .ok_or_else(|| SessionError::new(format!("file not found: {}", path.display())))
196 }
197
198 fn write(&self, path: &Path, data: &[u8]) -> Result<(), SessionError> {
199 let mut inner = self.inner.borrow_mut();
200 let parent = path
202 .parent()
203 .ok_or_else(|| SessionError::new("path has no parent directory"))?;
204 if !inner.dirs.contains(parent) {
205 return Err(SessionError::new(format!(
206 "parent directory does not exist: {}",
207 parent.display()
208 )));
209 }
210 inner.files.insert(path.to_path_buf(), data.to_vec());
211 Ok(())
212 }
213
214 fn append(&self, path: &Path, data: &[u8]) -> Result<(), SessionError> {
215 let mut inner = self.inner.borrow_mut();
216 let parent = path
217 .parent()
218 .ok_or_else(|| SessionError::new("path has no parent directory"))?;
219 if !inner.dirs.contains(parent) {
220 return Err(SessionError::new(format!(
221 "parent directory does not exist: {}",
222 parent.display()
223 )));
224 }
225 inner
226 .files
227 .entry(path.to_path_buf())
228 .or_default()
229 .extend_from_slice(data);
230 Ok(())
231 }
232
233 fn read_dir(&self, path: &Path) -> Result<Vec<PathBuf>, SessionError> {
234 let inner = self.inner.borrow();
235 if !inner.dirs.contains(path) {
236 return Err(SessionError::new(format!(
237 "directory not found: {}",
238 path.display()
239 )));
240 }
241 let mut children: BTreeSet<PathBuf> = BTreeSet::new();
243 for file_path in inner.files.keys() {
244 if file_path.parent() == Some(path) {
245 children.insert(file_path.clone());
246 }
247 }
248 for dir_path in inner.dirs.iter() {
249 if dir_path.parent() == Some(path) && dir_path != path {
250 children.insert(dir_path.clone());
251 }
252 }
253 Ok(children.into_iter().collect())
255 }
256
257 fn rename(&self, from: &Path, to: &Path) -> Result<(), SessionError> {
258 let mut inner = self.inner.borrow_mut();
259 let data = inner
260 .files
261 .remove(from)
262 .ok_or_else(|| SessionError::new(format!("file not found: {}", from.display())))?;
263 inner.files.insert(to.to_path_buf(), data);
264 Ok(())
265 }
266
267 fn remove(&self, path: &Path) -> Result<(), SessionError> {
268 let mut inner = self.inner.borrow_mut();
269 if inner.files.remove(path).is_some() {
270 return Ok(());
271 }
272 if inner.dirs.contains(path) {
273 inner.files.retain(|k, _| !k.starts_with(path));
275 inner.dirs.retain(|k| !k.starts_with(path));
276 return Ok(());
277 }
278 Err(SessionError::new(format!(
279 "path not found: {}",
280 path.display()
281 )))
282 }
283}
284
285#[cfg(test)]
288mod tests {
289 use super::*;
290
291 #[test]
294 fn memfs_write_read_roundtrip() {
295 let fs = MemFs::new();
296 let dir = PathBuf::from("/data");
297 let file = dir.join("hello.txt");
298 fs.create_dir_all(&dir).unwrap();
299 fs.write(&file, b"hello world").unwrap();
300 assert_eq!(fs.read(&file).unwrap(), b"hello world");
301 }
302
303 #[test]
304 fn memfs_write_without_parent_errors() {
305 let fs = MemFs::new();
306 let file = PathBuf::from("/missing/dir/file.txt");
307 let result = fs.write(&file, b"data");
308 assert!(result.is_err(), "expected error when parent dir is absent");
309 }
310
311 #[test]
312 fn memfs_read_dir_sorted() {
313 let fs = MemFs::new();
314 let dir = PathBuf::from("/root");
315 fs.create_dir_all(&dir).unwrap();
316 fs.write(&dir.join("c.txt"), b"c").unwrap();
318 fs.write(&dir.join("a.txt"), b"a").unwrap();
319 fs.write(&dir.join("b.txt"), b"b").unwrap();
320 let entries = fs.read_dir(&dir).unwrap();
321 assert_eq!(
322 entries,
323 vec![dir.join("a.txt"), dir.join("b.txt"), dir.join("c.txt")]
324 );
325 }
326
327 #[test]
328 fn memfs_read_dir_missing_errors() {
329 let fs = MemFs::new();
330 let result = fs.read_dir(Path::new("/nonexistent"));
331 assert!(result.is_err());
332 }
333
334 #[test]
335 fn memfs_rename_moves_file() {
336 let fs = MemFs::new();
337 let dir = PathBuf::from("/d");
338 fs.create_dir_all(&dir).unwrap();
339 let from = dir.join("old.txt");
340 let to = dir.join("new.txt");
341 fs.write(&from, b"payload").unwrap();
342 fs.rename(&from, &to).unwrap();
343 assert!(!fs.exists(&from));
344 assert!(fs.exists(&to));
345 assert_eq!(fs.read(&to).unwrap(), b"payload");
346 }
347
348 #[test]
349 fn memfs_rename_missing_errors() {
350 let fs = MemFs::new();
351 let result = fs.rename(Path::new("/a"), Path::new("/b"));
352 assert!(result.is_err());
353 }
354
355 #[test]
356 fn memfs_remove_file() {
357 let fs = MemFs::new();
358 let dir = PathBuf::from("/r");
359 fs.create_dir_all(&dir).unwrap();
360 let file = dir.join("f.txt");
361 fs.write(&file, b"x").unwrap();
362 fs.remove(&file).unwrap();
363 assert!(!fs.exists(&file));
364 }
365
366 #[test]
367 fn memfs_remove_dir_subtree() {
368 let fs = MemFs::new();
369 let parent = PathBuf::from("/p");
370 let child_dir = parent.join("sub");
371 fs.create_dir_all(&child_dir).unwrap();
372 fs.write(&child_dir.join("f.txt"), b"data").unwrap();
373 fs.remove(&parent).unwrap();
374 assert!(!fs.exists(&parent));
375 assert!(!fs.exists(&child_dir));
376 assert!(!fs.exists(&child_dir.join("f.txt")));
377 }
378
379 #[test]
380 fn memfs_append_creates_then_accumulates() {
381 let fs = MemFs::new();
382 let dir = PathBuf::from("/data");
383 let file = dir.join("journal.txt");
384 fs.create_dir_all(&dir).unwrap();
385 fs.append(&file, b"hello ").unwrap();
386 fs.append(&file, b"world").unwrap();
387 assert_eq!(fs.read(&file).unwrap(), b"hello world");
388 }
389
390 #[test]
391 fn memfs_append_without_parent_errors() {
392 let fs = MemFs::new();
393 let file = PathBuf::from("/missing/dir/journal.txt");
394 let result = fs.append(&file, b"data");
395 assert!(result.is_err(), "expected error when parent dir is absent");
396 }
397
398 #[test]
399 fn memfs_append_then_read_roundtrip() {
400 let fs = MemFs::new();
401 let dir = PathBuf::from("/logs");
402 let file = dir.join("log.txt");
403 fs.create_dir_all(&dir).unwrap();
404 fs.append(&file, b"line one\n").unwrap();
405 fs.append(&file, b"line two\n").unwrap();
406 assert_eq!(fs.read(&file).unwrap(), b"line one\nline two\n");
407 }
408
409 #[test]
410 fn memfs_exists_reflects_state() {
411 let fs = MemFs::new();
412 let dir = PathBuf::from("/e");
413 let file = dir.join("x.txt");
414 assert!(!fs.exists(&dir));
415 fs.create_dir_all(&dir).unwrap();
416 assert!(fs.exists(&dir));
417 assert!(!fs.exists(&file));
418 fs.write(&file, b"").unwrap();
419 assert!(fs.exists(&file));
420 }
421
422 #[test]
425 fn osfs_write_read_read_dir_roundtrip() {
426 let tmp = tempfile::tempdir().expect("tempdir");
427 let fs = OsFs;
428 let dir = tmp.path().join("subdir");
429 fs.create_dir_all(&dir).unwrap();
430 let file_a = dir.join("a.txt");
431 let file_b = dir.join("b.txt");
432 fs.write(&file_a, b"aaa").unwrap();
433 fs.write(&file_b, b"bbb").unwrap();
434 assert_eq!(fs.read(&file_a).unwrap(), b"aaa");
435 assert_eq!(fs.read(&file_b).unwrap(), b"bbb");
436 let entries = fs.read_dir(&dir).unwrap();
437 assert_eq!(entries, vec![file_a.clone(), file_b.clone()]);
438 }
439}