Skip to main content

bias_vfs/
error.rs

1//! Error and Result definitions
2
3use std::{error, fmt, io};
4
5/// The error type of this crate
6#[derive(Debug)]
7pub struct VfsError {
8    /// The path this error was encountered in
9    path: String,
10    /// The kind of error
11    kind: VfsErrorKind,
12    /// An optional human-readable string describing the context for this error
13    ///
14    /// If not provided, a generic context message is used
15    context: String,
16    /// The underlying error
17    cause: Option<Box<VfsError>>,
18}
19
20/// The only way to create a VfsError is via a VfsErrorKind
21///
22/// This conversion implements certain normalizations
23impl From<VfsErrorKind> for VfsError {
24    fn from(kind: VfsErrorKind) -> Self {
25        // Normalize the error here before we return it
26        let kind = match kind {
27            VfsErrorKind::IoError(io) => match io.kind() {
28                io::ErrorKind::NotFound => VfsErrorKind::FileNotFound,
29                // TODO: If MSRV changes to 1.53, enable this. Alternatively,
30                //      if it's possible to #[cfg] just this line, try that
31                // io::ErrorKind::Unsupported => VfsErrorKind::NotSupported,
32                _ => VfsErrorKind::IoError(io),
33            },
34            // Remaining kinda are passed through as-is
35            other => other,
36        };
37
38        Self {
39            // TODO (Techno): See if this could be checked at compile-time to make sure the VFS abstraction
40            //              never forgets to add a path. Might need a separate error type for FS impls vs VFS
41            path: "PATH NOT FILLED BY VFS LAYER".into(),
42            kind,
43            context: "An error occured".into(),
44            cause: None,
45        }
46    }
47}
48
49impl From<io::Error> for VfsError {
50    fn from(err: io::Error) -> Self {
51        Self::from(VfsErrorKind::IoError(err))
52    }
53}
54
55impl VfsError {
56    // Path filled by the VFS crate rather than the implementations
57    pub(crate) fn with_path(mut self, path: impl Into<String>) -> Self {
58        self.path = path.into();
59        self
60    }
61
62    pub fn with_context<C, F>(mut self, context: F) -> Self
63    where
64        C: fmt::Display + Send + Sync + 'static,
65        F: FnOnce() -> C,
66    {
67        self.context = context().to_string();
68        self
69    }
70
71    pub fn with_cause(mut self, cause: VfsError) -> Self {
72        self.cause = Some(Box::new(cause));
73        self
74    }
75
76    pub fn kind(&self) -> &VfsErrorKind {
77        &self.kind
78    }
79
80    pub fn path(&self) -> &String {
81        &self.path
82    }
83}
84
85impl fmt::Display for VfsError {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        write!(f, "{} for '{}': {}", self.context, self.path, self.kind())
88    }
89}
90
91impl error::Error for VfsError {
92    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
93        if let Some(cause) = &self.cause {
94            Some(cause)
95        } else {
96            None
97        }
98    }
99}
100
101/// The kinds of errors that can occur
102#[derive(Debug)]
103pub enum VfsErrorKind {
104    /// A generic I/O error
105    ///
106    /// Certain standard I/O errors are normalized to their VfsErrorKind counterparts
107    IoError(io::Error),
108
109    #[cfg(feature = "async-vfs")]
110    /// A generic async I/O error
111    AsyncIoError(io::Error),
112
113    /// The file or directory at the given path could not be found
114    FileNotFound,
115
116    /// The given path is invalid, e.g. because contains '.' or '..'
117    InvalidPath,
118
119    /// Generic error variant
120    Other(String),
121
122    /// There is already a directory at the given path
123    DirectoryExists,
124
125    /// There is already a file at the given path
126    FileExists,
127
128    /// Functionality not supported by this filesystem
129    NotSupported,
130}
131
132impl fmt::Display for VfsErrorKind {
133    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134        match self {
135            VfsErrorKind::IoError(cause) => {
136                write!(f, "IO error: {}", cause)
137            }
138            #[cfg(feature = "async-vfs")]
139            VfsErrorKind::AsyncIoError(cause) => {
140                write!(f, "Async IO error: {}", cause)
141            }
142            VfsErrorKind::FileNotFound => {
143                write!(f, "The file or directory could not be found")
144            }
145            VfsErrorKind::InvalidPath => {
146                write!(f, "The path is invalid")
147            }
148            VfsErrorKind::Other(message) => {
149                write!(f, "FileSystem error: {}", message)
150            }
151            VfsErrorKind::NotSupported => {
152                write!(f, "Functionality not supported by this filesystem")
153            }
154            VfsErrorKind::DirectoryExists => {
155                write!(f, "Directory already exists")
156            }
157            VfsErrorKind::FileExists => {
158                write!(f, "File already exists")
159            }
160        }
161    }
162}
163
164/// The result type of this crate
165pub type VfsResult<T> = std::result::Result<T, VfsError>;
166
167#[cfg(test)]
168mod tests {
169    use crate::error::VfsErrorKind;
170    use crate::{VfsError, VfsResult};
171
172    fn produce_vfs_result() -> VfsResult<()> {
173        Err(VfsError::from(VfsErrorKind::Other("Not a file".into())).with_path("foo"))
174    }
175
176    fn produce_anyhow_result() -> anyhow::Result<()> {
177        Ok(produce_vfs_result()?)
178    }
179
180    #[test]
181    fn anyhow_compatibility() {
182        let result = produce_anyhow_result().unwrap_err();
183        assert_eq!(
184            result.to_string(),
185            "An error occured for 'foo': FileSystem error: Not a file"
186        )
187    }
188}