canic-core 0.25.7

Canic — a canister orchestration and management toolkit for the Internet Computer
Documentation
use crate::access::AccessError;
use crate::dto::error::Error as PublicError;
use std::fmt;
use thiserror::Error as ThisError;

///
/// InternalError
///
/// Internal, structured error type.
///
/// This error:
/// - is NOT Candid-exposed
/// - is NOT stable across versions
/// - may evolve freely
///
/// All canister endpoints must convert this into a public error envelope
/// defined in dto/.
///

#[derive(Debug, ThisError)]
#[error("{message}")]
pub struct InternalError {
    class: InternalErrorClass,
    origin: InternalErrorOrigin,
    message: String,
    public_error: Option<PublicError>,
}

impl InternalError {
    pub fn new(
        class: InternalErrorClass,
        origin: InternalErrorOrigin,
        message: impl Into<String>,
    ) -> Self {
        Self {
            class,
            origin,
            message: message.into(),
            public_error: None,
        }
    }

    #[must_use]
    pub fn public(err: PublicError) -> Self {
        Self {
            class: InternalErrorClass::Domain,
            origin: InternalErrorOrigin::Domain,
            message: err.message.clone(),
            public_error: Some(err),
        }
    }

    pub fn domain(origin: InternalErrorOrigin, message: impl Into<String>) -> Self {
        Self::new(InternalErrorClass::Domain, origin, message)
    }

    pub fn invariant(origin: InternalErrorOrigin, message: impl Into<String>) -> Self {
        Self::new(InternalErrorClass::Invariant, origin, message)
    }

    pub fn infra(origin: InternalErrorOrigin, message: impl Into<String>) -> Self {
        Self::new(InternalErrorClass::Infra, origin, message)
    }

    pub fn ops(origin: InternalErrorOrigin, message: impl Into<String>) -> Self {
        Self::new(InternalErrorClass::Ops, origin, message)
    }

    pub fn workflow(origin: InternalErrorOrigin, message: impl Into<String>) -> Self {
        Self::new(InternalErrorClass::Workflow, origin, message)
    }

    #[must_use]
    pub const fn class(&self) -> InternalErrorClass {
        self.class
    }

    #[must_use]
    pub const fn origin(&self) -> InternalErrorOrigin {
        self.origin
    }

    #[must_use]
    pub const fn log_fields(&self) -> (InternalErrorClass, InternalErrorOrigin) {
        (self.class, self.origin)
    }

    #[must_use]
    pub const fn public_error(&self) -> Option<&PublicError> {
        self.public_error.as_ref()
    }
}

impl From<AccessError> for InternalError {
    fn from(err: AccessError) -> Self {
        Self::new(
            InternalErrorClass::Access,
            InternalErrorOrigin::Access,
            err.to_string(),
        )
    }
}

///
/// InternalErrorClass
///

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum InternalErrorClass {
    Access,
    Domain,
    Infra,
    Ops,
    Workflow,
    Invariant,
}

///
/// InternalErrorOrigin
///

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum InternalErrorOrigin {
    Access,
    Config,
    Domain,
    Infra,
    Ops,
    Storage,
    Workflow,
}

impl fmt::Display for InternalErrorClass {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let label = match self {
            Self::Access => "Access",
            Self::Domain => "Domain",
            Self::Infra => "Infra",
            Self::Ops => "Ops",
            Self::Workflow => "Workflow",
            Self::Invariant => "Invariant",
        };

        f.write_str(label)
    }
}

impl fmt::Display for InternalErrorOrigin {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let label = match self {
            Self::Access => "Access",
            Self::Config => "Config",
            Self::Domain => "Domain",
            Self::Infra => "Infra",
            Self::Ops => "Ops",
            Self::Storage => "Storage",
            Self::Workflow => "Workflow",
        };

        f.write_str(label)
    }
}