oofs 0.2.3

Error handling library that generates and injects context for you.
Documentation
use crate::{
    context::{Context, OofGeneratedContext},
    tags::Tags,
    Oof, OofExt,
};
use core::fmt;
use std::{convert::Infallible, error::Error};

#[cfg(feature = "location")]
use crate::Location;

#[derive(Debug)]
pub struct OofBuilder<E: 'static + Send + Sync + Error = Infallible> {
    context: Context,
    source: Option<E>,
    tags: Tags,
    attachments: Vec<String>,
    #[cfg(feature = "location")]
    location: Location,
}

impl OofBuilder {
    #[cfg_attr(feature = "location", track_caller)]
    pub(crate) fn new() -> Self {
        Self {
            context: Context::default(),
            source: None,
            #[cfg(feature = "location")]
            location: Location::caller(),
            tags: Tags::new(),
            attachments: Vec::new(),
        }
    }

    pub(crate) fn with_source<E>(self, source: E) -> OofBuilder<E>
    where
        E: 'static + Send + Sync + Error,
    {
        let Self {
            context,
            tags,
            attachments,
            location,
            ..
        } = self;

        OofBuilder {
            source: Some(source),
            context,
            tags,
            attachments,
            #[cfg(feature = "location")]
            location,
        }
    }
}

impl<E> OofBuilder<E>
where
    E: 'static + Send + Sync + Error,
{
    pub(crate) fn with_generated(mut self, context: OofGeneratedContext) -> Self {
        self.context = context.into();
        self
    }

    pub(crate) fn with_custom<D: ToString>(mut self, custom: D) -> Self {
        self.context = Context::Custom(custom.to_string());
        self
    }

    pub(crate) fn with_tag<T: 'static>(mut self) -> Self {
        self.tags.tag::<T>();
        self
    }

    pub(crate) fn with_tag_if<T, F>(self, f: F) -> Self
    where
        T: 'static,
        F: FnOnce(&E) -> bool,
    {
        if let Some(source) = &self.source {
            if f(&source) {
                return self.with_tag::<T>();
            }
        }

        self
    }

    pub(crate) fn with_tag_manually<F>(mut self, f: F) -> Self
    where
        F: FnOnce(&E, &mut Tags),
    {
        if let Some(source) = &self.source {
            f(&source, &mut self.tags);
        }

        self
    }

    pub(crate) fn with_attachment<D: fmt::Debug>(mut self, debuggable: D) -> Self {
        self.attachments.push(format!("{debuggable:?}"));
        self
    }

    pub(crate) fn with_attachment_lazy<D: ToString, F: FnOnce() -> D>(mut self, f: F) -> Self {
        self.attachments.push(f().to_string());
        self
    }

    pub(crate) fn build(self) -> Oof {
        Oof {
            source: self.source.map(Into::into),
            context: Box::new(self.context),
            #[cfg(feature = "location")]
            location: self.location,
            tags: self.tags,
            attachments: self.attachments,
        }
    }
}

impl<T, E> OofExt for Result<T, OofBuilder<E>>
where
    E: 'static + Send + Sync + Error,
{
    type Return = T;
    type Error = E;

    fn _context<D: ToString>(self, context: D) -> Result<T, OofBuilder<E>> {
        self.map_err(|b| b.with_custom(context))
    }

    fn _tag<Tag: 'static>(self) -> Result<T, OofBuilder<E>> {
        self.map_err(|b| b.with_tag::<Tag>())
    }

    fn _tag_if<Tag, F>(self, f: F) -> Result<T, OofBuilder<E>>
    where
        Tag: 'static,
        F: FnOnce(&Self::Error) -> bool,
    {
        self.map_err(|b| b.with_tag_if::<Tag, _>(f))
    }

    fn _tag_manually<F: FnOnce(&Self::Error, &mut Tags)>(
        self,
        f: F,
    ) -> Result<Self::Return, OofBuilder<Self::Error>> {
        self.map_err(|b| b.with_tag_manually(f))
    }

    fn _attach<D: fmt::Debug>(self, debuggable: D) -> Result<T, OofBuilder<E>> {
        self.map_err(|b| b.with_attachment(debuggable))
    }

    fn _attach_lazy<D: ToString, F: FnOnce() -> D>(self, f: F) -> Result<T, OofBuilder<E>> {
        self.map_err(|b| b.with_attachment_lazy(f))
    }
}

pub trait OofGenerator<T> {
    fn build_oof<F: FnOnce() -> OofGeneratedContext>(this: Self, f: F) -> Result<T, Oof>;
}

impl<T, E> OofGenerator<T> for Result<T, OofBuilder<E>>
where
    E: 'static + Send + Sync + Error,
{
    fn build_oof<F: FnOnce() -> OofGeneratedContext>(this: Self, f: F) -> Result<T, Oof> {
        match this {
            Ok(t) => Ok(t),
            Err(mut b) => {
                if b.context.is_none() {
                    b = b.with_generated(f());
                }

                Err(b.build())
            }
        }
    }
}

impl<T, E> OofGenerator<T> for Result<T, E>
where
    E: 'static + Send + Sync + Error,
{
    #[cfg_attr(feature = "location", track_caller)]
    fn build_oof<F: FnOnce() -> OofGeneratedContext>(this: Self, f: F) -> Result<T, Oof> {
        match this {
            Ok(t) => Ok(t),
            Err(e) => {
                let mut b = OofBuilder::new().with_source(e);

                if b.context.is_none() {
                    b = b.with_generated(f());
                }

                Err(b.build())
            }
        }
    }
}

impl<T> OofGenerator<T> for Option<T> {
    #[cfg_attr(feature = "location", track_caller)]
    fn build_oof<F: FnOnce() -> OofGeneratedContext>(this: Self, f: F) -> Result<T, Oof> {
        match this {
            Some(t) => Ok(t),
            None => {
                let mut b = OofBuilder::new();

                if b.context.is_none() {
                    b = b.with_generated(f());
                }

                Err(b.build())
            }
        }
    }
}