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 SyncDir: Filesystem {
117	fn sync_dir(&self, path: &Path) -> Result<()>;
118}
119
120pub trait Pread {
121	fn pread(&self, offset: u64, buf: &mut [u8]) -> Result<usize>;
122}
123
124pub trait Pwrite {
125	fn pwrite(&self, offset: u64, buf: &[u8]) -> Result<usize>;
126}
127
128pub trait SyncData {
129	fn sync_data(&self) -> Result<()>;
130}
131
132pub trait Truncate {
133	fn truncate(&self, len: u64) -> Result<()>;
134}
135
136pub trait Len {
137	fn len(&self) -> Result<u64>;
138
139	fn is_empty(&self) -> Result<bool> {
140		self.len().map(|len| len == 0)
141	}
142}
143
144#[derive(Clone)]
145pub enum Fs {
146	#[cfg(reifydb_target = "host")]
147	Host(HostFs),
148	Memory(MemoryFs),
149	#[cfg(feature = "testing")]
150	Testing(TestingFs),
151}
152
153pub enum File {
154	#[cfg(reifydb_target = "host")]
155	Host(HostFile),
156	Memory(MemoryFile),
157	#[cfg(feature = "testing")]
158	Testing(TestingFile),
159}
160
161pub enum FileMut {
162	#[cfg(reifydb_target = "host")]
163	Host(HostFileMut),
164	Memory(MemoryFileMut),
165	#[cfg(feature = "testing")]
166	Testing(TestingFileMut),
167}
168
169impl Filesystem for Fs {
170	type File = File;
171	type FileMut = FileMut;
172}
173
174impl Mkdir for Fs {
175	fn mkdir(&self, path: &Path) -> Result<()> {
176		match self {
177			#[cfg(reifydb_target = "host")]
178			Fs::Host(fs) => fs.mkdir(path),
179			Fs::Memory(fs) => fs.mkdir(path),
180			#[cfg(feature = "testing")]
181			Fs::Testing(fs) => fs.mkdir(path),
182		}
183	}
184}
185
186impl Create for Fs {
187	fn create(&self, path: &Path, len: u64) -> Result<FileMut> {
188		match self {
189			#[cfg(reifydb_target = "host")]
190			Fs::Host(fs) => fs.create(path, len).map(FileMut::Host),
191			Fs::Memory(fs) => fs.create(path, len).map(FileMut::Memory),
192			#[cfg(feature = "testing")]
193			Fs::Testing(fs) => fs.create(path, len).map(FileMut::Testing),
194		}
195	}
196}
197
198impl Open for Fs {
199	fn open(&self, path: &Path) -> Result<File> {
200		match self {
201			#[cfg(reifydb_target = "host")]
202			Fs::Host(fs) => fs.open(path).map(File::Host),
203			Fs::Memory(fs) => fs.open(path).map(File::Memory),
204			#[cfg(feature = "testing")]
205			Fs::Testing(fs) => fs.open(path).map(File::Testing),
206		}
207	}
208}
209
210impl OpenMut for Fs {
211	fn open_mut(&self, path: &Path) -> Result<FileMut> {
212		match self {
213			#[cfg(reifydb_target = "host")]
214			Fs::Host(fs) => fs.open_mut(path).map(FileMut::Host),
215			Fs::Memory(fs) => fs.open_mut(path).map(FileMut::Memory),
216			#[cfg(feature = "testing")]
217			Fs::Testing(fs) => fs.open_mut(path).map(FileMut::Testing),
218		}
219	}
220}
221
222impl ReadDir for Fs {
223	fn read_dir(&self, path: &Path) -> Result<Vec<PathBuf>> {
224		match self {
225			#[cfg(reifydb_target = "host")]
226			Fs::Host(fs) => fs.read_dir(path),
227			Fs::Memory(fs) => fs.read_dir(path),
228			#[cfg(feature = "testing")]
229			Fs::Testing(fs) => fs.read_dir(path),
230		}
231	}
232}
233
234impl Rename for Fs {
235	fn rename(&self, from: &Path, to: &Path) -> Result<()> {
236		match self {
237			#[cfg(reifydb_target = "host")]
238			Fs::Host(fs) => fs.rename(from, to),
239			Fs::Memory(fs) => fs.rename(from, to),
240			#[cfg(feature = "testing")]
241			Fs::Testing(fs) => fs.rename(from, to),
242		}
243	}
244}
245
246impl Unlink for Fs {
247	fn unlink(&self, path: &Path) -> Result<()> {
248		match self {
249			#[cfg(reifydb_target = "host")]
250			Fs::Host(fs) => fs.unlink(path),
251			Fs::Memory(fs) => fs.unlink(path),
252			#[cfg(feature = "testing")]
253			Fs::Testing(fs) => fs.unlink(path),
254		}
255	}
256}
257
258impl SyncDir for Fs {
259	fn sync_dir(&self, path: &Path) -> Result<()> {
260		match self {
261			#[cfg(reifydb_target = "host")]
262			Fs::Host(fs) => fs.sync_dir(path),
263			Fs::Memory(fs) => fs.sync_dir(path),
264			#[cfg(feature = "testing")]
265			Fs::Testing(fs) => fs.sync_dir(path),
266		}
267	}
268}
269
270impl Pread for File {
271	fn pread(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
272		match self {
273			#[cfg(reifydb_target = "host")]
274			File::Host(file) => file.pread(offset, buf),
275			File::Memory(file) => file.pread(offset, buf),
276			#[cfg(feature = "testing")]
277			File::Testing(file) => file.pread(offset, buf),
278		}
279	}
280}
281
282impl Len for File {
283	fn len(&self) -> Result<u64> {
284		match self {
285			#[cfg(reifydb_target = "host")]
286			File::Host(file) => file.len(),
287			File::Memory(file) => file.len(),
288			#[cfg(feature = "testing")]
289			File::Testing(file) => file.len(),
290		}
291	}
292}
293
294impl Pread for FileMut {
295	fn pread(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
296		match self {
297			#[cfg(reifydb_target = "host")]
298			FileMut::Host(file) => file.pread(offset, buf),
299			FileMut::Memory(file) => file.pread(offset, buf),
300			#[cfg(feature = "testing")]
301			FileMut::Testing(file) => file.pread(offset, buf),
302		}
303	}
304}
305
306impl Pwrite for FileMut {
307	fn pwrite(&self, offset: u64, buf: &[u8]) -> Result<usize> {
308		match self {
309			#[cfg(reifydb_target = "host")]
310			FileMut::Host(file) => file.pwrite(offset, buf),
311			FileMut::Memory(file) => file.pwrite(offset, buf),
312			#[cfg(feature = "testing")]
313			FileMut::Testing(file) => file.pwrite(offset, buf),
314		}
315	}
316}
317
318impl SyncData for FileMut {
319	fn sync_data(&self) -> Result<()> {
320		match self {
321			#[cfg(reifydb_target = "host")]
322			FileMut::Host(file) => file.sync_data(),
323			FileMut::Memory(file) => file.sync_data(),
324			#[cfg(feature = "testing")]
325			FileMut::Testing(file) => file.sync_data(),
326		}
327	}
328}
329
330impl Truncate for FileMut {
331	fn truncate(&self, len: u64) -> Result<()> {
332		match self {
333			#[cfg(reifydb_target = "host")]
334			FileMut::Host(file) => file.truncate(len),
335			FileMut::Memory(file) => file.truncate(len),
336			#[cfg(feature = "testing")]
337			FileMut::Testing(file) => file.truncate(len),
338		}
339	}
340}
341
342impl Len for FileMut {
343	fn len(&self) -> Result<u64> {
344		match self {
345			#[cfg(reifydb_target = "host")]
346			FileMut::Host(file) => file.len(),
347			FileMut::Memory(file) => file.len(),
348			#[cfg(feature = "testing")]
349			FileMut::Testing(file) => file.len(),
350		}
351	}
352}