diff options
Diffstat (limited to 'crates/rebel-common/src/error.rs')
-rw-r--r-- | crates/rebel-common/src/error.rs | 119 |
1 files changed, 119 insertions, 0 deletions
diff --git a/crates/rebel-common/src/error.rs b/crates/rebel-common/src/error.rs new file mode 100644 index 0000000..ba25af4 --- /dev/null +++ b/crates/rebel-common/src/error.rs @@ -0,0 +1,119 @@ +//! 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: Display, F: FnOnce() -> C>(self, f: F) -> Self::Output; + + fn context<C: Display>(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<String>, +} + +impl Error { + pub fn new<D: Display>(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::<io::Error>()) + { + 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: Display, F: FnOnce() -> C>(self, f: F) -> Self::Output { + let Error { cause, mut context } = self; + context.push(f().to_string()); + Error { cause, context } + } +} + +impl<E> From<E> for Error +where + E: Into<io::Error>, +{ + fn from(err: E) -> Self { + Error::from_io(&err.into()) + } +} + +pub type Result<T> = result::Result<T, Error>; + +impl<T, E> Contextualizable for result::Result<T, E> +where + E: Into<Error>, +{ + type Output = Result<T>; + + fn with_context<C: Display, F: FnOnce() -> C>(self, f: F) -> Self::Output { + self.map_err(|err| err.into().with_context(f)) + } +} + +impl<T> Contextualizable for Option<T> { + type Output = Result<T>; + + fn with_context<C: Display, F: FnOnce() -> C>(self, f: F) -> Self::Output { + self.ok_or_else(|| Error::new(f())) + } +} |