Skip to main content

canic_core/
error.rs

1use crate::access::AccessError;
2use crate::dto::error::Error as PublicError;
3use std::fmt;
4use thiserror::Error as ThisError;
5
6///
7/// InternalError
8///
9/// Internal, structured error type.
10///
11/// This error:
12/// - is NOT Candid-exposed
13/// - is NOT stable across versions
14/// - may evolve freely
15///
16/// All canister endpoints must convert this into a public error envelope
17/// defined in dto/.
18///
19
20#[derive(Debug, ThisError)]
21#[error("{message}")]
22pub struct InternalError {
23    class: InternalErrorClass,
24    origin: InternalErrorOrigin,
25    message: String,
26    public_error: Option<PublicError>,
27}
28
29impl InternalError {
30    pub fn new(
31        class: InternalErrorClass,
32        origin: InternalErrorOrigin,
33        message: impl Into<String>,
34    ) -> Self {
35        Self {
36            class,
37            origin,
38            message: message.into(),
39            public_error: None,
40        }
41    }
42
43    #[must_use]
44    pub fn public(err: PublicError) -> Self {
45        Self {
46            class: InternalErrorClass::Domain,
47            origin: InternalErrorOrigin::Domain,
48            message: err.message.clone(),
49            public_error: Some(err),
50        }
51    }
52
53    pub fn domain(origin: InternalErrorOrigin, message: impl Into<String>) -> Self {
54        Self::new(InternalErrorClass::Domain, origin, message)
55    }
56
57    pub fn invariant(origin: InternalErrorOrigin, message: impl Into<String>) -> Self {
58        Self::new(InternalErrorClass::Invariant, origin, message)
59    }
60
61    pub fn infra(origin: InternalErrorOrigin, message: impl Into<String>) -> Self {
62        Self::new(InternalErrorClass::Infra, origin, message)
63    }
64
65    pub fn ops(origin: InternalErrorOrigin, message: impl Into<String>) -> Self {
66        Self::new(InternalErrorClass::Ops, origin, message)
67    }
68
69    pub fn workflow(origin: InternalErrorOrigin, message: impl Into<String>) -> Self {
70        Self::new(InternalErrorClass::Workflow, origin, message)
71    }
72
73    #[must_use]
74    pub const fn class(&self) -> InternalErrorClass {
75        self.class
76    }
77
78    #[must_use]
79    pub const fn origin(&self) -> InternalErrorOrigin {
80        self.origin
81    }
82
83    #[must_use]
84    pub const fn log_fields(&self) -> (InternalErrorClass, InternalErrorOrigin) {
85        (self.class, self.origin)
86    }
87
88    #[must_use]
89    pub const fn public_error(&self) -> Option<&PublicError> {
90        self.public_error.as_ref()
91    }
92}
93
94impl From<AccessError> for InternalError {
95    fn from(err: AccessError) -> Self {
96        Self::new(
97            InternalErrorClass::Access,
98            InternalErrorOrigin::Access,
99            err.to_string(),
100        )
101    }
102}
103
104///
105/// InternalErrorClass
106///
107
108#[derive(Clone, Copy, Debug, Eq, PartialEq)]
109pub enum InternalErrorClass {
110    Access,
111    Domain,
112    Infra,
113    Ops,
114    Workflow,
115    Invariant,
116}
117
118///
119/// InternalErrorOrigin
120///
121
122#[derive(Clone, Copy, Debug, Eq, PartialEq)]
123pub enum InternalErrorOrigin {
124    Access,
125    Config,
126    Domain,
127    Infra,
128    Ops,
129    Storage,
130    Workflow,
131}
132
133impl fmt::Display for InternalErrorClass {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        let label = match self {
136            Self::Access => "Access",
137            Self::Domain => "Domain",
138            Self::Infra => "Infra",
139            Self::Ops => "Ops",
140            Self::Workflow => "Workflow",
141            Self::Invariant => "Invariant",
142        };
143
144        f.write_str(label)
145    }
146}
147
148impl fmt::Display for InternalErrorOrigin {
149    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150        let label = match self {
151            Self::Access => "Access",
152            Self::Config => "Config",
153            Self::Domain => "Domain",
154            Self::Infra => "Infra",
155            Self::Ops => "Ops",
156            Self::Storage => "Storage",
157            Self::Workflow => "Workflow",
158        };
159
160        f.write_str(label)
161    }
162}