anyerr 0.1.1

Dynamic error library with rich error wrapping and context support
Documentation
use crate::context::{AbstractContext, Context};
use crate::converter::Convertable;
use crate::core::{AnyError, AnyErrorBuilder};
use crate::kind::Kind;
use crate::overlay::{Applicable, Intermediate, Overlay};

impl<C, K> Overlay for AnyError<C, K>
where
    C: AbstractContext,
    K: Kind,
{
    type Output = Self;

    type Intermediate = IntermediateAnyError<C, K>;
}

/// The final [`AnyError`] in a intermediate state.
pub struct IntermediateAnyError<C, K>
where
    C: AbstractContext,
    K: Kind,
{
    builder: AnyErrorBuilder<C, K>,
}

impl<C, K> From<AnyErrorBuilder<C, K>> for IntermediateAnyError<C, K>
where
    C: AbstractContext,
    K: Kind,
{
    fn from(builder: AnyErrorBuilder<C, K>) -> Self {
        Self { builder }
    }
}

impl<C, K> From<IntermediateAnyError<C, K>> for AnyError<C, K>
where
    C: AbstractContext,
    K: Kind,
{
    fn from(value: IntermediateAnyError<C, K>) -> Self {
        value.builder.build()
    }
}

impl<C, K> Intermediate for IntermediateAnyError<C, K>
where
    C: AbstractContext,
    K: Kind,
{
    type Output = AnyError<C, K>;

    fn build(self) -> Self::Output {
        self.into()
    }
}

impl<C, K> Applicable<AnyError<C, K>> for String
where
    C: AbstractContext,
    K: Kind,
{
    type Output = IntermediateAnyError<C, K>;

    /// Makes a new [`IntermediateAnyError`] which wraps `target` and sets
    /// its error message to `self`.
    fn apply(self, target: AnyError<C, K>) -> Self::Output {
        AnyError::builder().message(self).source(target).into()
    }
}

impl<C, K> Applicable<AnyError<C, K>> for &str
where
    C: AbstractContext,
    K: Kind,
{
    type Output = IntermediateAnyError<C, K>;

    /// Makes a new [`IntermediateAnyError`] which wraps `target` and sets
    /// its error message to `self`.
    fn apply(self, target: AnyError<C, K>) -> Self::Output {
        AnyError::builder().message(self).source(target).into()
    }
}

impl<C, K, S> Applicable<AnyError<C, K>> for (S, K)
where
    C: AbstractContext,
    K: Kind,
    S: Into<String>,
{
    type Output = IntermediateAnyError<C, K>;

    /// Makes a new [`IntermediateAnyError`] which wraps `target` and sets
    /// its error message and error kind to `self`'s corresponding components.
    fn apply(self, target: AnyError<C, K>) -> Self::Output {
        AnyError::builder()
            .message(self.0)
            .kind(self.1)
            .source(target)
            .into()
    }
}

impl<C, K, Q, R> Applicable<IntermediateAnyError<C, K>> for (Q, R)
where
    C: Context,
    K: Kind,
    Q: Into<C::Key>,
    R: Convertable<C::Converter, C::Value>,
{
    type Output = IntermediateAnyError<C, K>;

    /// Add context information represented as a key-value pairs to `target`
    /// using `self`'s components.
    fn apply(self, target: IntermediateAnyError<C, K>) -> Self::Output {
        target.builder.context(self.0, self.1).into()
    }
}

#[cfg(test)]
mod tests {
    use std::error::Error;

    use crate::context::LiteralKeyStringMapContext;
    use crate::kind::DefaultErrorKind;

    use super::*;

    type ErrKind = DefaultErrorKind;
    type DefaultAnyError = AnyError<LiteralKeyStringMapContext, ErrKind>;

    #[test]
    fn any_error_overlay_succeeds_when_message_is_given() {
        let source = DefaultAnyError::minimal("source error");
        let err = source.overlay(String::from("wrapper error")).build();
        assert_eq!(err.to_string(), "wrapper error");
        assert_eq!(err.source().unwrap().to_string(), "source error");

        let source = DefaultAnyError::minimal("source error");
        let err = source.overlay("wrapper error").build();
        assert_eq!(err.to_string(), "wrapper error");
        assert_eq!(err.source().unwrap().to_string(), "source error");
    }

    #[test]
    fn any_error_overlay_succeeds_when_message_and_kind_are_given() {
        let source = DefaultAnyError::minimal("source error");
        let err = source
            .overlay(("wrapper error", ErrKind::ValueValidation))
            .build();
        assert_eq!(err.to_string(), "wrapper error");
        assert_eq!(err.kind(), ErrKind::ValueValidation);
        assert_eq!(err.source().unwrap().to_string(), "source error");
    }

    #[test]
    fn intermediate_any_error_context_succeeds() {
        let source = DefaultAnyError::minimal("source error");
        let err = source
            .overlay("wrapper error")
            .context("key", "value")
            .build();
        assert_eq!(err.to_string(), "wrapper error");
        assert_eq!(err.get("key"), Some("\"value\""));
    }

    #[test]
    fn intermediate_any_error_into_any_error_succeeds_with_try_operator() {
        fn source_error_func() -> Result<(), DefaultAnyError> {
            Err(AnyError::minimal("source error"))
        }
        fn wrapper_error_func() -> Result<(), DefaultAnyError> {
            source_error_func().map_err(|err| err.overlay("wrapper error"))?;
            Ok(())
        }
        let err = wrapper_error_func().unwrap_err();
        assert_eq!(err.to_string(), "wrapper error");
    }
}