dir_structure/traits/vfs.rs
1//! Virtual file system traits.
2//!
3//! This module defines traits for virtual file systems (VFS) that can be implemented
4//! for different backends, such as the local file system, in-memory file systems, or
5//! even remote (e.g., cloud storage) file systems.
6//!
7//! Ultimately, all uses of the library will end up in VFS calls, on either [`Vfs`] or
8//! [`WriteSupportingVfs`], depending on whether they are read-only or read-write operations.
9//!
10//! The trait hierarchy is as follows:
11//!
12//! [`WriteSupportingVfs`] < [`Vfs`] < [`VfsCore`]
13//!
14//! [`VfsCore`] is a type whose only purpose is to define the associated [`Path`][VfsCore::Path] type,
15//! which is used throughout the other traits. It is a super-trait of [`Vfs`].
16//!
17//! [`Vfs`] is the main trait for read-only virtual file systems. It provides methods for reading
18//! files and directories. It is a super-trait of [`WriteSupportingVfs`].
19//!
20//! [`WriteSupportingVfs`] is a trait for virtual file systems that support writing operations.
21//! It extends [`Vfs`] with methods for writing files and creating/removing directories.
22//!
23//! # Why `Pin<&Self>`?
24//!
25//! The reason why the methods of the VFS traits take `self` as `Pin<&Self>` instead of
26//! `&self` is to allow for implementations that may need to keep the VFS instance
27//! pinned in memory. This is particularly important for
28//! [`DeferredRead`][crate::deferred_read::DeferredRead], which will hold a reference
29//! to the VFS instance for a potentially long time, and must ensure that the VFS
30//! instance does not move in memory during that time. Using `Pin<&Self>` allows
31//! such implementations to be safe and sound. This is more for the [`ReadFrom`] /
32//! [`WriteTo`] implementations with [`DeferredRead`][crate::deferred_read::DeferredRead]
33//! than for the VFS implementations, but using it here as well ensures that all
34//! [`ReadFrom`] / [`WriteTo`] implementations have a consistent interface with the
35//! [`Vfs`] / [`WriteSupportingVfs`] traits.
36//!
37//! The Virtual File Systems have an associated path type, defined by the [`PathType`] trait.
38//! See its documentation for more details.
39//!
40//! The reason why the path type is an associated type in the [`VfsCore`] trait, instead of being
41//! an associated type in the [`Vfs`] trait directly, is to allow a clear common interface between
42//! the syncrhonous and asynchronous VFS traits. The asynchronous VFS trait,
43//! [`VfsAsync`], also has
44//! as a super-trait [`VfsCore`], and thus shares the same associated path type, but does not depend on
45//! the [`Vfs`] trait directly.
46//!
47//! This change allows us to specify that our types only work with a specific path type, without
48//! forcing them to be tied to a specific VFS implementation. As an example, we can have a
49//! type that works with any VFS that uses [`Path`] as its path type, without being tied to a specific
50//! VFS implementation / kind of implementation:
51//!
52//! ```rust,ignore
53//! use std::path::Path;
54//! use std::pin::Pin;
55//!
56//! use dir_structure::prelude::*;
57//!
58//! #[derive(DirStructure)]
59//! struct PathOnlyDir<Vfs: VfsCore<Path = Path>> {
60//! #[dir_structure(path = "file.txt")]
61//! file: String,
62//! __phantom: std::marker::PhantomData<Vfs>,
63//! }
64//!
65//! // sync
66//! PathOnlyDir::read_from(Path::new("some/dir"), Pin::new(&SomeVfsImplementationThatUsesPath));
67//! // async works as well
68//! PathOnlyDir::read_from_async(PathBuf::from("some/dir"), Pin::new(&SomeAsyncVfsImplementationThatUsesPath)).await;
69//! ```
70//!
71//! # For implementers of Virtual File Systems
72//!
73//! This section covers a bit about what you need to know to implement your own VFS.
74//! Ultimately, you will need to implement the [`Vfs`] trait for your type. If your
75//! Vfs accepts write operations as well, it should additionally implement
76//! [`WriteSupportingVfs`]. Before you can implement [`Vfs`], you will need to implement
77//! its super-trait, [`VfsCore`], which only requires you to define the associated
78//! path type. The path type must implement the [`PathType`] trait, which is also
79//! defined in this module. This [`PathType`] trait is implemented for [`Path`], so
80//! if your VFS uses [`Path`] as its path type, you can just use that.
81//!
82//! If you want to implement your own path type, you will need to implement the
83//! [`PathType`] trait for it. The [`PathType`] trait is designed to be as general
84//! as possible, to allow for a wide variety of path types. However, it does have
85//! some requirements, such as being able to join paths and to get the parent path.
86//! See the documentation for the [`PathType`] trait for more details.
87//!
88//! ## Tool-specific extensions of the VFS traits
89//!
90//! Some tools (wrappers) in the library provide their own extensions of the VFS traits, with
91//! extra functionality that is required by the tool. For example, the [`AtomicDir`](crate::atomic_dir::AtomicDir)
92//! tool requires the ability to create temporary directories, and thus provides its own traits
93//! required for working with temporary directories, but they need to be implemented by the VFS
94//! type.
95//!
96//! Here we list all such extensions, which implementers of VFS traits may want to be aware of,
97//! if they want to support these tools.
98//!
99//! ### [`AtomicDir<T>`](crate::atomic_dir::AtomicDir)
100//!
101//! For the [`AtomicDir`](crate::atomic_dir::AtomicDir) tool to work, the VFS type itself must implement
102//! the [`VfsSupportsTemporaryDirectories`](crate::atomic_dir::VfsSupportsTemporaryDirectories) trait.
103//! See its documentation for more details.
104
105use std::error::Error as StdError;
106use std::ffi::OsStr;
107use std::ffi::OsString;
108use std::io::Read;
109use std::io::Seek;
110use std::io::Write;
111use std::path;
112use std::path::Path;
113use std::path::PathBuf;
114use std::pin::Pin;
115use std::result::Result as StdResult;
116
117use crate::error::Error;
118use crate::error::Result;
119use crate::error::VfsResult;
120use crate::error::WrapIoError as _;
121use crate::prelude::*;
122
123/// Core trait for a virtual file system, providing the associated path type.
124pub trait VfsCore {
125 /// The path type used to represent paths in this virtual file system.
126 type Path: PathType + ?Sized;
127}
128
129/// A virtual file system. Writing operations are provided by the [`WriteSupportingVfs` trait](self::WriteSupportingVfs).
130///
131/// See the [module-level documentation](self) for more details.
132pub trait Vfs<'vfs>: VfsCore + 'vfs {
133 /// The type of the directory walker returned by the [`walk_dir` method](Vfs::walk_dir).
134 ///
135 /// See [`DirWalker`] for more details.
136 type DirWalk<'a>: DirWalker<'a, P = Self::Path>
137 where
138 'vfs: 'a,
139 Self: 'a;
140
141 /// The type of the file returned by the [`open_read` method](Vfs::open_read). This allows us to
142 /// read a file in chunks, which might be required for some certain file formats.
143 ///
144 /// In addtion, [`Vfs`] types whose [`RFile`](Self::RFile) implements [`Seek`] can be used in
145 /// contexts that require seeking, such as image decoding. The [`VfsWithSeekRead`] trait encodes this property.
146 ///
147 /// See the [`VfsWithSeekRead`] trait for more details. Note that it is automatically implemented for all [`Vfs`]
148 /// types with a seekable [`RFile`](Self::RFile).
149 type RFile: Read + 'vfs;
150
151 /// Opens a file for reading, at the specified path.
152 fn open_read(self: Pin<&Self>, path: &Self::Path) -> VfsResult<Self::RFile, Self>;
153
154 /// Reads the contents of a file, at the specified path. This is a core method that
155 /// all other read methods are built upon, except for the ones that read via
156 /// [`open_read`][Vfs::open_read] and [`RFile`][Vfs::RFile].
157 fn read(self: Pin<&Self>, path: &Self::Path) -> VfsResult<Vec<u8>, Self>;
158 /// Reads the contents of a file, at the specified path, and returns it as a string.
159 ///
160 /// This is a convenience method that reads the file as bytes using the [`read`][Vfs::read] method,
161 /// and then converts the bytes to a string. If the bytes are not valid UTF-8, it returns a
162 /// [`Error::Parse`] error.
163 ///
164 /// Overriding this method can be useful for VFS implementations that can read files with guarantees
165 /// about their encoding, or where the underlying storage provides a more efficient way to read strings
166 /// directly, but is not required.
167 fn read_string(self: Pin<&Self>, path: &Self::Path) -> VfsResult<String, Self> {
168 self.read(path).and_then(|bytes| {
169 String::from_utf8(bytes).map_err(|e| Error::Parse(path.owned(), Box::new(e)))
170 })
171 }
172
173 /// Checks if a file exists at the specified path.
174 fn exists(self: Pin<&Self>, path: &Self::Path) -> VfsResult<bool, Self>;
175
176 /// Checks if a directory exists at the specified path.
177 fn is_dir(self: Pin<&Self>, path: &Self::Path) -> VfsResult<bool, Self>;
178
179 /// Walks a directory at the specified path, returning a stream of directory entries.
180 ///
181 /// [`DirWalker`] represents a stream of directory entries, and behaves similarly to an [`Iterator`] over
182 /// `Result<DirEntryInfo>`.
183 ///
184 /// This method returns a [`DirWalker`] that can be used to iterate over the entries in the directory.
185 ///
186 /// See the [`DirWalker`] documentation for more details.
187 fn walk_dir<'b>(self: Pin<&'b Self>, path: &Self::Path) -> VfsResult<Self::DirWalk<'b>, Self>
188 where
189 'vfs: 'b;
190}
191
192/// Extension trait for [`Vfs`] that provides additional convenience methods.
193///
194/// This trait is automatically implemented for all types that implement [`Vfs`].
195pub trait VfsExt<'vfs>: Vfs<'vfs> {
196 /// Reads a file / directory at the specified path, and parses it into the specified type using its
197 /// [`ReadFrom`] implementation.
198 ///
199 /// This method takes `self` as a pinned reference, to ensure that the `Vfs` implementation
200 /// is not moved while the read operation is in progress, or if the type `T` being read stores
201 /// a reference to the `Vfs` instance (e.g., via [`DeferredRead`][crate::deferred_read::DeferredRead]).
202 fn read_typed_pinned<T: ReadFrom<'vfs, Self>>(
203 self: Pin<&'vfs Self>,
204 path: impl AsRef<Self::Path>,
205 ) -> VfsResult<T, Self> {
206 T::read_from(path.as_ref(), self)
207 }
208
209 /// Reads a file / directory at the specified path, and parses it into the specified type using its
210 /// [`ReadFrom`] implementation.
211 ///
212 /// This method takes `self` as a regular reference, and pins it internally, calling
213 /// [`read_typed_pinned`][VfsExt::read_typed_pinned] on the pinned reference.
214 fn read_typed<T: ReadFrom<'vfs, Self>>(
215 &'vfs self,
216 path: impl AsRef<Self::Path>,
217 ) -> VfsResult<T, Self>
218 where
219 Self: Unpin,
220 {
221 Pin::new(self).read_typed_pinned(path)
222 }
223}
224
225// Blanket impl.
226impl<'vfs, V: Vfs<'vfs> + ?Sized> VfsExt<'vfs> for V {}
227
228/// Marks that the [`RFile`](Vfs::RFile) type of this [`Vfs`] also implements [`Seek`],
229/// allowing it to be used in contexts that require seeking, such as image decoding.
230///
231/// This trait is automatically implemented for any [`Vfs`] whose [`RFile`](Vfs::RFile) implements [`Seek`].
232pub trait VfsWithSeekRead<'vfs>: Vfs<'vfs>
233where
234 Self::RFile: Seek,
235{
236}
237
238// Blanket impl.
239impl<'vfs, T: Vfs<'vfs>> VfsWithSeekRead<'vfs> for T where T::RFile: Seek {}
240
241/// A virtual file system that supports writing operations.
242///
243/// This trait extends the [`Vfs`] trait with methods for writing files and creating/removing directories.
244///
245/// See the [module-level documentation](self) for more details.
246pub trait WriteSupportingVfs<'vfs>: Vfs<'vfs> {
247 /// The type of the file returned by the [`open_write` method](WriteSupportingVfs::open_write).
248 ///
249 /// This allows us to write a file in chunks, which might be required for some certain file formats.
250 ///
251 /// If this type additionally implements [`Seek`], then, similarly to [`VfsWithSeekRead`],
252 /// the [`VfsWithSeekWrite`] trait will be automatically implemented for this [`WriteSupportingVfs`].
253 ///
254 /// See [`VfsWithSeekWrite`] for more.
255 type WFile: Write + 'vfs;
256
257 /// Opens a file for writing, at the specified path.
258 ///
259 /// This method will create the file if it does not exist, or truncate it if it does.
260 /// Note that this does not create parent directories, that should happen separately in the calling code,
261 /// which can be done using the [`create_parent_dir`][WriteSupportingVfs::create_parent_dir] method.
262 fn open_write(self: Pin<&Self>, path: &Self::Path) -> VfsResult<Self::WFile, Self>;
263
264 /// Writes the data to a file, to the specified path.
265 ///
266 /// Similarly to [`open_write`][WriteSupportingVfs::open_write], this method will not create parent directories.
267 /// The parent directories should be created separately in the calling code, which can be done using
268 /// the [`create_parent_dir`][WriteSupportingVfs::create_parent_dir] method.
269 fn write(self: Pin<&Self>, path: &Self::Path, data: &[u8]) -> VfsResult<(), Self> {
270 self.open_write(path)?
271 .write_all(data)
272 .wrap_io_error_with(path)
273 }
274
275 /// Removes a directory and all its contents.
276 ///
277 /// This method should remove the directory at the specified path, along with all its contents.
278 fn remove_dir_all(self: Pin<&Self>, path: &Self::Path) -> VfsResult<(), Self>;
279
280 /// Creates a new directory at the specified path.
281 ///
282 /// This method should create a new, empty directory at the specified path.
283 fn create_dir(self: Pin<&Self>, path: &Self::Path) -> VfsResult<(), Self>;
284
285 /// Creates a new directory and all its parent directories at the specified path.
286 ///
287 /// This is a convenience method that can be used to ensure that a directory exists, along with all its parent directories.
288 fn create_dir_all(self: Pin<&Self>, path: &Self::Path) -> VfsResult<(), Self>;
289
290 /// Creates the parent directory for the specified path, if it does not exist.
291 ///
292 /// This is a convenience method that can be used before writing a file, to ensure that the parent directory
293 /// exists. If the parent directory already exists, this method does nothing. If the path has no parent, this method does nothing.
294 fn create_parent_dir(self: Pin<&Self>, path: &Self::Path) -> VfsResult<(), Self> {
295 if let Some(parent) = path.parent()
296 && !self.exists(parent)?
297 {
298 self.create_dir_all(parent)?;
299 }
300 Ok(())
301 }
302}
303
304/// Extension trait for [`WriteSupportingVfs`] that provides additional convenience methods.
305pub trait WriteSupportingVfsExt<'vfs>: WriteSupportingVfs<'vfs> {
306 /// Writes a file / directory at the specified path, using the specified data type's
307 /// [`WriteTo`] implementation.
308 ///
309 /// This method takes `self` as a pinned reference, to ensure that the `Vfs` implementation
310 /// is not moved while the write operation is in progress.
311 fn write_typed_pinned<T: WriteTo<'vfs, Self>>(
312 self: Pin<&'vfs Self>,
313 path: impl AsRef<Self::Path>,
314 value: &T,
315 ) -> VfsResult<(), Self> {
316 value.write_to(path.as_ref(), self)
317 }
318
319 /// Writes a file / directory at the specified path, using the specified data type's
320 /// [`WriteTo`] implementation.
321 ///
322 /// This method takes `self` as a regular reference, and pins it internally.
323 fn write_typed<T: WriteTo<'vfs, Self>>(
324 &'vfs self,
325 path: impl AsRef<Self::Path>,
326 value: &T,
327 ) -> VfsResult<(), Self>
328 where
329 Self: Unpin,
330 {
331 Pin::new(self).write_typed_pinned(path, value)
332 }
333}
334
335// Blanket impl.
336impl<'vfs, Vfs: WriteSupportingVfs<'vfs> + ?Sized> WriteSupportingVfsExt<'vfs> for Vfs {}
337
338/// Marks that the [`WFile`](WriteSupportingVfs::WFile) type of this [`WriteSupportingVfs`] also implements [`Seek`],
339/// allowing it to be used in contexts that require seeking.
340///
341/// This trait is automatically implemented for any [`WriteSupportingVfs`] whose [`WFile`](WriteSupportingVfs::WFile) implements [`Seek`].
342pub trait VfsWithSeekWrite<'vfs>: WriteSupportingVfs<'vfs>
343where
344 Self::WFile: Seek,
345{
346}
347
348// Blanket impl.
349impl<'vfs, T: WriteSupportingVfs<'vfs>> VfsWithSeekWrite<'vfs> for T where T::WFile: Seek {}
350
351/// A trait representing a path in a virtual file system.
352///
353/// This can be implemented for custom path types, allowing you to create [`Vfs`] implementations that
354/// work with path types other than [`Path`] and [`PathBuf`]. [`PathType`] is implemented for the reference type,
355/// which is "linked" to the owned type via the associated type [`OwnedPath`][PathType::OwnedPath].
356///
357/// Once you have a value of that owned type (e.g., `MyPathBuf`), you can get a reference to the path type
358/// (e.g., `&MyPath`) via the [`AsRef`] trait, which is a super-trait of [`OwnedPathType`].
359///
360/// This trait is implemented for [`Path`], so if your VFS uses [`Path`] as its path type, you can just use that.
361///
362/// All the examples in the documentation for [`PathType`] and [`OwnedPathType`] use [`Path`] and [`PathBuf`]. You
363/// may treat them as expected behaviours for your own implementations of these traits, and derive your tests from them.
364pub trait PathType: PartialEq + Send + Sync {
365 /// The owned version of this path type.
366 ///
367 /// This owned type must implement the [`OwnedPathType`] trait, which has as a super-trait of [`AsRef`],
368 /// allowing us to get a reference to this [`PathType`] type from the [`OwnedPathType`] type.
369 type OwnedPath: OwnedPathType<RefType = Self>;
370
371 /// The type of a path segment (a component of a path).
372 ///
373 /// This represents a single segment of a path, such as a file or directory name.
374 ///
375 /// In the case of [`Path`], this is [`OsStr`].
376 type PathSegmentRef: ToOwned<Owned = Self::PathSegmentOwned> + PartialEq + ?Sized;
377
378 /// The owned version of a path segment.
379 ///
380 /// This represents a single segment of a path, such as a file or directory name.
381 ///
382 /// This is the owned version of [`PathType::PathSegmentRef`].
383 type PathSegmentOwned: Send + Sync + Clone + Eq + AsRef<Self::PathSegmentRef>;
384
385 /// Returns the parent path, if it exists.
386 ///
387 /// # Example
388 ///
389 /// ```rust
390 /// use std::path::{Path, PathBuf};
391 /// use dir_structure::traits::vfs::PathType;
392 ///
393 /// let path = Path::new("some/dir/file.txt");
394 /// let parent = PathType::parent(path);
395 /// assert_eq!(parent, Some(Path::new("some/dir")));
396 ///
397 /// let path = Path::new("file.txt");
398 /// let parent = PathType::parent(path);
399 /// assert_eq!(parent, Some(Path::new("")));
400 ///
401 /// let empty = Path::new("");
402 /// let parent = PathType::parent(empty);
403 /// assert_eq!(parent, None);
404 /// ```
405 fn parent(&self) -> Option<&Self>;
406
407 /// Joins this path with another path fragment, returning a new owned path.
408 ///
409 /// # Example
410 ///
411 /// ```rust
412 /// use std::path::{Path, PathBuf};
413 /// use dir_structure::traits::vfs::PathType;
414 ///
415 /// let path = Path::new("some/dir");
416 /// let new_path = PathType::join(path, Path::new("file.txt"));
417 /// assert_eq!(new_path, PathBuf::from("some/dir/file.txt"));
418 /// ```
419 fn join(&self, new_fragment: impl AsRef<Self>) -> Self::OwnedPath;
420
421 /// Joins this path with a path segment, returning a new owned path.
422 ///
423 /// # Example
424 ///
425 /// ```rust
426 /// use std::path::{Path, PathBuf};
427 /// use std::ffi::OsStr;
428 /// use dir_structure::traits::vfs::PathType;
429 ///
430 /// let path = Path::new("some/dir");
431 /// let new_path = PathType::join_segment(path, OsStr::new("file.txt"));
432 /// assert_eq!(new_path, PathBuf::from("some/dir/file.txt"));
433 /// ```
434 fn join_segment(&self, new_fragment: impl AsRef<Self::PathSegmentRef>) -> Self::OwnedPath;
435
436 /// Joins this path with a string slice as a path segment, returning a new owned path.
437 ///
438 /// This is the method used to derive the paths of subfields in a directory structure,
439 /// when using the derive macro.
440 ///
441 /// # Example
442 ///
443 /// ```
444 /// use std::path::{Path, PathBuf};
445 /// use dir_structure::traits::vfs::PathType;
446 ///
447 /// let path = Path::new("some/dir");
448 /// let new_path = PathType::join_segment_str(path, "file.txt");
449 /// assert_eq!(new_path, PathBuf::from("some/dir/file.txt"));
450 /// ```
451 fn join_segment_str(&self, new_fragment: &str) -> Self::OwnedPath;
452
453 /// The error type returned when stripping a prefix fails.
454 type StripPrefixError: StdError + Send + Sync + 'static;
455
456 /// Strips the given base path from this path, returning the relative path if successful.
457 ///
458 /// # Example
459 ///
460 /// ```rust
461 /// use std::path::{Path, PathBuf};
462 /// use dir_structure::traits::vfs::PathType;
463 ///
464 /// let path = Path::new("some/dir/file.txt");
465 /// let base = Path::new("some");
466 /// let relative = PathType::strip_prefix(path, base).unwrap();
467 /// assert_eq!(relative, Path::new("dir/file.txt"));
468 ///
469 /// let base = Path::new("other");
470 /// let relative = PathType::strip_prefix(path, base);
471 /// assert!(relative.is_err());
472 /// ```
473 fn strip_prefix(&self, base: &Self) -> StdResult<&Self, Self::StripPrefixError>;
474
475 /// Converts this path to its owned version.
476 ///
477 /// # Example
478 ///
479 /// ```rust
480 /// use std::path::{Path, PathBuf};
481 /// use dir_structure::traits::vfs::PathType;
482 ///
483 /// let path = Path::new("some/dir/file.txt");
484 /// let owned = PathType::owned(path);
485 /// assert_eq!(owned, PathBuf::from("some/dir/file.txt"));
486 /// ```
487 fn owned(&self) -> Self::OwnedPath;
488}
489
490impl PathType for Path {
491 type OwnedPath = PathBuf;
492 type PathSegmentRef = OsStr;
493 type PathSegmentOwned = OsString;
494
495 fn parent(&self) -> Option<&Self> {
496 self.parent()
497 }
498
499 fn join(&self, new_fragment: impl AsRef<Self>) -> Self::OwnedPath {
500 Self::join(self, new_fragment.as_ref())
501 }
502
503 fn join_segment(&self, new_fragment: impl AsRef<Self::PathSegmentRef>) -> Self::OwnedPath {
504 Self::join(self, new_fragment.as_ref())
505 }
506
507 fn join_segment_str(&self, new_fragment: &str) -> Self::OwnedPath {
508 Self::join(self, new_fragment)
509 }
510
511 type StripPrefixError = path::StripPrefixError;
512
513 fn strip_prefix(&self, base: &Self) -> StdResult<&Self, Self::StripPrefixError> {
514 self.strip_prefix(base)
515 }
516
517 fn owned(&self) -> Self::OwnedPath {
518 self.to_path_buf()
519 }
520}
521
522/// A trait representing an owned path in a virtual file system.
523///
524/// This can be implemented for custom owned path types, allowing you to create [`Vfs`] implementations that
525/// work with owned path types other than [`PathBuf`]. [`OwnedPathType`] is implemented for the owned type,
526/// which is "linked" to the reference type via the associated type [`RefType`][OwnedPathType::RefType].
527///
528/// Once you have a value of that owned type (e.g., `MyPathBuf`), you can get a reference to the path type
529/// (e.g., `&MyPath`) via the [`AsRef`] trait, which is a super-trait of [`OwnedPathType`].
530///
531/// A couple of methods are provided to manipulate the path in place, such as inserting a new fragment
532/// at the front of the path, or pushing a new segment at the end of the path.
533pub trait OwnedPathType: Clone + AsRef<Self::RefType> + PartialEq + Send + Sync {
534 /// The reference type corresponding to this owned path type.
535 ///
536 /// This reference type must implement the [`PathType`] trait, and its associated
537 /// [`OwnedPath`][PathType::OwnedPath] must be `Self`.
538 type RefType: PathType<OwnedPath = Self> + ?Sized;
539
540 /// Returns the parent path, if it exists.
541 ///
542 /// Convenience method that calls the [`parent` method](PathType::parent) on the reference type.
543 ///
544 /// # Example
545 ///
546 /// ```rust
547 /// use std::path::{Path, PathBuf};
548 /// use dir_structure::traits::vfs::OwnedPathType;
549 ///
550 /// let path = PathBuf::from("some/dir/file.txt");
551 /// let parent = OwnedPathType::parent(&path);
552 /// assert_eq!(parent, Some(Path::new("some/dir")));
553 ///
554 /// let path = PathBuf::from("file.txt");
555 /// let parent = OwnedPathType::parent(&path);
556 /// assert_eq!(parent, Some(Path::new("")));
557 ///
558 /// let empty = PathBuf::from("");
559 /// let parent = OwnedPathType::parent(&empty);
560 /// assert_eq!(parent, None);
561 /// ```
562 fn parent(&self) -> Option<&Self::RefType> {
563 self.as_ref().parent()
564 }
565
566 /// Inserts a new path fragment at the front of this path.
567 ///
568 /// This is needed when reading a [`DirDescendants`][crate::dir_descendants::DirDescendants],
569 /// to prepend the base path to the relative paths of the entries found recursively.
570 ///
571 /// # Examples
572 ///
573 /// ```rust
574 /// use std::path::{Path, PathBuf};
575 /// use std::ffi::OsStr;
576 /// use dir_structure::traits::vfs::OwnedPathType;
577 ///
578 /// let mut path = PathBuf::from("dir/file.txt");
579 /// path.insert_in_front(OsStr::new("some"));
580 /// assert_eq!(path, PathBuf::from("some/dir/file.txt"));
581 ///
582 /// let mut path = PathBuf::from("file.txt");
583 /// path.insert_in_front(OsStr::new("some"));
584 /// assert_eq!(path, PathBuf::from("some/file.txt"));
585 ///
586 /// let mut path = PathBuf::from("");
587 /// path.insert_in_front(OsStr::new("some"));
588 /// assert_eq!(path, PathBuf::from("some"));
589 /// ```
590 fn insert_in_front(&mut self, new_fragment: &<Self::RefType as PathType>::PathSegmentRef);
591
592 /// Pushes a new path segment at the end of this path.
593 ///
594 /// # Example
595 ///
596 /// ```rust
597 /// use std::path::{Path, PathBuf};
598 /// use dir_structure::traits::vfs::OwnedPathType;
599 ///
600 /// let mut path = PathBuf::from("some/dir");
601 /// path.push_segment_str("file.txt");
602 /// assert_eq!(path, PathBuf::from("some/dir/file.txt"));
603 ///
604 /// let mut path = PathBuf::from("some/dir/");
605 /// path.push_segment_str("file.txt");
606 /// assert_eq!(path, PathBuf::from("some/dir/file.txt"));
607 /// ```
608 fn push_segment_str(&mut self, new_fragment: &str);
609}
610
611impl OwnedPathType for PathBuf {
612 type RefType = Path;
613
614 fn parent(&self) -> Option<&Self::RefType> {
615 self.as_path().parent()
616 }
617
618 fn insert_in_front(&mut self, new_fragment: &<Self::RefType as PathType>::PathSegmentRef) {
619 let mut new_path = PathBuf::from(new_fragment);
620 new_path.push(&self);
621 *self = new_path;
622 }
623
624 fn push_segment_str(&mut self, new_fragment: &str) {
625 self.push(new_fragment);
626 }
627}
628
629/// The type of a directory entry.
630#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
631#[cfg_attr(feature = "assert_eq", derive(assert_eq::AssertEq))]
632pub enum DirEntryKind {
633 /// A regular file.
634 File,
635 /// A directory.
636 Directory,
637}
638
639impl DirEntryKind {
640 /// Returns true if the entry is a file.
641 ///
642 /// # Examples
643 ///
644 /// ```
645 /// use dir_structure::traits::vfs::DirEntryKind;
646 ///
647 /// let entry = DirEntryKind::File;
648 /// assert!(entry.is_file());
649 ///
650 /// let entry = DirEntryKind::Directory;
651 /// assert!(!entry.is_file());
652 /// ```
653 pub fn is_file(self) -> bool {
654 matches!(self, DirEntryKind::File)
655 }
656
657 /// Returns true if the entry is a directory.
658 ///
659 /// # Examples
660 ///
661 /// ```
662 /// use dir_structure::traits::vfs::DirEntryKind;
663 ///
664 /// let entry = DirEntryKind::File;
665 /// assert!(!entry.is_dir());
666 ///
667 /// let entry = DirEntryKind::Directory;
668 /// assert!(entry.is_dir());
669 /// ```
670 pub fn is_dir(self) -> bool {
671 matches!(self, DirEntryKind::Directory)
672 }
673}
674
675/// Information about a directory entry.
676#[derive(Debug, Clone, PartialEq, Eq, Hash)]
677#[cfg_attr(feature = "assert_eq", derive(assert_eq::AssertEq))]
678pub struct DirEntryInfo<P: PathType + ?Sized> {
679 /// The name of the entry.
680 pub name: P::PathSegmentOwned,
681 /// The path of the entry.
682 pub path: P::OwnedPath,
683 /// The kind of the entry.
684 pub kind: DirEntryKind,
685}
686
687/// A trait for walking a directory.
688///
689/// Behaves similarly to an [`Iterator`] over Result<[`DirEntryInfo`], <Self::P as PathType>::OwnedPath>.
690pub trait DirWalker<'vfs>: 'vfs {
691 /// The path type used by this directory walker.
692 ///
693 /// This is the same as the path type used by the [`Vfs`] that created this walker.
694 type P: PathType + ?Sized;
695 /// Returns the next directory entry.
696 fn next(&mut self) -> Option<Result<DirEntryInfo<Self::P>, <Self::P as PathType>::OwnedPath>>;
697}