//! Serializable errors with context use std::{error::Error as _, fmt::Display, io, result}; use serde::{Deserialize, Serialize}; pub trait Contextualizable: Sized { type Output; fn with_context C>(self, f: F) -> Self::Output; fn context(self, c: C) -> Self::Output { self.with_context(|| c) } } #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub enum ErrorCause { Code(i32), String(String), } impl Display for ErrorCause { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ErrorCause::Code(code) => io::Error::from_raw_os_error(*code).fmt(f), ErrorCause::String(string) => f.write_str(string), } } } #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct Error { pub cause: ErrorCause, pub context: Vec, } impl Error { pub fn new(cause: D) -> Self { Error { cause: ErrorCause::String(cause.to_string()), context: Vec::new(), } } pub fn from_io(err: &io::Error) -> Self { if let Some(source) = err .source() .and_then(|source| source.downcast_ref::()) { return Error::from_io(source).context(err.to_string()); } let cause = match err.raw_os_error() { Some(code) => ErrorCause::Code(code), None => ErrorCause::String(err.to_string()), }; Error { cause, context: vec![], } } } impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("Error: ")?; let mut it = self.context.iter().rev(); if let Some(ctx) = it.next() { write!(f, "{}\n\nCaused by:\n ", ctx)?; for ctx in it { write!(f, "{}\n ", ctx)?; } } self.cause.fmt(f) } } impl Contextualizable for Error { type Output = Error; fn with_context C>(self, f: F) -> Self::Output { let Error { cause, mut context } = self; context.push(f().to_string()); Error { cause, context } } } impl From for Error where E: Into, { fn from(err: E) -> Self { Error::from_io(&err.into()) } } pub type Result = result::Result; impl Contextualizable for result::Result where E: Into, { type Output = Result; fn with_context C>(self, f: F) -> Self::Output { self.map_err(|err| err.into().with_context(f)) } } impl Contextualizable for Option { type Output = Result; fn with_context C>(self, f: F) -> Self::Output { self.ok_or_else(|| Error::new(f())) } }