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(message: M, source: E) -> Self where M: Into, E: Into, { 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(message: M) -> impl FnOnce(E) -> Self where M: Into, E: Into, { |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(message_fn: F) -> impl FnOnce(E) -> Self where F: FnOnce() -> M, M: Into, E: Into, { |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(self, message: M) -> T where M: Into, T: From; } impl ErrorExt for E where E: Into, { fn fail(self, message: M) -> T where M: Into, T: From, { 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(self, message: M) -> Result where M: Into; fn fail_with(self, message_fn: F) -> Result where F: FnOnce() -> M, M: Into; } impl ResultExt for Result where E: Into, { type Ok = T; fn fail(self, message: M) -> Result where M: Into, { self.map_err(Failed::with_message(message)) } fn fail_with(self, message_fn: F) -> Result where F: FnOnce() -> M, M: Into, { self.map_err(Failed::with_message_from(message_fn)) } }