1use anyhow::anyhow;
4use camino::{Utf8Path, Utf8PathBuf};
5use indigo_kernel::IndigoBuildHasher;
6use std::{
7 collections::HashMap,
8 fs,
9 sync::{Arc, Mutex},
10};
11
12pub trait Fs {
13 fn read(&self, path: &Utf8Path) -> anyhow::Result<Vec<u8>>;
14
15 fn write(&self, path: &Utf8Path, bytes: &[u8]) -> anyhow::Result<()>;
16
17 fn exists(&self, path: &Utf8Path) -> anyhow::Result<bool>;
18}
19
20const _ASSERT_FS_IS_DYN_COMPATIBLE: Option<&dyn Fs> = None;
21
22impl<T: Fs + ?Sized> Fs for Arc<T> {
23 fn read(&self, path: &Utf8Path) -> anyhow::Result<Vec<u8>> {
24 T::read(self, path)
25 }
26 fn write(&self, path: &Utf8Path, bytes: &[u8]) -> anyhow::Result<()> {
27 T::write(self, path, bytes)
28 }
29 fn exists(&self, path: &Utf8Path) -> anyhow::Result<bool> {
30 T::exists(self, path)
31 }
32}
33
34pub struct NoFs;
37
38impl Fs for NoFs {
39 fn read(&self, _path: &Utf8Path) -> anyhow::Result<Vec<u8>> {
40 panic!("No filesystem implementation configured");
41 }
42
43 fn write(&self, _path: &Utf8Path, _bytes: &[u8]) -> anyhow::Result<()> {
44 panic!("No filesystem implementation configured");
45 }
46
47 fn exists(&self, _path: &Utf8Path) -> anyhow::Result<bool> {
48 panic!("No filesystem implementation configured");
49 }
50}
51
52pub struct RealFs;
54
55impl Fs for RealFs {
56 fn read(&self, path: &Utf8Path) -> anyhow::Result<Vec<u8>> {
57 Ok(fs::read(path)?)
58 }
59
60 fn write(&self, path: &Utf8Path, bytes: &[u8]) -> anyhow::Result<()> {
61 Ok(fs::write(path, bytes)?)
62 }
63
64 fn exists(&self, path: &Utf8Path) -> anyhow::Result<bool> {
65 Ok(fs::exists(path)?)
66 }
67}
68
69#[derive(Default)]
72pub struct TestFs {
73 pub files: Arc<Mutex<HashMap<Utf8PathBuf, Vec<u8>, IndigoBuildHasher>>>,
74}
75
76impl Fs for TestFs {
77 fn read(&self, path: &Utf8Path) -> anyhow::Result<Vec<u8>> {
78 let files = self.files.lock().unwrap();
79 if let Some(bytes) = files.get(path) {
80 Ok(bytes.clone())
81 } else {
82 Err(anyhow!("File not found: `{path}`"))
83 }
84 }
85
86 fn write(&self, path: &Utf8Path, bytes: &[u8]) -> anyhow::Result<()> {
87 let mut files = self.files.lock().unwrap();
88 files.insert(path.to_path_buf(), bytes.to_vec());
89 Ok(())
90 }
91
92 fn exists(&self, path: &Utf8Path) -> anyhow::Result<bool> {
93 let files = self.files.lock().unwrap();
94 let exists = files.contains_key(path);
95 Ok(exists)
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn test() -> anyhow::Result<()> {
105 let fs = TestFs::default();
106 fs.write("foo.rs".into(), b"fn foo() {}")?;
107 fs.write("bar.rs".into(), b"fn bar() {}")?;
108 assert!(fs.exists("foo.rs".into())?);
109 assert_eq!(&fs.read("foo.rs".into())?, b"fn foo() {}");
110 assert_eq!(&fs.read("bar.rs".into())?, b"fn bar() {}");
111 assert!(fs.read("baz.rs".into()).is_err());
112 assert!(!fs.exists("baz.rs".into())?);
113 fs.write("foo.rs".into(), b"fn foo() { panic!() }")?;
114 assert_eq!(&fs.read("foo.rs".into())?, b"fn foo() { panic!() }");
115 Ok(())
116 }
117}