1use std::sync::Arc;
2
3mod error;
4use error::FsResultToStorageFsResult;
5pub use error::{BatchFSError, BatchFSResult, FSError, FSOperation, FSResult};
6use rspack_fs::{FileMetadata, IntermediateFileSystem, ReadStream, WriteStream};
7use rspack_paths::{Utf8Path, Utf8PathBuf};
8use rustc_hash::FxHashSet as HashSet;
9
10#[async_trait::async_trait]
11pub trait FileSystem: std::fmt::Debug + Sync + Send {
12 async fn exists(&self, path: &Utf8Path) -> FSResult<bool>;
13 async fn remove_dir(&self, path: &Utf8Path) -> FSResult<()>;
14 async fn ensure_dir(&self, path: &Utf8Path) -> FSResult<()>;
15 async fn write_file(&self, path: &Utf8Path) -> FSResult<Writer>;
16 async fn read_file(&self, path: &Utf8Path) -> FSResult<Reader>;
17 async fn read_dir(&self, path: &Utf8Path) -> FSResult<HashSet<String>>;
18 async fn metadata(&self, path: &Utf8Path) -> FSResult<FileMetadata>;
19 async fn remove_file(&self, path: &Utf8Path) -> FSResult<()>;
20 async fn move_file(&self, from: &Utf8Path, to: &Utf8Path) -> FSResult<()>;
21}
22
23#[derive(Debug)]
24pub struct Writer {
25 path: Utf8PathBuf,
26 stream: Box<dyn WriteStream>,
27}
28
29impl Writer {
30 pub async fn write_line(&mut self, line: &str) -> FSResult<()> {
31 self
32 .stream
33 .write_line(line)
34 .await
35 .to_storage_fs_result(&self.path, FSOperation::Write)
36 }
37 pub async fn write(&mut self, buf: &[u8]) -> FSResult<usize> {
38 self
39 .stream
40 .write(buf)
41 .await
42 .to_storage_fs_result(&self.path, FSOperation::Write)
43 }
44 pub async fn write_all(&mut self, buf: &[u8]) -> FSResult<()> {
45 self
46 .stream
47 .write_all(buf)
48 .await
49 .to_storage_fs_result(&self.path, FSOperation::Write)
50 }
51 pub async fn flush(&mut self) -> FSResult<()> {
52 self
53 .stream
54 .flush()
55 .await
56 .to_storage_fs_result(&self.path, FSOperation::Write)
57 }
58 pub async fn close(&mut self) -> FSResult<()> {
59 self
60 .stream
61 .close()
62 .await
63 .to_storage_fs_result(&self.path, FSOperation::Write)
64 }
65}
66
67#[derive(Debug)]
68pub struct Reader {
69 path: Utf8PathBuf,
70 stream: Box<dyn ReadStream>,
71}
72
73impl Reader {
74 pub async fn read_line(&mut self) -> FSResult<String> {
75 self
76 .stream
77 .read_line()
78 .await
79 .to_storage_fs_result(&self.path, FSOperation::Read)
80 }
81 pub async fn read(&mut self, length: usize) -> FSResult<Vec<u8>> {
82 self
83 .stream
84 .read(length)
85 .await
86 .to_storage_fs_result(&self.path, FSOperation::Read)
87 }
88 pub async fn read_until(&mut self, byte: u8) -> FSResult<Vec<u8>> {
89 self
90 .stream
91 .read_until(byte)
92 .await
93 .to_storage_fs_result(&self.path, FSOperation::Read)
94 }
95 pub async fn read_to_end(&mut self) -> FSResult<Vec<u8>> {
96 self
97 .stream
98 .read_to_end()
99 .await
100 .to_storage_fs_result(&self.path, FSOperation::Read)
101 }
102 pub async fn skip(&mut self, offset: usize) -> FSResult<()> {
103 self
104 .stream
105 .skip(offset)
106 .await
107 .to_storage_fs_result(&self.path, FSOperation::Read)
108 }
109 pub async fn close(&mut self) -> FSResult<()> {
110 self
111 .stream
112 .close()
113 .await
114 .to_storage_fs_result(&self.path, FSOperation::Read)
115 }
116}
117
118#[derive(Debug)]
119pub struct BridgeFileSystem(pub Arc<dyn IntermediateFileSystem>);
120
121#[async_trait::async_trait]
122impl FileSystem for BridgeFileSystem {
123 async fn exists(&self, path: &Utf8Path) -> FSResult<bool> {
124 match self.metadata(path).await {
125 Ok(_) => Ok(true),
126 Err(e) => {
127 if e.is_not_found() {
128 Ok(false)
129 } else {
130 Err(e)
131 }
132 }
133 }
134 }
135
136 async fn remove_dir(&self, path: &Utf8Path) -> FSResult<()> {
137 if self.exists(path).await? {
138 self
139 .0
140 .remove_dir_all(path)
141 .await
142 .to_storage_fs_result(path, FSOperation::Remove)?;
143 }
144 Ok(())
145 }
146
147 async fn ensure_dir(&self, path: &Utf8Path) -> FSResult<()> {
148 self
149 .0
150 .create_dir_all(path)
151 .await
152 .to_storage_fs_result(path, FSOperation::Dir)?;
153 Ok(())
154 }
155
156 async fn write_file(&self, path: &Utf8Path) -> FSResult<Writer> {
157 if self.exists(path).await? {
158 self.remove_file(path).await?;
159 }
160 self
161 .ensure_dir(path.parent().expect("should have parent"))
162 .await?;
163
164 let stream = self
165 .0
166 .create_write_stream(path)
167 .await
168 .to_storage_fs_result(path, FSOperation::Write)?;
169
170 Ok(Writer {
171 path: path.to_path_buf(),
172 stream,
173 })
174 }
175
176 async fn read_file(&self, path: &Utf8Path) -> FSResult<Reader> {
177 let stream = self
178 .0
179 .create_read_stream(path)
180 .await
181 .to_storage_fs_result(path, FSOperation::Read)?;
182 Ok(Reader {
183 path: path.to_path_buf(),
184 stream,
185 })
186 }
187
188 async fn read_dir(&self, path: &Utf8Path) -> FSResult<HashSet<String>> {
189 let files = self
190 .0
191 .read_dir(path)
192 .await
193 .to_storage_fs_result(path, FSOperation::Read)?;
194 Ok(files.into_iter().collect::<HashSet<_>>())
195 }
196
197 async fn metadata(&self, path: &Utf8Path) -> FSResult<FileMetadata> {
198 let res = self
199 .0
200 .stat(path)
201 .await
202 .to_storage_fs_result(path, FSOperation::Stat)?;
203 Ok(res)
204 }
205
206 async fn remove_file(&self, path: &Utf8Path) -> FSResult<()> {
207 if self.exists(path).await? {
208 self
209 .0
210 .remove_file(path)
211 .await
212 .to_storage_fs_result(path, FSOperation::Remove)?;
213 }
214 Ok(())
215 }
216
217 async fn move_file(&self, from: &Utf8Path, to: &Utf8Path) -> FSResult<()> {
218 if self.exists(from).await? {
219 self
220 .ensure_dir(to.parent().expect("should have parent"))
221 .await?;
222 self
223 .0
224 .rename(from, to)
225 .await
226 .to_storage_fs_result(from, FSOperation::Move)?;
227 }
228 Ok(())
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use std::sync::Arc;
235
236 use rspack_fs::MemoryFileSystem;
237 use rspack_paths::Utf8PathBuf;
238
239 use super::{BridgeFileSystem, FSResult};
240 use crate::FileSystem;
241
242 fn get_path(p: &str) -> Utf8PathBuf {
243 Utf8PathBuf::from(p)
244 }
245
246 async fn test_create_dir(fs: &BridgeFileSystem) -> FSResult<()> {
247 fs.ensure_dir(&get_path("/parent/from")).await?;
248 fs.ensure_dir(&get_path("/parent/to")).await?;
249
250 assert!(fs.exists(&get_path("/parent/from")).await?);
251 assert!(fs.exists(&get_path("/parent/to")).await?);
252
253 assert!(fs.metadata(&get_path("/parent/from")).await?.is_directory);
254 assert!(fs.metadata(&get_path("/parent/to")).await?.is_directory);
255
256 Ok(())
257 }
258
259 async fn test_write_file(fs: &BridgeFileSystem) -> FSResult<()> {
260 let mut writer = fs.write_file(&get_path("/parent/from/file.txt")).await?;
261
262 writer.write_line("hello").await?;
263 writer.write(b" world").await?;
264 writer.flush().await?;
265
266 assert!(fs.exists(&get_path("/parent/from/file.txt")).await?);
267 assert!(
268 fs.metadata(&get_path("/parent/from/file.txt"))
269 .await?
270 .is_file
271 );
272
273 Ok(())
274 }
275
276 async fn test_read_file(fs: &BridgeFileSystem) -> FSResult<()> {
277 let mut reader = fs.read_file(&get_path("/parent/from/file.txt")).await?;
278
279 assert_eq!(reader.read_line().await?, "hello");
280 assert_eq!(reader.read(b" world".len()).await?, b" world");
281
282 Ok(())
283 }
284
285 async fn test_move_file(fs: &BridgeFileSystem) -> FSResult<()> {
286 fs.move_file(
287 &get_path("/parent/from/file.txt"),
288 &get_path("/parent/to/file.txt"),
289 )
290 .await?;
291 assert!(!fs.exists(&get_path("/parent/from/file.txt")).await?);
292 assert!(fs.exists(&get_path("/parent/to/file.txt")).await?);
293 assert!(fs.metadata(&get_path("/parent/to/file.txt")).await?.is_file);
294
295 Ok(())
296 }
297
298 async fn test_remove_file(fs: &BridgeFileSystem) -> FSResult<()> {
299 fs.remove_file(&get_path("/parent/to/file.txt")).await?;
300 assert!(!fs.exists(&get_path("/parent/to/file.txt")).await?);
301 Ok(())
302 }
303
304 async fn test_remove_dir(fs: &BridgeFileSystem) -> FSResult<()> {
305 fs.remove_dir(&get_path("/parent/from")).await?;
306 fs.remove_dir(&get_path("/parent/to")).await?;
307 assert!(!fs.exists(&get_path("/parent/from")).await?);
308 assert!(!fs.exists(&get_path("/parent/to")).await?);
309 Ok(())
310 }
311
312 async fn test_error(fs: &BridgeFileSystem) -> FSResult<()> {
313 match fs.metadata(&get_path("/parent/from/not_exist.txt")).await {
314 Ok(_) => panic!("should error"),
315 Err(e) => assert_eq!(
316 e.to_string(),
317 r#"stat `/parent/from/not_exist.txt` failed due to `file not exist`"#
318 ),
319 };
320
321 Ok(())
322 }
323
324 async fn test_memory_fs(fs: &BridgeFileSystem) -> FSResult<()> {
325 test_create_dir(fs).await?;
326 test_write_file(fs).await?;
327 test_read_file(fs).await?;
328 test_move_file(fs).await?;
329 test_remove_file(fs).await?;
330 test_remove_dir(fs).await?;
331 test_error(fs).await?;
332
333 Ok(())
334 }
335
336 #[tokio::test]
337 #[cfg_attr(miri, ignore)]
338 async fn should_storage_bridge_fs_work() -> FSResult<()> {
339 let fs = BridgeFileSystem(Arc::new(MemoryFileSystem::default()));
340
341 test_memory_fs(&fs).await?;
342 Ok(())
343 }
344}