anyfs_backend/traits/
fs_dir.rs

1//! Directory operations for virtual filesystems.
2
3use std::path::Path;
4
5use crate::{DirEntry, FsError};
6
7/// Directory operations for a virtual filesystem.
8///
9/// # Thread Safety
10///
11/// All implementations must be `Send + Sync`. Methods use `&self` to allow
12/// concurrent access.
13///
14/// # Object Safety
15///
16/// This trait is object-safe and can be used as `dyn FsDir`.
17pub trait FsDir: Send + Sync {
18    /// List directory contents.
19    ///
20    /// Returns an iterator over directory entries. The outer `Result` indicates
21    /// whether the directory could be opened; each item's `Result` indicates
22    /// whether that specific entry could be read.
23    ///
24    /// # Errors
25    ///
26    /// - [`FsError::NotFound`] if the path does not exist
27    /// - [`FsError::NotADirectory`] if the path is not a directory
28    fn read_dir(&self, path: &Path) -> Result<ReadDirIter, FsError>;
29
30    /// Create a directory (parent must exist).
31    ///
32    /// # Errors
33    ///
34    /// - [`FsError::NotFound`] if parent directory does not exist
35    /// - [`FsError::AlreadyExists`] if the path already exists
36    fn create_dir(&self, path: &Path) -> Result<(), FsError>;
37
38    /// Create a directory and all parent directories.
39    ///
40    /// This is idempotent - succeeds if the directory already exists.
41    ///
42    /// # Errors
43    ///
44    /// - [`FsError::NotADirectory`] if a component of the path exists but is not a directory
45    fn create_dir_all(&self, path: &Path) -> Result<(), FsError>;
46
47    /// Remove an empty directory.
48    ///
49    /// # Errors
50    ///
51    /// - [`FsError::NotFound`] if the path does not exist
52    /// - [`FsError::NotADirectory`] if the path is not a directory
53    /// - [`FsError::DirectoryNotEmpty`] if the directory is not empty
54    fn remove_dir(&self, path: &Path) -> Result<(), FsError>;
55
56    /// Remove a directory and all its contents recursively.
57    ///
58    /// # Errors
59    ///
60    /// - [`FsError::NotFound`] if the path does not exist
61    /// - [`FsError::NotADirectory`] if the path is not a directory
62    fn remove_dir_all(&self, path: &Path) -> Result<(), FsError>;
63}
64
65/// Iterator over directory entries.
66///
67/// Wraps a boxed iterator for flexibility across different backends.
68///
69/// - Outer `Result` (from [`FsDir::read_dir`]) = "can I open this directory?"
70/// - Inner `Result` (per item) = "can I read this entry?"
71///
72/// # Example
73///
74/// ```rust
75/// use anyfs_backend::{Fs, FsError};
76/// use std::path::Path;
77///
78/// // Generic function that works with any Fs implementation
79/// fn list_files<B: Fs>(backend: &B) -> Result<Vec<String>, FsError> {
80///     let mut names = Vec::new();
81///     for entry in backend.read_dir(Path::new("/"))? {
82///         let entry = entry?;
83///         names.push(entry.name);
84///     }
85///     Ok(names)
86/// }
87/// ```
88pub struct ReadDirIter(Box<dyn Iterator<Item = Result<DirEntry, FsError>> + Send + 'static>);
89
90impl ReadDirIter {
91    /// Create from any compatible iterator.
92    pub fn new<I>(iter: I) -> Self
93    where
94        I: Iterator<Item = Result<DirEntry, FsError>> + Send + 'static,
95    {
96        Self(Box::new(iter))
97    }
98
99    /// Create from a pre-collected vector.
100    ///
101    /// Useful for middleware like Overlay that merges multiple directory listings.
102    pub fn from_vec(entries: Vec<Result<DirEntry, FsError>>) -> Self {
103        Self(Box::new(entries.into_iter()))
104    }
105
106    /// Collect all entries, short-circuiting on first error.
107    ///
108    /// This is a convenience method equivalent to `iter.collect::<Result<Vec<_>, _>>()`.
109    pub fn collect_all(self) -> Result<Vec<DirEntry>, FsError> {
110        self.collect()
111    }
112}
113
114impl Iterator for ReadDirIter {
115    type Item = Result<DirEntry, FsError>;
116
117    fn next(&mut self) -> Option<Self::Item> {
118        self.0.next()
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use crate::FileType;
126    use std::path::PathBuf;
127
128    #[test]
129    fn read_dir_iter_from_vec() {
130        let entries = vec![
131            Ok(DirEntry {
132                name: "a".into(),
133                path: PathBuf::from("/a"),
134                file_type: FileType::File,
135                size: 0,
136                inode: 1,
137            }),
138            Ok(DirEntry {
139                name: "b".into(),
140                path: PathBuf::from("/b"),
141                file_type: FileType::Directory,
142                size: 0,
143                inode: 2,
144            }),
145        ];
146        let iter = ReadDirIter::from_vec(entries);
147        let collected: Vec<_> = iter.collect();
148        assert_eq!(collected.len(), 2);
149    }
150
151    #[test]
152    fn read_dir_iter_collect_all_success() {
153        let entries = vec![Ok(DirEntry {
154            name: "a".into(),
155            path: PathBuf::from("/a"),
156            file_type: FileType::File,
157            size: 100,
158            inode: 1,
159        })];
160        let iter = ReadDirIter::from_vec(entries);
161        let result = iter.collect_all();
162        assert!(result.is_ok());
163        let entries = result.unwrap();
164        assert_eq!(entries.len(), 1);
165        assert_eq!(entries[0].name, "a");
166    }
167
168    #[test]
169    fn read_dir_iter_collect_all_error() {
170        let entries: Vec<Result<DirEntry, FsError>> = vec![
171            Ok(DirEntry {
172                name: "a".into(),
173                path: PathBuf::from("/a"),
174                file_type: FileType::File,
175                size: 0,
176                inode: 1,
177            }),
178            Err(FsError::PermissionDenied {
179                path: PathBuf::from("/b"),
180                operation: "read_dir",
181            }),
182        ];
183        let iter = ReadDirIter::from_vec(entries);
184        let result = iter.collect_all();
185        assert!(result.is_err());
186    }
187
188    #[test]
189    fn read_dir_iter_is_send() {
190        fn assert_send<T: Send>() {}
191        assert_send::<ReadDirIter>();
192    }
193}