canic_core/dto/
error.rs

1use crate::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, Serialize)]
12pub struct Error {
13    pub code: ErrorCode,
14    pub message: String,
15}
16
17impl Display for Error {
18    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19        write!(f, "[{:?}] {}", self.code, self.message)
20    }
21}
22
23impl Error {
24    #[must_use]
25    pub const fn new(code: ErrorCode, message: String) -> Self {
26        Self { code, message }
27    }
28
29    /// 409 – Conflict with existing state or resource.
30    pub fn conflict(message: impl Into<String>) -> Self {
31        Self::new(ErrorCode::Conflict, message.into())
32    }
33
34    /// 403 – Authenticated caller is not permitted to perform this action.
35    pub fn forbidden(message: impl Into<String>) -> Self {
36        Self::new(ErrorCode::Forbidden, message.into())
37    }
38
39    /// 500 – Internal or unexpected failure.
40    pub fn internal(message: impl Into<String>) -> Self {
41        Self::new(ErrorCode::Internal, message.into())
42    }
43
44    /// 400 – Invalid input or malformed request.
45    pub fn invalid(message: impl Into<String>) -> Self {
46        Self::new(ErrorCode::InvalidInput, message.into())
47    }
48
49    /// 500 – Broken invariant or impossible internal state.
50    pub fn invariant(message: impl Into<String>) -> Self {
51        Self::new(ErrorCode::InvariantViolation, message.into())
52    }
53
54    /// 429 / 507 – Resource, quota, or capacity exhaustion.
55    pub fn exhausted(message: impl Into<String>) -> Self {
56        Self::new(ErrorCode::ResourceExhausted, message.into())
57    }
58
59    /// 404 – Requested resource was not found.
60    pub fn not_found(message: impl Into<String>) -> Self {
61        Self::new(ErrorCode::NotFound, message.into())
62    }
63
64    /// 401 – Caller is unauthenticated or has an invalid identity.
65    pub fn unauthorized(message: impl Into<String>) -> Self {
66        Self::new(ErrorCode::Unauthorized, message.into())
67    }
68}
69
70///
71/// ErrorCode
72///
73/// Stable public error codes returned by the API.
74/// New variants may be added in the future; consumers must handle unknown values.
75///
76
77#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
78#[non_exhaustive]
79#[remain::sorted]
80pub enum ErrorCode {
81    Conflict,
82    Forbidden,
83    Internal,
84    InvalidInput,
85    InvariantViolation,
86    NotFound,
87    ResourceExhausted,
88    Unauthorized,
89}