diff options
Diffstat (limited to 'src/error/failed.rs')
| -rw-r--r-- | src/error/failed.rs | 155 |
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)) + } +} |
