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}