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(crate) 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    pub fn public(err: PublicError) -> Self {
44        Self {
45            class: InternalErrorClass::Domain,
46            origin: InternalErrorOrigin::Domain,
47            message: err.message.clone(),
48            public_error: Some(err),
49        }
50    }
51
52    pub fn domain(origin: InternalErrorOrigin, message: impl Into<String>) -> Self {
53        Self::new(InternalErrorClass::Domain, origin, message)
54    }
55
56    pub fn invariant(origin: InternalErrorOrigin, message: impl Into<String>) -> Self {
57        Self::new(InternalErrorClass::Invariant, origin, message)
58    }
59
60    pub fn infra(origin: InternalErrorOrigin, message: impl Into<String>) -> Self {
61        Self::new(InternalErrorClass::Infra, origin, message)
62    }
63
64    pub fn ops(origin: InternalErrorOrigin, message: impl Into<String>) -> Self {
65        Self::new(InternalErrorClass::Ops, origin, message)
66    }
67
68    pub fn workflow(origin: InternalErrorOrigin, message: impl Into<String>) -> Self {
69        Self::new(InternalErrorClass::Workflow, origin, message)
70    }
71
72    pub const fn class(&self) -> InternalErrorClass {
73        self.class
74    }
75
76    pub const fn origin(&self) -> InternalErrorOrigin {
77        self.origin
78    }
79
80    #[must_use]
81    pub const fn log_fields(&self) -> (InternalErrorClass, InternalErrorOrigin) {
82        (self.class, self.origin)
83    }
84
85    #[must_use]
86    pub const fn public_error(&self) -> Option<&PublicError> {
87        self.public_error.as_ref()
88    }
89}
90
91impl From<AccessError> for InternalError {
92    fn from(err: AccessError) -> Self {
93        Self::new(
94            InternalErrorClass::Access,
95            InternalErrorOrigin::Access,
96            err.to_string(),
97        )
98    }
99}
100
101///
102/// InternalErrorClass
103///
104
105#[derive(Clone, Copy, Debug, Eq, PartialEq)]
106pub(crate) enum InternalErrorClass {
107    Access,
108    Domain,
109    Infra,
110    Ops,
111    Workflow,
112    Invariant,
113}
114
115///
116/// InternalErrorOrigin
117///
118
119#[derive(Clone, Copy, Debug, Eq, PartialEq)]
120pub(crate) enum InternalErrorOrigin {
121    Access,
122    Config,
123    Domain,
124    Infra,
125    Ops,
126    Storage,
127    Workflow,
128}
129
130impl fmt::Display for InternalErrorClass {
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        let label = match self {
133            Self::Access => "Access",
134            Self::Domain => "Domain",
135            Self::Infra => "Infra",
136            Self::Ops => "Ops",
137            Self::Workflow => "Workflow",
138            Self::Invariant => "Invariant",
139        };
140
141        f.write_str(label)
142    }
143}
144
145impl fmt::Display for InternalErrorOrigin {
146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147        let label = match self {
148            Self::Access => "Access",
149            Self::Config => "Config",
150            Self::Domain => "Domain",
151            Self::Infra => "Infra",
152            Self::Ops => "Ops",
153            Self::Storage => "Storage",
154            Self::Workflow => "Workflow",
155        };
156
157        f.write_str(label)
158    }
159}