anyerr 0.1.1

Dynamic error library with rich error wrapping and context support
Documentation
use std::any::Any;
use std::backtrace::Backtrace;
use std::borrow::Borrow;
use std::error::Error;
use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
use std::hash::Hash;

use crate::context::{AbstractContext, Context, Entry, Iter};
use crate::converter::Convertable;
use crate::core::{AnyError, ContextDepth};
use crate::kind::Kind;

#[derive(Debug)]
pub enum ErrorData<C, K>
where
    C: AbstractContext,
    K: Kind,
{
    Simple {
        kind: K,
        message: String,
        backtrace: Backtrace,
        context: C,
    },
    Layered {
        kind: K,
        message: String,
        context: C,
        source: AnyError<C, K>,
    },
    Wrapped {
        backtrace: Backtrace,
        inner: Box<dyn Error + Send + Sync + 'static>,
    },
}

impl<C, K> ErrorData<C, K>
where
    C: AbstractContext,
    K: Kind,
{
    pub fn kind(&self) -> K {
        match self {
            Self::Simple { kind, .. } => *kind,
            Self::Layered { kind, .. } => *kind,
            Self::Wrapped { .. } => K::RAW_KIND,
        }
    }

    pub fn message(&self) -> String {
        match self {
            Self::Simple { message, .. } => message.into(),
            Self::Layered { message, .. } => message.into(),
            Self::Wrapped { inner, .. } => inner.to_string(),
        }
    }

    pub fn backtrace(&self) -> &Backtrace {
        match self {
            Self::Simple { backtrace, .. } => backtrace,
            Self::Layered { source, .. } => source.backtrace(),
            Self::Wrapped { backtrace, .. } => backtrace,
        }
    }

    pub fn context(&self, depth: ContextDepth) -> C::Iter<'_> {
        match self {
            Self::Simple { context, .. } => context.iter(),
            Self::Layered {
                context, source, ..
            } => match depth {
                ContextDepth::All => context.iter().compose(source.context(depth)),
                ContextDepth::Shallowest => context.iter(),
            },
            Self::Wrapped { .. } => C::Iter::default(),
        }
    }
}

impl<C, K> ErrorData<C, K>
where
    C: crate::context::SingletonContext,
    K: Kind,
{
    pub fn value(&self) -> Option<&<C::Entry as Entry>::ValueBorrowed> {
        match self {
            Self::Simple { context, .. } => context.value(),
            Self::Layered { context, .. } => context.value(),
            Self::Wrapped { .. } => None,
        }
    }
}

impl<C, K> ErrorData<C, K>
where
    C: crate::context::StringContext,
    K: Kind,
{
    pub fn get<Q>(&self, key: &Q) -> Option<&<C::Entry as Entry>::ValueBorrowed>
    where
        <C::Entry as Entry>::KeyBorrowed: Borrow<Q>,
        Q: Debug + Eq + Hash + ?Sized,
    {
        match self {
            Self::Simple { context, .. } => context.get(key),
            Self::Layered { context, .. } => context.get(key),
            Self::Wrapped { .. } => None,
        }
    }
}

impl<C, K> ErrorData<C, K>
where
    C: crate::context::AnyContext,
    K: Kind,
{
    pub fn value_as<T, Q>(&self, key: &Q) -> Option<&T>
    where
        <C::Entry as Entry>::KeyBorrowed: Borrow<Q>,
        Q: Debug + Eq + Hash + ?Sized,
        T: Any,
    {
        match self {
            Self::Simple { context, .. } => context.value_as::<T, _>(key),
            Self::Layered { context, .. } => context.value_as::<T, _>(key),
            Self::Wrapped { .. } => None,
        }
    }
}

impl<C, K> Display for ErrorData<C, K>
where
    C: AbstractContext,
    K: Kind,
{
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        match self {
            Self::Simple { message, .. } => write!(f, "{message}"),
            Self::Layered { message, .. } => write!(f, "{message}"),
            Self::Wrapped { inner, .. } => write!(f, "{inner}"),
        }
    }
}

impl<C, K> Error for ErrorData<C, K>
where
    C: AbstractContext,
    K: Kind,
{
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            Self::Simple { .. } => None,
            Self::Layered { source, .. } => Some(source),
            Self::Wrapped { .. } => None,
        }
    }
}

pub struct ErrorDataBuilder<C, K>
where
    C: AbstractContext,
    K: Kind,
{
    kind: K,
    message: String,
    context: C,
    source: Option<AnyError<C, K>>,
}

impl<C, K> ErrorDataBuilder<C, K>
where
    C: AbstractContext,
    K: Kind,
{
    pub fn new() -> Self {
        Self {
            kind: K::default(),
            message: String::new(),
            context: C::default(),
            source: None,
        }
    }

    pub fn kind(mut self, kind: K) -> Self {
        self.kind = kind;
        self
    }

    pub fn message<S: Into<String>>(mut self, message: S) -> Self {
        self.message = message.into();
        self
    }

    pub fn source(mut self, source: AnyError<C, K>) -> Self {
        self.source = Some(source);
        self
    }

    pub fn build(self) -> ErrorData<C, K> {
        match self.source {
            Some(source) => ErrorData::Layered {
                kind: self.kind,
                message: self.message,
                context: self.context,
                source,
            },
            None => ErrorData::Simple {
                kind: self.kind,
                message: self.message,
                backtrace: Backtrace::capture(),
                context: self.context,
            },
        }
    }
}

impl<C, K> ErrorDataBuilder<C, K>
where
    C: Context,
    K: Kind,
{
    pub fn context<Q, R>(mut self, key: Q, value: R) -> Self
    where
        Q: Into<C::Key>,
        R: Convertable<C::Converter, C::Value>,
    {
        self.context.insert_with::<C::Converter, _, _>(key, value);
        self
    }
}

#[cfg(test)]
mod tests {
    use crate::context::map::LiteralKeyStringMapEntry;
    use crate::context::{Entry, LiteralKeyStringMapContext};
    use crate::kind::DefaultErrorKind;

    use super::*;

    type DefaultErrorData = ErrorData<LiteralKeyStringMapContext, DefaultErrorKind>;
    type DefaultErrorDataBuilder = ErrorDataBuilder<LiteralKeyStringMapContext, DefaultErrorKind>;

    #[test]
    fn error_data_message_succeeds() {
        {
            let data = DefaultErrorData::Simple {
                kind: DefaultErrorKind::Unknown,
                message: "simple".into(),
                backtrace: Backtrace::capture(),
                context: LiteralKeyStringMapContext::new(),
            };
            assert_eq!(data.message(), "simple");
            assert_eq!(data.to_string(), "simple");
        }
        {
            let data = DefaultErrorData::Layered {
                kind: DefaultErrorKind::Unknown,
                message: "layered".into(),
                context: LiteralKeyStringMapContext::new(),
                source: AnyError::from(DefaultErrorData::Simple {
                    kind: DefaultErrorKind::Unknown,
                    message: "simple".into(),
                    backtrace: Backtrace::capture(),
                    context: LiteralKeyStringMapContext::new(),
                }),
            };
            assert_eq!(data.message(), "layered");
            assert_eq!(data.to_string(), "layered");
        }
        {
            let data = DefaultErrorData::Wrapped {
                backtrace: Backtrace::capture(),
                inner: "wrapped".into(),
            };
            assert_eq!(data.message(), "wrapped");
            assert_eq!(data.to_string(), "wrapped");
        }
    }

    #[test]
    fn error_data_context_succeeds() {
        {
            let data = DefaultErrorData::Simple {
                kind: DefaultErrorKind::Unknown,
                message: "simple".into(),
                backtrace: Backtrace::capture(),
                context: LiteralKeyStringMapContext::from(vec![("key", "1")]),
            };

            let mut iter = data.context(ContextDepth::All);
            assert_eq!(
                iter.next(),
                Some(&LiteralKeyStringMapEntry::new("key", "1"))
            );
            assert_eq!(iter.next(), None);

            let mut iter = data.context(ContextDepth::Shallowest);
            assert_eq!(
                iter.next(),
                Some(&LiteralKeyStringMapEntry::new("key", "1"))
            );
            assert_eq!(iter.next(), None);
        }
        {
            let data = DefaultErrorData::Layered {
                kind: DefaultErrorKind::Unknown,
                message: "layered".into(),
                context: LiteralKeyStringMapContext::from(vec![("key2", "2")]),
                source: AnyError::from(DefaultErrorData::Simple {
                    kind: DefaultErrorKind::Unknown,
                    message: "simple".into(),
                    backtrace: Backtrace::capture(),
                    context: LiteralKeyStringMapContext::from(vec![("key1", "1")]),
                }),
            };

            let mut iter = data.context(ContextDepth::All);
            assert_eq!(
                iter.next(),
                Some(&LiteralKeyStringMapEntry::new("key2", "2"))
            );
            assert_eq!(
                iter.next(),
                Some(&LiteralKeyStringMapEntry::new("key1", "1"))
            );
            assert_eq!(iter.next(), None);

            let mut iter = data.context(ContextDepth::Shallowest);
            assert_eq!(
                iter.next(),
                Some(&LiteralKeyStringMapEntry::new("key2", "2"))
            );
            assert_eq!(iter.next(), None);
        }
        {
            let data = DefaultErrorData::Wrapped {
                backtrace: Backtrace::capture(),
                inner: "wrapped".into(),
            };

            let mut iter = data.context(ContextDepth::All);
            assert_eq!(iter.next(), None);

            let mut iter = data.context(ContextDepth::Shallowest);
            assert_eq!(iter.next(), None);
        }
    }

    #[test]
    fn error_data_builder_build() {
        {
            let data = DefaultErrorDataBuilder::new()
                .kind(DefaultErrorKind::ValueValidation)
                .message("simple")
                .context("key", "1")
                .build();
            assert!(matches!(data, ErrorData::Simple { .. }));
            assert_eq!(data.kind(), DefaultErrorKind::ValueValidation);
        }
        {
            let data = DefaultErrorDataBuilder::new()
                .message("layered")
                .context("key", "1")
                .source(AnyError::from(DefaultErrorData::Simple {
                    kind: DefaultErrorKind::Unknown,
                    message: "simple".into(),
                    backtrace: Backtrace::capture(),
                    context: LiteralKeyStringMapContext::from(vec![("key1", "1")]),
                }))
                .build();
            assert_eq!(data.kind(), DefaultErrorKind::default());
            assert!(matches!(data, ErrorData::Layered { .. }));
        }
    }
}