summaryrefslogtreecommitdiffstats
path: root/crates/rebel-common/src/error.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/rebel-common/src/error.rs')
-rw-r--r--crates/rebel-common/src/error.rs119
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()))
+ }
+}