Skip to main content

canic_core/dto/
error.rs

1use crate::{InternalError, access::AccessError, dto::prelude::*};
2use std::fmt::{self, Display};
3
4///
5/// Error
6///
7/// Public-facing error DTO returned across the canister API boundary.
8/// Encodes a stable error code and a human-readable message.
9///
10
11#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
12pub struct Error {
13    pub code: ErrorCode,
14    pub message: String,
15}
16
17impl Error {
18    #[must_use]
19    pub const fn new(code: ErrorCode, message: String) -> Self {
20        Self { code, message }
21    }
22
23    /// 409 – Conflict with existing state or resource.
24    pub fn conflict(message: impl Into<String>) -> Self {
25        Self::new(ErrorCode::Conflict, message.into())
26    }
27
28    /// 409 – Policy violation with a stable policy-specific code.
29    pub fn policy(code: ErrorCode, message: impl Into<String>) -> Self {
30        Self::new(code, message.into())
31    }
32
33    /// 403 – Authenticated caller is not permitted to perform this action.
34    pub fn forbidden(message: impl Into<String>) -> Self {
35        Self::new(ErrorCode::Forbidden, message.into())
36    }
37
38    /// 500 – Internal or unexpected failure.
39    pub fn internal(message: impl Into<String>) -> Self {
40        Self::new(ErrorCode::Internal, message.into())
41    }
42
43    /// 400 – Invalid input or malformed request.
44    pub fn invalid(message: impl Into<String>) -> Self {
45        Self::new(ErrorCode::InvalidInput, message.into())
46    }
47
48    /// 500 – Broken invariant or impossible internal state.
49    pub fn invariant(message: impl Into<String>) -> Self {
50        Self::new(ErrorCode::InvariantViolation, message.into())
51    }
52
53    /// 429 / 507 – Resource, quota, or capacity exhaustion.
54    pub fn exhausted(message: impl Into<String>) -> Self {
55        Self::new(ErrorCode::ResourceExhausted, message.into())
56    }
57
58    /// 404 – Requested resource was not found.
59    pub fn not_found(message: impl Into<String>) -> Self {
60        Self::new(ErrorCode::NotFound, message.into())
61    }
62
63    /// 401 – Caller is unauthenticated or has an invalid identity.
64    pub fn unauthorized(message: impl Into<String>) -> Self {
65        Self::new(ErrorCode::Unauthorized, message.into())
66    }
67
68    /// 503 – Service is temporarily unavailable due to runtime controls.
69    pub fn unavailable(message: impl Into<String>) -> Self {
70        Self::new(ErrorCode::Unavailable, message.into())
71    }
72}
73
74impl Display for Error {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        write!(f, "[{:?}] {}", self.code, self.message)
77    }
78}
79
80impl From<AccessError> for Error {
81    fn from(err: AccessError) -> Self {
82        Self::from(InternalError::from(err))
83    }
84}
85
86///
87/// ErrorCode
88///
89/// Stable public error codes returned by the API.
90/// New variants may be added in the future; consumers must handle unknown values.
91///
92
93#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
94#[non_exhaustive]
95#[remain::sorted]
96pub enum ErrorCode {
97    Conflict,
98    Forbidden,
99    Internal,
100    InvalidInput,
101    InvariantViolation,
102    NotFound,
103    PolicyReplicaRequiresSingletonWithScaling,
104    PolicyRoleAlreadyRegistered,
105    PolicyShardRequiresSingletonWithSharding,
106    PolicySingletonAlreadyRegisteredUnderParent,
107    PolicyTenantRequiresSingletonParent,
108    ResourceExhausted,
109    Unauthorized,
110    Unavailable,
111}