1use std::{error, fmt, io};
4
5#[derive(Debug)]
7pub struct VfsError {
8 path: String,
10 kind: VfsErrorKind,
12 context: String,
16 cause: Option<Box<VfsError>>,
18}
19
20impl From<VfsErrorKind> for VfsError {
24 fn from(kind: VfsErrorKind) -> Self {
25 let kind = match kind {
27 VfsErrorKind::IoError(io) => match io.kind() {
28 io::ErrorKind::NotFound => VfsErrorKind::FileNotFound,
29 _ => VfsErrorKind::IoError(io),
33 },
34 other => other,
36 };
37
38 Self {
39 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 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#[derive(Debug)]
103pub enum VfsErrorKind {
104 IoError(io::Error),
108
109 #[cfg(feature = "async-vfs")]
110 AsyncIoError(io::Error),
112
113 FileNotFound,
115
116 InvalidPath,
118
119 Other(String),
121
122 DirectoryExists,
124
125 FileExists,
127
128 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
164pub 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}