summaryrefslogtreecommitdiff
path: root/src/error/failed.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/error/failed.rs')
-rw-r--r--src/error/failed.rs155
1 files changed, 155 insertions, 0 deletions
diff --git a/src/error/failed.rs b/src/error/failed.rs
new file mode 100644
index 0000000..4d55552
--- /dev/null
+++ b/src/error/failed.rs
@@ -0,0 +1,155 @@
+use std::{borrow::Cow, error::Error, fmt};
+
+use super::BoxedError;
+
+// Don't bother allocating a String for each error if we have a static error message, which is
+// a fairly common use case.
+pub type Message = Cow<'static, str>;
+
+// Intended to represent a generic failure caused by some underlying cause, tagged with a
+// context-appropriate message to help operators and developers diagnose and repair the issue.
+// Unlike `Internal`, this error type has no default HTTP representation - it's not meant for that
+// use case: the intended audience for a `Failed` and its underlying cause is the service operator,
+// not the conversational users or client developers.
+//
+// The underlying cause will be used as the `Failed` error's `source()`, and the provided message
+// will be used as its `Display` implementation.
+//
+// Ways to make a `Failed`:
+//
+// * Call `ResultExt::fail` or `ResultExt::fail_with` on a result to convert its error type (most
+// preferred).
+// * Call `ErrorExt::fail` to convert an existing error into `Failed`.
+// * Call `Result::map_err` with `Failed::with_message` or `Failed::with_message_from` to convert
+// its error type to `Failed`.
+// * Call `Failed::new` with a message and an existing error (least preferred).
+//
+// Ways to use `Failed`:
+//
+// * As the error type of a `Result` return value, when all errors that can arise in a function
+// are of interest to the operator only but there are multiple failure types to contend with.
+// * As the error type of a `Result` return value, when there's only one error that can arise but
+// you want to annotate that error with a contextual message, like what the function was doing at
+// the time.
+// * As a variant in am error enum, when _some_ errors are only of operator interest but other
+// errors may need to be broken out for matching, or returned to clients.
+//
+// When not to use `Failed`:
+//
+// * When the caller needs to match on the specific error type. `Failed`, by design, assumes that
+// nothing up the call stack will care about the structure of the error, and while you _can_ get
+// at the underlying error to check what you ahve by going through the `source()` chain, it's
+// tricky to do well. If the specific error is relevant to the caller, surface it through the
+// error type, instead.
+#[derive(Debug)]
+pub struct Failed {
+ source: BoxedError,
+ message: Message,
+}
+
+impl Failed {
+ pub fn new<M, E>(message: M, source: E) -> Self
+ where
+ M: Into<Message>,
+ E: Into<BoxedError>,
+ {
+ Self {
+ source: source.into(),
+ message: message.into(),
+ }
+ }
+}
+
+// Adapters for use with `Result::map_err`.
+impl Failed {
+ // The returned adaptor wraps an error with `Failed`, using the provided message.
+ pub fn with_message<M, E>(message: M) -> impl FnOnce(E) -> Self
+ where
+ M: Into<Message>,
+ E: Into<BoxedError>,
+ {
+ |source| Self::new(message, source)
+ }
+
+ // The returned adaptor wraps an error with `Failed`, using a message from the provided message
+ // callback.
+ pub fn with_message_from<F, M, E>(message_fn: F) -> impl FnOnce(E) -> Self
+ where
+ F: FnOnce() -> M,
+ M: Into<Message>,
+ E: Into<BoxedError>,
+ {
+ |source| Self::new(message_fn(), source)
+ }
+}
+
+impl Error for Failed {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ Some(self.source.as_ref())
+ }
+}
+
+impl fmt::Display for Failed {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.message.fmt(f)
+ }
+}
+
+pub trait ErrorExt {
+ // Convenience for `Failed::new(message, err).into()`, when wrapping an existing error into
+ // `Failed`.
+ fn fail<M, T>(self, message: M) -> T
+ where
+ M: Into<Message>,
+ T: From<Failed>;
+}
+
+impl<E> ErrorExt for E
+where
+ E: Into<BoxedError>,
+{
+ fn fail<M, T>(self, message: M) -> T
+ where
+ M: Into<Message>,
+ T: From<Failed>,
+ {
+ Failed::new(message, self).into()
+ }
+}
+
+pub trait ResultExt {
+ type Ok;
+
+ // Convenience for `.map_err(Failed::with_message(message))`, when a result's error type can be
+ // collapsed into `Failed`.
+ fn fail<M>(self, message: M) -> Result<Self::Ok, Failed>
+ where
+ M: Into<Message>;
+
+ fn fail_with<F, M>(self, message_fn: F) -> Result<Self::Ok, Failed>
+ where
+ F: FnOnce() -> M,
+ M: Into<Message>;
+}
+
+impl<T, E> ResultExt for Result<T, E>
+where
+ E: Into<BoxedError>,
+{
+ type Ok = T;
+
+ fn fail<M>(self, message: M) -> Result<T, Failed>
+ where
+ M: Into<Message>,
+ {
+ self.map_err(Failed::with_message(message))
+ }
+
+ fn fail_with<F, M>(self, message_fn: F) -> Result<T, Failed>
+ where
+ F: FnOnce() -> M,
+ M: Into<Message>,
+ {
+ self.map_err(Failed::with_message_from(message_fn))
+ }
+}