pict-rs-error 0.1.0

Error types and codes for pict-rs
Documentation
mod code;

use exn::Exn;
use tracing_error::SpanTrace;

pub use self::code::{ErrorCode, OwnedErrorCode};

#[derive(Debug)]
pub struct Disconnected;

impl std::fmt::Display for Disconnected {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("Repo is disconnected")
    }
}

impl std::error::Error for Disconnected {}

#[derive(Debug)]
pub enum Status {
    Size,
    Server,
    Client,
    RangeNotSatisfiable,
    Forbidden,
    Unauthorized,
    NotFound,
}

pub struct ApplicationError<E>
where
    E: std::error::Error + Send + Sync + 'static,
{
    pub status: Status,
    pub code: ErrorCode,
    spantrace: SpanTrace,
    exn: Exn<E>,
}

impl<E> std::fmt::Debug for ApplicationError<E>
where
    E: std::error::Error + Send + Sync + 'static,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("Error: ")?;
        std::fmt::Display::fmt(&self.root_cause(), f)?;
        f.write_str("\nCode: ")?;
        std::fmt::Display::fmt(&self.code, f)?;
        f.write_str("\n\n")?;
        std::fmt::Debug::fmt(&self.exn, f)?;
        f.write_str("\n\n")?;
        std::fmt::Display::fmt(&color_spantrace::colorize(&self.spantrace), f)?;
        f.write_str("\n")
    }
}

pub trait ApplicationResultExt<T, E>
where
    E: std::error::Error + Send + Sync + 'static,
{
    fn or_raise<F, G>(self, err: F) -> Result<T, ApplicationError<G>>
    where
        F: FnOnce() -> G,
        G: std::error::Error + Send + Sync + 'static;

    fn or_code(self, code: ErrorCode) -> Result<T, ApplicationError<E>>;

    fn or_status(self, status: Status) -> Result<T, ApplicationError<E>>;
}

impl<T, E> ApplicationResultExt<T, E> for Result<T, ApplicationError<E>>
where
    E: std::error::Error + Send + Sync + 'static,
{
    #[track_caller]
    fn or_raise<F, G>(self, err: F) -> Result<T, ApplicationError<G>>
    where
        F: FnOnce() -> G,
        G: std::error::Error + Send + Sync + 'static,
    {
        match self {
            Ok(t) => Ok(t),
            Err(e) => Err(e.raise(err())),
        }
    }

    fn or_code(self, code: ErrorCode) -> Result<T, ApplicationError<E>> {
        self.map_err(|e| e.error_code(code))
    }

    fn or_status(self, status: Status) -> Result<T, ApplicationError<E>> {
        self.map_err(|e| e.status(status))
    }
}

impl<T, E> ApplicationResultExt<T, E> for Result<T, E>
where
    E: std::error::Error + Send + Sync + 'static,
{
    #[track_caller]
    fn or_raise<F, G>(self, err: F) -> Result<T, ApplicationError<G>>
    where
        F: FnOnce() -> G,
        G: std::error::Error + Send + Sync + 'static,
    {
        match self {
            Ok(t) => Ok(t),
            Err(e) => Err(ApplicationError::new(e).raise(err())),
        }
    }

    #[track_caller]
    fn or_code(self, code: ErrorCode) -> Result<T, ApplicationError<E>> {
        match self {
            Ok(t) => Ok(t),
            Err(e) => Err(ApplicationError::new(e).error_code(code)),
        }
    }

    #[track_caller]
    fn or_status(self, status: Status) -> Result<T, ApplicationError<E>> {
        match self {
            Ok(t) => Ok(t),
            Err(e) => Err(ApplicationError::new(e).status(status)),
        }
    }
}

impl<T, E> ApplicationResultExt<T, E> for Result<T, Exn<E>>
where
    E: std::error::Error + Send + Sync + 'static,
{
    #[track_caller]
    fn or_raise<F, G>(self, err: F) -> Result<T, ApplicationError<G>>
    where
        F: FnOnce() -> G,
        G: std::error::Error + Send + Sync + 'static,
    {
        match self {
            Ok(t) => Ok(t),
            Err(e) => Err(ApplicationError::wrap(e).raise(err())),
        }
    }

    fn or_code(self, code: ErrorCode) -> Result<T, ApplicationError<E>> {
        match self {
            Ok(t) => Ok(t),
            Err(e) => Err(ApplicationError::wrap(e).error_code(code)),
        }
    }

    fn or_status(self, status: Status) -> Result<T, ApplicationError<E>> {
        match self {
            Ok(t) => Ok(t),
            Err(e) => Err(ApplicationError::wrap(e).status(status)),
        }
    }
}

impl<E> std::fmt::Display for ApplicationError<E>
where
    E: std::error::Error + Send + Sync + 'static,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        std::fmt::Display::fmt(&*self.exn, f)
    }
}

#[derive(Debug)]
pub struct RunError;

impl std::fmt::Display for RunError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("Failed to run pict-rs")
    }
}

impl std::error::Error for RunError {}

#[derive(Debug)]
pub struct PictRsError {
    error: ApplicationError<RunError>,
}

impl std::fmt::Display for PictRsError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("Error in pict-rs")
    }
}

impl std::error::Error for PictRsError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        Some(self.error.error())
    }
}

impl PictRsError {
    /// Get the [`exn::Frame`] representing this error
    ///
    /// pict-rs uses [`exn`] to keep track of error nesting and sources, and the frame API can be
    /// useful for traversing error trees
    pub fn frame(&self) -> &exn::Frame {
        self.error.frame()
    }
}

impl From<ApplicationError<RunError>> for PictRsError {
    fn from(value: ApplicationError<RunError>) -> Self {
        Self { error: value }
    }
}

impl From<std::io::Error> for PictRsError {
    fn from(value: std::io::Error) -> Self {
        ApplicationError::new(value).raise(RunError).into()
    }
}

impl<E> ApplicationError<E>
where
    E: std::error::Error + Send + Sync + 'static,
{
    #[track_caller]
    pub fn new(error: E) -> Self {
        Self {
            status: Status::Server,
            code: ErrorCode::UNKNOWN_ERROR,
            spantrace: SpanTrace::capture(),
            exn: Exn::new(error),
        }
    }

    #[track_caller]
    pub fn wrap(exn: Exn<E>) -> Self {
        Self {
            status: Status::Server,
            code: ErrorCode::UNKNOWN_ERROR,
            spantrace: SpanTrace::capture(),
            exn,
        }
    }

    pub fn status(self, status: Status) -> Self {
        Self { status, ..self }
    }

    pub fn error_code(self, code: ErrorCode) -> Self {
        Self { code, ..self }
    }

    #[track_caller]
    pub fn raise<G>(self, error: G) -> ApplicationError<G>
    where
        G: std::error::Error + Send + Sync + 'static,
    {
        let ApplicationError {
            status,
            code,
            spantrace,
            exn,
        } = self;

        ApplicationError {
            status,
            code,
            spantrace,
            exn: exn.raise(error),
        }
    }

    pub fn root_cause(&self) -> &(dyn std::error::Error + 'static) {
        fn get_first_root(mut frame: &exn::Frame) -> &(dyn std::error::Error + 'static) {
            loop {
                if frame.children().is_empty() {
                    return frame.error();
                } else {
                    frame = &frame.children()[0]
                }
            }
        }

        get_first_root(self.exn.frame())
    }

    pub fn is_not_found(&self) -> bool {
        matches!(self.status, Status::NotFound)
    }

    pub fn error(&self) -> &E {
        &self.exn
    }

    pub fn frame(&self) -> &exn::Frame {
        self.exn.frame()
    }

    pub fn is_disconnected(&self) -> bool {
        fn walk_chain(mut error: &(dyn std::error::Error + 'static)) -> bool {
            loop {
                if error.is::<Disconnected>() {
                    return true;
                }

                if let Some(src) = error.source() {
                    error = src
                } else {
                    return false;
                }
            }
        }

        fn walk_tree(frame: &exn::Frame) -> bool {
            if walk_chain(frame.error()) {
                true
            } else {
                frame.children().iter().any(walk_tree)
            }
        }

        walk_tree(self.exn.frame())
    }
}