Skip to content

Commit 68076bb

Browse files
committed
Improve error ergonomics for end users
Allows end users of VFS to match against ErrorKind directly without having to drill down through the WithContext variant Also offers a normalization for I/O errors to ensure the corresponding VFS Error kind is used rather than a generic I/O Error
1 parent 9728b20 commit 68076bb

File tree

8 files changed

+301
-250
lines changed

8 files changed

+301
-250
lines changed

src/error.rs

+103-75
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,134 @@
11
//! Error and Result definitions
22
3-
use std::fmt::{Display, Formatter};
4-
use std::io::Error;
3+
use std::{error, fmt, io};
54

65
/// The error type of this crate
76
#[derive(Debug)]
8-
pub enum VfsError {
9-
/// A generic IO error
10-
IoError(std::io::Error),
7+
pub 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+
}
1119

12-
/// The file or directory at the given path could not be found
13-
FileNotFound {
14-
/// The path of the file not found
15-
path: String,
16-
},
20+
/// The only way to create a VfsError is via a VfsErrorKind
21+
///
22+
/// This conversion implements certain normalizations
23+
impl 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+
io::ErrorKind::Unsupported => VfsErrorKind::NotSupported,
30+
_ => VfsErrorKind::IoError(io),
31+
},
32+
// Remaining kinda are passed through as-is
33+
other => other,
34+
};
1735

18-
/// The given path is invalid, e.g. because contains '.' or '..'
19-
InvalidPath {
20-
/// The invalid path
21-
path: String,
22-
},
36+
Self {
37+
// TODO (Techno): See if this could be checked at compile-time to make sure the VFS abstraction
38+
// never forgets to add a path. Might need a separate error type for FS impls vs VFS
39+
path: "PATH NOT FILLED BY VFS LAYER".into(),
40+
kind,
41+
context: "An error occured".into(),
42+
cause: None,
43+
}
44+
}
45+
}
2346

24-
/// Generic error variant
25-
Other {
26-
/// The generic error message
27-
message: String,
28-
},
29-
30-
/// Generic error context, used for adding context to an error (like a path)
31-
WithContext {
32-
/// The context error message
33-
context: String,
34-
/// The underlying error
35-
cause: Box<VfsError>,
36-
},
47+
impl From<io::Error> for VfsError {
48+
fn from(err: io::Error) -> Self {
49+
Self::from(VfsErrorKind::IoError(err))
50+
}
51+
}
3752

38-
/// Functionality not supported by this filesystem
39-
NotSupported,
53+
impl VfsError {
54+
// Path filled by the VFS crate rather than the implementations
55+
pub(crate) fn with_path(mut self, path: impl Into<String>) -> Self {
56+
self.path = path.into();
57+
self
58+
}
59+
60+
pub fn with_context<C, F>(mut self, context: F) -> Self
61+
where
62+
C: fmt::Display + Send + Sync + 'static,
63+
F: FnOnce() -> C,
64+
{
65+
self.context = context().to_string();
66+
self
67+
}
68+
69+
pub fn with_cause(mut self, cause: VfsError) -> Self {
70+
self.cause = Some(Box::new(cause));
71+
self
72+
}
73+
74+
pub fn kind(&self) -> &VfsErrorKind {
75+
&self.kind
76+
}
77+
}
78+
79+
impl fmt::Display for VfsError {
80+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81+
write!(f, "{} for '{}': {}", self.context, self.path, self.kind())
82+
}
4083
}
4184

42-
impl std::error::Error for VfsError {
43-
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
44-
if let Self::WithContext { cause, .. } = self {
85+
impl error::Error for VfsError {
86+
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
87+
if let Some(cause) = &self.cause {
4588
Some(cause)
4689
} else {
4790
None
4891
}
4992
}
5093
}
5194

52-
impl From<String> for VfsError {
53-
fn from(message: String) -> Self {
54-
VfsError::Other { message }
55-
}
56-
}
95+
/// The kinds of errors that can occur
96+
#[derive(Debug)]
97+
pub enum VfsErrorKind {
98+
/// A generic I/O error
99+
///
100+
/// Certain standard I/O errors are normalized to their VfsErrorKind counterparts
101+
IoError(io::Error),
57102

58-
impl From<std::io::Error> for VfsError {
59-
fn from(cause: Error) -> Self {
60-
VfsError::IoError(cause)
61-
}
103+
/// The file or directory at the given path could not be found
104+
FileNotFound,
105+
106+
/// The given path is invalid, e.g. because contains '.' or '..'
107+
InvalidPath,
108+
109+
/// Generic error variant
110+
Other(String),
111+
112+
/// Functionality not supported by this filesystem
113+
NotSupported,
62114
}
63115

64-
impl std::fmt::Display for VfsError {
65-
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
116+
impl fmt::Display for VfsErrorKind {
117+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66118
match self {
67-
VfsError::IoError(cause) => {
119+
VfsErrorKind::IoError(cause) => {
68120
write!(f, "IO error: {}", cause)
69121
}
70-
VfsError::FileNotFound { path } => {
71-
write!(f, "The file or directory `{}` could not be found", path)
122+
VfsErrorKind::FileNotFound => {
123+
write!(f, "The file or directory could not be found")
72124
}
73-
VfsError::InvalidPath { path } => {
74-
write!(f, "The path `{}` is invalid", path)
125+
VfsErrorKind::InvalidPath => {
126+
write!(f, "The path is invalid")
75127
}
76-
VfsError::Other { message } => {
128+
VfsErrorKind::Other(message) => {
77129
write!(f, "FileSystem error: {}", message)
78130
}
79-
VfsError::WithContext { context, cause } => {
80-
write!(f, "{}, cause: {}", context, cause)
81-
}
82-
VfsError::NotSupported => {
131+
VfsErrorKind::NotSupported => {
83132
write!(f, "Functionality not supported by this filesystem")
84133
}
85134
}
@@ -88,24 +137,3 @@ impl std::fmt::Display for VfsError {
88137

89138
/// The result type of this crate
90139
pub type VfsResult<T> = std::result::Result<T, VfsError>;
91-
92-
/// Result extension trait to add context information
93-
pub(crate) trait VfsResultExt<T> {
94-
fn with_context<C, F>(self, f: F) -> VfsResult<T>
95-
where
96-
C: Display + Send + Sync + 'static,
97-
F: FnOnce() -> C;
98-
}
99-
100-
impl<T> VfsResultExt<T> for VfsResult<T> {
101-
fn with_context<C, F>(self, context: F) -> VfsResult<T>
102-
where
103-
C: Display + Send + Sync + 'static,
104-
F: FnOnce() -> C,
105-
{
106-
self.map_err(|error| VfsError::WithContext {
107-
context: context().to_string(),
108-
cause: Box::new(error),
109-
})
110-
}
111-
}

src/filesystem.rs

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! The filesystem trait definitions needed to implement new virtual filesystems
22
3-
use crate::{SeekAndRead, VfsError, VfsMetadata, VfsPath, VfsResult};
3+
use crate::error::VfsErrorKind;
4+
use crate::{SeekAndRead, VfsMetadata, VfsPath, VfsResult};
45
use std::fmt::Debug;
56
use std::io::Write;
67

@@ -34,16 +35,16 @@ pub trait FileSystem: Debug + Sync + Send + 'static {
3435
/// Removes the directory at this path
3536
fn remove_dir(&self, path: &str) -> VfsResult<()>;
3637
/// Copies the src path to the destination path within the same filesystem (optional)
37-
fn copy_file(&self, _src: &str, _dest: &str) -> VfsResult<()> {
38-
Err(VfsError::NotSupported)
38+
fn copy_file(&self, _src: &str, dest: &str) -> VfsResult<()> {
39+
Err(VfsErrorKind::NotSupported.into())
3940
}
4041
/// Moves the src path to the destination path within the same filesystem (optional)
41-
fn move_file(&self, _src: &str, _dest: &str) -> VfsResult<()> {
42-
Err(VfsError::NotSupported)
42+
fn move_file(&self, _src: &str, dest: &str) -> VfsResult<()> {
43+
Err(VfsErrorKind::NotSupported.into())
4344
}
4445
/// Moves the src directory to the destination path within the same filesystem (optional)
45-
fn move_dir(&self, _src: &str, _dest: &str) -> VfsResult<()> {
46-
Err(VfsError::NotSupported)
46+
fn move_dir(&self, _src: &str, dest: &str) -> VfsResult<()> {
47+
Err(VfsErrorKind::NotSupported.into())
4748
}
4849
}
4950

src/impls/altroot.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! A file system with its root in a particular directory of another filesystem
22
3-
use crate::{FileSystem, SeekAndRead, VfsError, VfsMetadata, VfsPath, VfsResult};
3+
use crate::{error::VfsErrorKind, FileSystem, SeekAndRead, VfsMetadata, VfsPath, VfsResult};
44
use std::io::Write;
55

66
/// Similar to a chroot but done purely by path manipulation
@@ -78,7 +78,7 @@ impl FileSystem for AltrootFS {
7878

7979
fn copy_file(&self, src: &str, dest: &str) -> VfsResult<()> {
8080
if dest.is_empty() {
81-
return Err(VfsError::NotSupported);
81+
return Err(VfsErrorKind::NotSupported.into());
8282
}
8383
self.path(src)?.copy_file(&self.path(dest)?)
8484
}

0 commit comments

Comments
 (0)