1use std::fmt;
13use std::io;
14use std::path::Path;
15
16pub struct Error {
29 inner: Box<Inner>,
31}
32
33pub type Result<T> = std::result::Result<T, Error>;
34
35#[derive(Default)]
36struct Inner {
37 sources: Vec<Box<dyn std::error::Error + Send + Sync + 'static>>,
38 messages: Vec<String>,
39 is_corruption: bool,
40 io_error_kind: Option<io::ErrorKind>,
41}
42
43impl Error {
44 pub fn is_corruption(&self) -> bool {
79 self.inner.is_corruption
80 }
81
82 pub fn io_error_kind(&self) -> io::ErrorKind {
83 self.inner.io_error_kind.unwrap_or(io::ErrorKind::Other)
84 }
85
86 pub(crate) fn message(mut self, message: impl ToString) -> Self {
90 self.inner.messages.push(message.to_string());
91 self
92 }
93
94 pub(crate) fn source(self, source: impl std::error::Error + Send + Sync + 'static) -> Self {
95 self.source_dyn(Box::new(source))
96 }
97
98 fn source_dyn(mut self, source: Box<dyn std::error::Error + Send + Sync + 'static>) -> Self {
99 if let Some(err) = source.downcast_ref::<Error>() {
101 if err.is_corruption() {
102 self = self.mark_corruption();
103 }
104 }
105
106 self.inner.sources.push(source);
107 self
108 }
109
110 pub(crate) fn mark_corruption(mut self) -> Self {
111 self.inner.is_corruption = true;
112 self
113 }
114
115 pub(crate) fn blank() -> Self {
116 Error {
117 inner: Default::default(),
118 }
119 }
120
121 #[inline(never)]
124 pub(crate) fn programming(message: impl ToString) -> Self {
125 Self::blank().message(format!("ProgrammingError: {}", message.to_string()))
126 }
127
128 #[inline(never)]
132 pub(crate) fn corruption(path: &Path, message: impl ToString) -> Self {
133 let message = format!("{:?}: {}", path, message.to_string());
134 Self::blank().mark_corruption().message(message)
135 }
136
137 #[inline(never)]
141 pub(crate) fn path(path: &Path, message: impl ToString) -> Self {
142 let message = format!("{:?}: {}", path, message.to_string());
143 Self::blank().message(message)
144 }
145
146 #[inline(never)]
148 pub(crate) fn wrap(
149 err: Box<dyn std::error::Error + Send + Sync + 'static>,
150 message: impl LazyToString,
151 ) -> Self {
152 Self::blank()
153 .message(message.to_string_costly())
154 .source_dyn(err)
155 }
156}
157
158impl From<&str> for Error {
160 fn from(s: &str) -> Self {
161 Self::blank().message(s)
162 }
163}
164
165impl<E: std::error::Error + Send + Sync + 'static> From<(&str, E)> for Error {
167 fn from(s: (&str, E)) -> Self {
168 Self::blank().message(s.0).source(s.1)
169 }
170}
171
172impl fmt::Display for Error {
173 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
174 let mut lines = Vec::new();
175 for message in &self.inner.messages {
176 lines.push(message.to_string());
177 }
178 if !self.inner.sources.is_empty() {
179 lines.push(format!("Caused by {} errors:", self.inner.sources.len()));
180 for source in &self.inner.sources {
181 lines.push(indent(format!("{}", source), 2, '-'));
182 }
183 }
184 write!(f, "{}", lines.join("\n"))
185 }
186}
187
188impl fmt::Debug for Error {
189 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
190 let mut lines = Vec::new();
191 for message in &self.inner.messages {
192 lines.push(message.to_string());
193 }
194 if self.is_corruption() {
195 lines.push("(This error is considered as a data corruption)".to_string())
196 }
197 if !self.inner.sources.is_empty() {
198 lines.push(format!("Caused by {} errors:", self.inner.sources.len()));
199 for source in &self.inner.sources {
200 lines.push(indent(format!("{:?}", source), 2, '-'));
201 }
202 }
203 write!(f, "{}", lines.join("\n"))
204 }
205}
206
207fn indent(s: String, spaces: usize, first_line_prefix: char) -> String {
208 if spaces == 0 {
209 s
210 } else {
211 format!(
212 "{}{}{}",
213 first_line_prefix,
214 " ".repeat(spaces - 1),
215 s.lines()
216 .collect::<Vec<_>>()
217 .join(&format!("\n{}", " ".repeat(spaces)))
218 )
219 }
220}
221
222pub(crate) trait ResultExt<T> {
223 fn corruption(self) -> Self;
225
226 fn context<S: LazyToString>(self, message: S) -> Self;
228
229 fn source<E: std::error::Error + Send + Sync + 'static>(self, source: E) -> Self;
231}
232
233impl<T> ResultExt<T> for Result<T> {
234 fn corruption(self) -> Self {
235 self.map_err(|err| err.mark_corruption())
236 }
237
238 fn context<S: LazyToString>(self, message: S) -> Self {
239 self.map_err(|err| err.message(message.to_string_costly()))
240 }
241
242 fn source<E: std::error::Error + Send + Sync + 'static>(self, source: E) -> Self {
243 self.map_err(|err| err.source(source))
244 }
245}
246
247impl std::error::Error for Error {
248 }
254
255pub(crate) trait IoResultExt<T> {
256 fn context<TS: LazyToString>(self, path: &Path, message: TS) -> Result<T>;
264
265 fn infallible(self) -> Result<T>;
267}
268
269impl<T> IoResultExt<T> for std::io::Result<T> {
270 fn context<TS: LazyToString>(self, path: &Path, message: TS) -> Result<T> {
271 self.map_err(|err| {
272 use std::io::ErrorKind;
273 let kind = err.kind().clone();
274 let corruption = match kind {
275 ErrorKind::UnexpectedEof | ErrorKind::InvalidData => true,
279 _ => false,
280 };
281 let is_eperm = kind == ErrorKind::PermissionDenied;
282
283 let mut err = Error::blank().source(err).message(format!(
284 "{:?}: {}",
285 path,
286 message.to_string_costly()
287 ));
288 if corruption {
289 err = err.mark_corruption();
290 }
291 err.inner.io_error_kind = Some(kind);
292
293 if is_eperm {
295 #[cfg(unix)]
296 #[allow(clippy::redundant_closure_call)]
297 {
298 let add_stat_context = |path: &Path, err: Error| {
299 use std::os::unix::fs::MetadataExt;
300 if let Ok(meta) = path.metadata() {
301 let msg = format!(
302 "stat({:?}) = dev:{} ino:{} mode:0o{:o} uid:{} gid:{} mtime:{}",
303 path,
304 meta.dev(),
305 meta.ino(),
306 meta.mode(),
307 meta.uid(),
308 meta.gid(),
309 meta.mtime()
310 );
311 err.message(msg)
312 } else {
313 err
314 }
315 };
316 err = add_stat_context(path, err);
317 if let Some(parent) = path.parent() {
319 err = add_stat_context(parent, err);
320 }
321 }
322 }
323
324 err
325 })
326 }
327
328 fn infallible(self) -> Result<T> {
329 self.map_err(|err| Error::blank().source(err).message("Unexpected failure"))
330 }
331}
332
333pub(crate) trait LazyToString {
334 fn to_string_costly(&self) -> String;
335}
336
337impl LazyToString for &'static str {
339 fn to_string_costly(&self) -> String {
340 (*self).to_string()
341 }
342}
343
344impl<F: Fn() -> String> LazyToString for F {
345 fn to_string_costly(&self) -> String {
346 self()
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353
354 #[test]
355 fn test_error_format() {
356 let mut e = Error::blank();
357
358 assert_eq!(format!("{}", &e), "");
359
360 e = e.message("Error Message 1");
363 e = e.message("Error Message 2");
364 assert_eq!(
365 format!("{}", &e),
366 r#"Error Message 1
367Error Message 2"#
368 );
369
370 e = e.source(Error::blank().message("Inner Error 1"));
372 e = e.source(
373 Error::blank()
374 .message("Inner Error 2")
375 .source(Error::blank().message("Nested Error 1")),
376 );
377 assert_eq!(
378 format!("{}", &e),
379 r#"Error Message 1
380Error Message 2
381Caused by 2 errors:
382- Inner Error 1
383- Inner Error 2
384 Caused by 1 errors:
385 - Nested Error 1"#
386 );
387
388 e = e.mark_corruption();
390 assert_eq!(
391 format!("{:?}", &e),
392 r#"Error Message 1
393Error Message 2
394(This error is considered as a data corruption)
395Caused by 2 errors:
396- Inner Error 1
397- Inner Error 2
398 Caused by 1 errors:
399 - Nested Error 1"#
400 );
401 }
402
403 #[test]
404 fn test_result_ext() {
405 let result: Result<()> = Err(Error::blank()).corruption();
406 assert!(result.unwrap_err().is_corruption());
407 }
408
409 #[test]
410 fn test_inherit_corruption() {
411 assert!(!Error::blank().is_corruption());
412 assert!(!Error::blank().source(Error::blank()).is_corruption());
413 assert!(
414 Error::blank()
415 .source(Error::blank().mark_corruption())
416 .is_corruption()
417 );
418 assert!(
419 Error::blank()
420 .source(Error::blank().source(Error::blank().mark_corruption()))
421 .is_corruption()
422 );
423 }
424
425 #[test]
426 fn test_io_result_ext() {
427 let err = io_result().context(Path::new("a.txt"), "cannot open for reading");
428 assert_eq!(
429 format!("{}", err.unwrap_err()),
430 r#""a.txt": cannot open for reading
431Caused by 1 errors:
432- io::Error: something wrong happened"#
433 );
434
435 let name = "b.txt";
436 let err = io_result().context(Path::new(&name), || format!("cannot open {}", &name));
437 assert_eq!(
438 format!("{}", err.unwrap_err()),
439 r#""b.txt": cannot open b.txt
440Caused by 1 errors:
441- io::Error: something wrong happened"#
442 );
443 }
444
445 fn io_result() -> std::io::Result<()> {
446 Err(std::io::Error::new(
447 std::io::ErrorKind::Other,
448 "io::Error: something wrong happened",
449 ))
450 }
451}