use std::error::Error;
use std::fmt::Debug;
use std::fmt::{self, Display};
pub mod prelude {
pub use crate::{
in_context_of, in_context_of_with, wrap_in_context_of, wrap_in_context_of_with,
ErrorContext, ErrorNoContext, MapErrorNoContext, ResultErrorWhile, ResultErrorWhileWrap,
ToErrorNoContext, WithContext, WrapContext,
};
}
pub trait WithContext<C> {
type ContextError;
fn with_context(self, context: C) -> Self::ContextError;
}
pub trait ResultErrorWhile<C> {
type ContextError;
fn error_while(self, context: C) -> Self::ContextError;
fn error_while_with<F>(self, context: F) -> Self::ContextError
where
F: FnOnce() -> C;
}
impl<O, E, C> ResultErrorWhile<C> for Result<O, E>
where
E: WithContext<C, ContextError = E>,
{
type ContextError = Self;
fn error_while(self, context: C) -> Self {
self.map_err(|e| e.with_context(context))
}
fn error_while_with<F>(self, context: F) -> Self::ContextError
where
F: FnOnce() -> C,
{
self.map_err(|e| e.with_context(context()))
}
}
#[derive(Debug)]
pub struct ErrorNoContext<E>(pub E);
impl<E> Display for ErrorNoContext<E>
where
E: Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl<E> Error for ErrorNoContext<E>
where
E: Error,
{
fn description(&self) -> &str {
self.0.description()
}
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.0.source()
}
}
impl<E, C> WithContext<C> for ErrorNoContext<E> {
type ContextError = ErrorContext<E, C>;
fn with_context(self, context: C) -> ErrorContext<E, C> {
ErrorContext {
error: self.0,
context,
}
}
}
pub trait ToErrorNoContext<T> {
fn to_root_cause(self) -> ErrorNoContext<T>;
}
impl<T> ToErrorNoContext<T> for T {
fn to_root_cause(self) -> ErrorNoContext<Self> {
ErrorNoContext(self)
}
}
pub trait MapErrorNoContext<O, E> {
fn map_error_context(self) -> Result<O, ErrorNoContext<E>>;
}
impl<O, E> MapErrorNoContext<O, E> for Result<O, E> {
fn map_error_context(self) -> Result<O, ErrorNoContext<E>> {
self.map_err(ToErrorNoContext::to_root_cause)
}
}
#[derive(Debug)]
pub struct ErrorContext<E, C> {
pub error: E,
pub context: C,
}
impl<E, C> Display for ErrorContext<E, C>
where
E: Display,
C: Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "while {} got error: {}", self.context, self.error)
}
}
impl<E, C> Error for ErrorContext<E, C>
where
E: Error,
C: Display + Debug,
{
fn description(&self) -> &str {
self.error.description()
}
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.error.source()
}
}
impl<E, C, C2> WithContext<C2> for ErrorContext<E, C> {
type ContextError = ErrorContext<ErrorContext<E, C>, C2>;
fn with_context(self, context: C2) -> ErrorContext<ErrorContext<E, C>, C2> {
ErrorContext {
error: self,
context,
}
}
}
pub trait WrapContext<C> {
type ContextError;
fn wrap_context(self, context: C) -> Self::ContextError;
}
impl<E, C> WrapContext<C> for E {
type ContextError = ErrorContext<E, C>;
fn wrap_context(self, context: C) -> ErrorContext<E, C> {
ErrorContext {
error: self,
context,
}
}
}
pub trait ResultErrorWhileWrap<O, E, C> {
fn wrap_error_while(self, context: C) -> Result<O, ErrorContext<E, C>>;
fn wrap_error_while_with<F>(self, context: F) -> Result<O, ErrorContext<E, C>>
where
F: FnOnce() -> C;
}
impl<O, E, C> ResultErrorWhileWrap<O, E, C> for Result<O, E>
where
E: WrapContext<C, ContextError = ErrorContext<E, C>>,
{
fn wrap_error_while(self, context: C) -> Result<O, ErrorContext<E, C>> {
self.map_err(|e| e.wrap_context(context))
}
fn wrap_error_while_with<F>(self, context: F) -> Result<O, ErrorContext<E, C>>
where
F: FnOnce() -> C,
{
self.map_err(|e| e.wrap_context(context()))
}
}
pub fn in_context_of<O, E, C, CE, B>(context: C, body: B) -> Result<O, CE>
where
E: WithContext<C, ContextError = CE>,
B: FnOnce() -> Result<O, E>,
{
body().map_err(|e| e.with_context(context))
}
pub fn in_context_of_with<O, E, C, CE, F, M, B>(context: F, body: B) -> Result<O, CE>
where
F: FnOnce() -> C,
E: WithContext<C, ContextError = CE>,
B: FnOnce() -> Result<O, E>,
{
body().map_err(|e| e.with_context(context()))
}
pub fn wrap_in_context_of<O, E, C, B>(context: C, body: B) -> Result<O, ErrorContext<E, C>>
where
E: WrapContext<C, ContextError = ErrorContext<E, C>>,
B: FnOnce() -> Result<O, E>,
{
body().map_err(|e| e.wrap_context(context))
}
pub fn wrap_in_context_of_with<O, E, C, F, B>(
context: F,
body: B,
) -> Result<O, ErrorContext<E, C>>
where
F: FnOnce() -> C,
E: WrapContext<C, ContextError = ErrorContext<E, C>>,
B: FnOnce() -> Result<O, E>,
{
body().map_err(|e| e.wrap_context(context()))
}
#[cfg(test)]
mod tests {
use super::prelude::*;
use assert_matches::*;
use std::io;
#[derive(Debug)]
enum FooError {
Foo {
context: Vec<String>,
},
Bar {
num: i32,
context: Vec<String>,
},
IoError {
error: io::Error,
context: Vec<String>,
},
}
impl WithContext<String> for FooError {
type ContextError = Self;
fn with_context(mut self, message: String) -> Self {
match self {
FooError::Foo {
ref mut context, ..
} => context.push(message),
FooError::Bar {
ref mut context, ..
} => context.push(message),
FooError::IoError {
ref mut context, ..
} => context.push(message),
}
self
}
}
impl From<ErrorContext<io::Error, String>> for FooError {
fn from(error_context: ErrorContext<io::Error, String>) -> FooError {
FooError::IoError {
error: error_context.error,
context: vec![error_context.context],
}
}
}
#[test]
fn test_in_type_context() {
let err: Result<(), FooError> = Err(FooError::Foo {
context: Vec::new(),
});
assert_matches!(err.error_while("doing stuff".to_string()), Err(FooError::Foo { context }) => assert_eq!(context, vec!["doing stuff".to_string()]));
let err: Result<(), FooError> = Err(FooError::Bar {
num: 1,
context: Vec::new(),
});
assert_matches!(err.error_while("doing stuff".to_string()), Err(FooError::Bar { num: 1, context }) => assert_eq!(context, vec!["doing stuff".to_string()]));
}
#[test]
fn test_wrapped_context() {
use std::io::{Error, ErrorKind};
let err: Result<(), Error> = Err(Error::new(ErrorKind::Other, "oh no!"));
assert_eq!(
err.wrap_error_while("doing stuff".to_string())
.unwrap_err()
.to_string(),
"while doing stuff got error: oh no!"
);
}
#[test]
fn test_wrapped_context_nested() {
use std::io::{Error, ErrorKind};
let err: Result<(), Error> = Err(Error::new(ErrorKind::Other, "file is no good"));
assert_eq!(
err.wrap_error_while("opening file".to_string())
.wrap_error_while("processing fish sticks".to_string())
.unwrap_err()
.to_string(),
"while processing fish sticks got error: while opening file got error: file is no good"
);
}
#[test]
fn test_in_context_of_type_context() {
let err = in_context_of("doing stuff".to_string(), || {
let err: Result<(), FooError> = Err(FooError::Foo {
context: Vec::new(),
});
err
});
assert_matches!(err.error_while("doing other stuff".to_string()), Err(FooError::Foo { context: c }) => assert_eq!(c, vec!["doing stuff".to_string(), "doing other stuff".to_string()]));
}
#[test]
fn test_wrap_in_context_of_type_context() {
fn foo() -> Result<(), FooError> {
wrap_in_context_of("doing stuff".to_string(), || {
Err(io::Error::new(io::ErrorKind::InvalidInput, "boom!"))?;
Ok(())
})?;
Ok(())
}
assert_matches!(foo().error_while("doing other stuff".to_string()), Err(FooError::IoError { context, .. }) => assert_eq!(context, vec!["doing stuff".to_string(), "doing other stuff".to_string()]));
}
#[test]
fn test_in_context_of_wrapped_context() {
use std::io::{Error, ErrorKind};
let err = in_context_of("opening file".to_string(), || {
let err: Result<(), Error> = Err(Error::new(ErrorKind::Other, "file is no good"));
err.map_error_context()
});
assert_eq!(
err.wrap_error_while("processing fish sticks".to_string())
.unwrap_err()
.to_string(),
"while processing fish sticks got error: while opening file got error: file is no good"
);
}
}