Skip to main content

reifydb_runtime/io/fs/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4#[cfg(reifydb_target = "host")]
5pub mod host;
6pub mod memory;
7#[cfg(feature = "testing")]
8pub mod testing;
9
10use std::{
11	error::Error as StdError,
12	fmt,
13	fmt::Display,
14	io,
15	path::{Path, PathBuf},
16};
17
18#[cfg(reifydb_target = "host")]
19use crate::io::fs::host::{HostFile, HostFileMut, HostFs};
20use crate::io::fs::memory::{MemoryFile, MemoryFileMut, MemoryFs};
21#[cfg(feature = "testing")]
22use crate::io::fs::testing::{TestingFile, TestingFileMut, TestingFs};
23
24pub type Result<T> = std::result::Result<T, FsError>;
25
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub enum FsError {
28	NotFound(PathBuf),
29	AlreadyExists(PathBuf),
30	NoSpace(PathBuf),
31	NotADirectory(PathBuf),
32	Io {
33		path: PathBuf,
34		message: String,
35	},
36}
37
38impl FsError {
39	pub fn from_io(path: &Path, err: io::Error) -> Self {
40		match err.kind() {
41			io::ErrorKind::NotFound => FsError::NotFound(path.to_path_buf()),
42			io::ErrorKind::AlreadyExists => FsError::AlreadyExists(path.to_path_buf()),
43			io::ErrorKind::StorageFull => FsError::NoSpace(path.to_path_buf()),
44			io::ErrorKind::NotADirectory => FsError::NotADirectory(path.to_path_buf()),
45			_ => FsError::Io {
46				path: path.to_path_buf(),
47				message: err.to_string(),
48			},
49		}
50	}
51
52	pub fn path(&self) -> &Path {
53		match self {
54			FsError::NotFound(path) => path,
55			FsError::AlreadyExists(path) => path,
56			FsError::NoSpace(path) => path,
57			FsError::NotADirectory(path) => path,
58			FsError::Io {
59				path,
60				..
61			} => path,
62		}
63	}
64}
65
66impl Display for FsError {
67	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68		match self {
69			FsError::NotFound(path) => write!(f, "not found: {}", path.display()),
70			FsError::AlreadyExists(path) => write!(f, "already exists: {}", path.display()),
71			FsError::NoSpace(path) => write!(f, "out of space: {}", path.display()),
72			FsError::NotADirectory(path) => write!(f, "not a directory: {}", path.display()),
73			FsError::Io {
74				path,
75				message,
76			} => write!(f, "io error on {}: {}", path.display(), message),
77		}
78	}
79}
80
81impl StdError for FsError {}
82
83pub trait Filesystem {
84	type File: Pread + Len;
85	type FileMut: Pread + Pwrite + SyncData + Truncate + Len;
86}
87
88pub trait Mkdir: Filesystem {
89	fn mkdir(&self, path: &Path) -> Result<()>;
90}
91
92pub trait Create: Filesystem {
93	fn create(&self, path: &Path, len: u64) -> Result<Self::FileMut>;
94}
95
96pub trait Open: Filesystem {
97	fn open(&self, path: &Path) -> Result<Self::File>;
98}
99
100pub trait OpenMut: Filesystem {
101	fn open_mut(&self, path: &Path) -> Result<Self::FileMut>;
102}
103
104pub trait ReadDir: Filesystem {
105	fn read_dir(&self, path: &Path) -> Result<Vec<PathBuf>>;
106}
107
108pub trait Rename: Filesystem {
109	fn rename(&self, from: &Path, to: &Path) -> Result<()>;
110}
111
112pub trait Unlink: Filesystem {
113	fn unlink(&self, path: &Path) -> Result<()>;
114}
115
116pub trait RemoveDir: Filesystem {
117	fn remove_dir(&self, path: &Path) -> Result<()>;
118}
119
120pub trait SyncDir: Filesystem {
121	fn sync_dir(&self, path: &Path) -> Result<()>;
122}
123
124pub trait Pread {
125	fn pread(&self, offset: u64, buf: &mut [u8]) -> Result<usize>;
126}
127
128pub trait Pwrite {
129	fn pwrite(&self, offset: u64, buf: &[u8]) -> Result<usize>;
130}
131
132pub trait SyncData {
133	fn sync_data(&self) -> Result<()>;
134}
135
136pub trait Truncate {
137	fn truncate(&self, len: u64) -> Result<()>;
138}
139
140pub trait Len {
141	fn len(&self) -> Result<u64>;
142
143	fn is_empty(&self) -> Result<bool> {
144		self.len().map(|len| len == 0)
145	}
146}
147
148#[derive(Clone)]
149pub enum Fs {
150	#[cfg(reifydb_target = "host")]
151	Host(HostFs),
152	Memory(MemoryFs),
153	#[cfg(feature = "testing")]
154	Testing(TestingFs),
155}
156
157pub enum File {
158	#[cfg(reifydb_target = "host")]
159	Host(HostFile),
160	Memory(MemoryFile),
161	#[cfg(feature = "testing")]
162	Testing(TestingFile),
163}
164
165pub enum FileMut {
166	#[cfg(reifydb_target = "host")]
167	Host(HostFileMut),
168	Memory(MemoryFileMut),
169	#[cfg(feature = "testing")]
170	Testing(TestingFileMut),
171}
172
173impl Filesystem for Fs {
174	type File = File;
175	type FileMut = FileMut;
176}
177
178impl Mkdir for Fs {
179	fn mkdir(&self, path: &Path) -> Result<()> {
180		match self {
181			#[cfg(reifydb_target = "host")]
182			Fs::Host(fs) => fs.mkdir(path),
183			Fs::Memory(fs) => fs.mkdir(path),
184			#[cfg(feature = "testing")]
185			Fs::Testing(fs) => fs.mkdir(path),
186		}
187	}
188}
189
190impl Create for Fs {
191	fn create(&self, path: &Path, len: u64) -> Result<FileMut> {
192		match self {
193			#[cfg(reifydb_target = "host")]
194			Fs::Host(fs) => fs.create(path, len).map(FileMut::Host),
195			Fs::Memory(fs) => fs.create(path, len).map(FileMut::Memory),
196			#[cfg(feature = "testing")]
197			Fs::Testing(fs) => fs.create(path, len).map(FileMut::Testing),
198		}
199	}
200}
201
202impl Open for Fs {
203	fn open(&self, path: &Path) -> Result<File> {
204		match self {
205			#[cfg(reifydb_target = "host")]
206			Fs::Host(fs) => fs.open(path).map(File::Host),
207			Fs::Memory(fs) => fs.open(path).map(File::Memory),
208			#[cfg(feature = "testing")]
209			Fs::Testing(fs) => fs.open(path).map(File::Testing),
210		}
211	}
212}
213
214impl OpenMut for Fs {
215	fn open_mut(&self, path: &Path) -> Result<FileMut> {
216		match self {
217			#[cfg(reifydb_target = "host")]
218			Fs::Host(fs) => fs.open_mut(path).map(FileMut::Host),
219			Fs::Memory(fs) => fs.open_mut(path).map(FileMut::Memory),
220			#[cfg(feature = "testing")]
221			Fs::Testing(fs) => fs.open_mut(path).map(FileMut::Testing),
222		}
223	}
224}
225
226impl ReadDir for Fs {
227	fn read_dir(&self, path: &Path) -> Result<Vec<PathBuf>> {
228		match self {
229			#[cfg(reifydb_target = "host")]
230			Fs::Host(fs) => fs.read_dir(path),
231			Fs::Memory(fs) => fs.read_dir(path),
232			#[cfg(feature = "testing")]
233			Fs::Testing(fs) => fs.read_dir(path),
234		}
235	}
236}
237
238impl Rename for Fs {
239	fn rename(&self, from: &Path, to: &Path) -> Result<()> {
240		match self {
241			#[cfg(reifydb_target = "host")]
242			Fs::Host(fs) => fs.rename(from, to),
243			Fs::Memory(fs) => fs.rename(from, to),
244			#[cfg(feature = "testing")]
245			Fs::Testing(fs) => fs.rename(from, to),
246		}
247	}
248}
249
250impl Unlink for Fs {
251	fn unlink(&self, path: &Path) -> Result<()> {
252		match self {
253			#[cfg(reifydb_target = "host")]
254			Fs::Host(fs) => fs.unlink(path),
255			Fs::Memory(fs) => fs.unlink(path),
256			#[cfg(feature = "testing")]
257			Fs::Testing(fs) => fs.unlink(path),
258		}
259	}
260}
261
262impl RemoveDir for Fs {
263	fn remove_dir(&self, path: &Path) -> Result<()> {
264		match self {
265			#[cfg(reifydb_target = "host")]
266			Fs::Host(fs) => fs.remove_dir(path),
267			Fs::Memory(fs) => fs.remove_dir(path),
268			#[cfg(feature = "testing")]
269			Fs::Testing(fs) => fs.remove_dir(path),
270		}
271	}
272}
273
274impl SyncDir for Fs {
275	fn sync_dir(&self, path: &Path) -> Result<()> {
276		match self {
277			#[cfg(reifydb_target = "host")]
278			Fs::Host(fs) => fs.sync_dir(path),
279			Fs::Memory(fs) => fs.sync_dir(path),
280			#[cfg(feature = "testing")]
281			Fs::Testing(fs) => fs.sync_dir(path),
282		}
283	}
284}
285
286impl Pread for File {
287	fn pread(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
288		match self {
289			#[cfg(reifydb_target = "host")]
290			File::Host(file) => file.pread(offset, buf),
291			File::Memory(file) => file.pread(offset, buf),
292			#[cfg(feature = "testing")]
293			File::Testing(file) => file.pread(offset, buf),
294		}
295	}
296}
297
298impl Len for File {
299	fn len(&self) -> Result<u64> {
300		match self {
301			#[cfg(reifydb_target = "host")]
302			File::Host(file) => file.len(),
303			File::Memory(file) => file.len(),
304			#[cfg(feature = "testing")]
305			File::Testing(file) => file.len(),
306		}
307	}
308}
309
310impl Pread for FileMut {
311	fn pread(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
312		match self {
313			#[cfg(reifydb_target = "host")]
314			FileMut::Host(file) => file.pread(offset, buf),
315			FileMut::Memory(file) => file.pread(offset, buf),
316			#[cfg(feature = "testing")]
317			FileMut::Testing(file) => file.pread(offset, buf),
318		}
319	}
320}
321
322impl Pwrite for FileMut {
323	fn pwrite(&self, offset: u64, buf: &[u8]) -> Result<usize> {
324		match self {
325			#[cfg(reifydb_target = "host")]
326			FileMut::Host(file) => file.pwrite(offset, buf),
327			FileMut::Memory(file) => file.pwrite(offset, buf),
328			#[cfg(feature = "testing")]
329			FileMut::Testing(file) => file.pwrite(offset, buf),
330		}
331	}
332}
333
334impl SyncData for FileMut {
335	fn sync_data(&self) -> Result<()> {
336		match self {
337			#[cfg(reifydb_target = "host")]
338			FileMut::Host(file) => file.sync_data(),
339			FileMut::Memory(file) => file.sync_data(),
340			#[cfg(feature = "testing")]
341			FileMut::Testing(file) => file.sync_data(),
342		}
343	}
344}
345
346impl Truncate for FileMut {
347	fn truncate(&self, len: u64) -> Result<()> {
348		match self {
349			#[cfg(reifydb_target = "host")]
350			FileMut::Host(file) => file.truncate(len),
351			FileMut::Memory(file) => file.truncate(len),
352			#[cfg(feature = "testing")]
353			FileMut::Testing(file) => file.truncate(len),
354		}
355	}
356}
357
358impl Len for FileMut {
359	fn len(&self) -> Result<u64> {
360		match self {
361			#[cfg(reifydb_target = "host")]
362			FileMut::Host(file) => file.len(),
363			FileMut::Memory(file) => file.len(),
364			#[cfg(feature = "testing")]
365			FileMut::Testing(file) => file.len(),
366		}
367	}
368}