virtual_filesystem/
roc_fs.rs

1use crate::file::{DirEntry, File, Metadata, OpenOptions};
2use crate::util::{not_found, not_supported};
3use crate::FileSystem;
4use itertools::Itertools;
5use std::io::ErrorKind;
6
7/// "Read-only collection" filesystem. Does not support writing, but supports reading from any
8/// of the layers. Differs from `OverlayFS` in that it only supports reading and is much less
9/// complex and doesn't need to write a `.whiteout` directory that can sometimes prove problematic.
10pub struct RocFS {
11    pub layers: Vec<Box<dyn FileSystem>>,
12}
13
14impl RocFS {
15    /// Creates a new read-only collection filesystem from layers. Layers will be traversed in order
16    /// of their appearance in the vector.
17    ///
18    /// # Argument
19    /// `layers`: The layers of the filesystem.
20    pub fn new(layers: Vec<Box<dyn FileSystem>>) -> Self {
21        Self { layers }
22    }
23
24    /// Checks each layer for a successful result.
25    ///
26    /// # Arguments
27    /// `f`: The filesystem method.  
28    /// `path`: The path invoked.  
29    fn for_each_layer<R, F: Fn(&dyn FileSystem, &str) -> crate::Result<R>>(
30        &self,
31        f: F,
32        path: &str,
33    ) -> crate::Result<R> {
34        for layer in &self.layers {
35            match f(&**layer, path) {
36                Ok(path) => return Ok(path),
37                Err(err) if err.kind() == ErrorKind::NotFound => continue,
38                Err(err) => return Err(err),
39            }
40        }
41
42        Err(not_found())
43    }
44}
45
46impl FileSystem for RocFS {
47    fn create_dir(&self, _path: &str) -> crate::Result<()> {
48        Err(not_supported())
49    }
50
51    fn metadata(&self, path: &str) -> crate::Result<Metadata> {
52        self.for_each_layer(|layer, path| layer.metadata(path), path)
53    }
54
55    fn open_file_options(&self, path: &str, options: &OpenOptions) -> crate::Result<Box<dyn File>> {
56        self.for_each_layer(|layer, path| layer.open_file_options(path, options), path)
57    }
58
59    fn read_dir(
60        &self,
61        path: &str,
62    ) -> crate::Result<Box<dyn Iterator<Item = crate::Result<DirEntry>>>> {
63        Ok(Box::new(
64            self.layers
65                .iter()
66                .map(|layer| layer.read_dir(path))
67                .filter(|res| {
68                    res.as_ref()
69                        .err()
70                        .map(|err| err.kind() != ErrorKind::NotFound)
71                        .unwrap_or(true)
72                })
73                .flatten_ok()
74                .try_collect::<_, Vec<_>, _>()?
75                .into_iter(),
76        ))
77    }
78
79    fn remove_dir(&self, _path: &str) -> crate::Result<()> {
80        Err(not_supported())
81    }
82
83    fn remove_file(&self, _path: &str) -> crate::Result<()> {
84        Err(not_supported())
85    }
86}
87
88#[cfg(test)]
89mod test {
90    use crate::file::Metadata;
91    use crate::physical_fs::PhysicalFS;
92    use crate::roc_fs::RocFS;
93    use crate::util::test::read_directory;
94    use crate::FileSystem;
95    use std::io::ErrorKind;
96
97    #[test]
98    fn read_dir_happy_case() {
99        let folder_a = PhysicalFS::new("test/folder_a");
100        let folder_b = PhysicalFS::new("test/folder_b");
101
102        let roc_fs = RocFS::new(vec![Box::new(folder_a), Box::new(folder_b)]);
103        let root = read_directory(&roc_fs, "/");
104
105        itertools::assert_equal(root.keys(), vec!["file_a", "file_b"]);
106        itertools::assert_equal(root.values(), vec![&Metadata::file(6), &Metadata::file(6)])
107    }
108
109    #[test]
110    fn read_dir_missing_folder() {
111        let folder_a = PhysicalFS::new("test/folder_a");
112        let folder_c = PhysicalFS::new("test/folder_c");
113
114        let roc_fs = RocFS::new(vec![Box::new(folder_a), Box::new(folder_c)]);
115        let root = read_directory(&roc_fs, "/");
116
117        itertools::assert_equal(root.keys(), vec!["file_a"]);
118        itertools::assert_equal(root.values(), vec![&Metadata::file(6)])
119    }
120
121    #[test]
122    fn read_dir_missing_folders() {
123        let folder_c = PhysicalFS::new("test/folder_c");
124        let folder_d = PhysicalFS::new("test/folder_d");
125
126        let roc_fs = RocFS::new(vec![Box::new(folder_c), Box::new(folder_d)]);
127        let root = read_directory(&roc_fs, "/");
128
129        assert!(root.is_empty());
130    }
131
132    #[test]
133    fn open_file_happy_case() {
134        let folder_a = PhysicalFS::new("test/folder_a");
135        let folder_b = PhysicalFS::new("test/folder_b");
136
137        let roc_fs = RocFS::new(vec![Box::new(folder_a), Box::new(folder_b)]);
138
139        let file_a = roc_fs
140            .open_file("/file_a")
141            .unwrap()
142            .read_into_string()
143            .unwrap();
144
145        let file_b = roc_fs
146            .open_file("/file_b")
147            .unwrap()
148            .read_into_string()
149            .unwrap();
150
151        assert_eq!(file_a, "file a");
152        assert_eq!(file_b, "file b");
153    }
154
155    #[test]
156    fn open_file_not_found() {
157        let roc_fs = RocFS::new(vec![]);
158
159        let open_res = roc_fs.open_file("abc");
160
161        assert!(open_res.is_err());
162        assert_eq!(open_res.err().unwrap().kind(), ErrorKind::NotFound);
163    }
164}