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}