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