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 API error payload.
8//
9
10#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
11pub struct Error {
12    pub code: ErrorCode,
13    pub message: String,
14}
15
16impl Error {
17    #[must_use]
18    pub const fn new(code: ErrorCode, message: String) -> Self {
19        Self { code, message }
20    }
21
22    // 409 – Conflict with existing state or resource.
23    pub fn conflict(message: impl Into<String>) -> Self {
24        Self::new(ErrorCode::Conflict, message.into())
25    }
26
27    // 409 – Policy violation with a stable policy-specific code.
28    pub fn policy(code: ErrorCode, message: impl Into<String>) -> Self {
29        Self::new(code, message.into())
30    }
31
32    // 403 – Authenticated caller is not permitted to perform this action.
33    pub fn forbidden(message: impl Into<String>) -> Self {
34        Self::new(ErrorCode::Forbidden, message.into())
35    }
36
37    // 500 – Internal or unexpected failure.
38    pub fn internal(message: impl Into<String>) -> Self {
39        Self::new(ErrorCode::Internal, message.into())
40    }
41
42    // 400 – Invalid input or malformed request.
43    pub fn invalid(message: impl Into<String>) -> Self {
44        Self::new(ErrorCode::InvalidInput, message.into())
45    }
46
47    // 400 – Replay-sensitive command omitted its client operation ID.
48    #[must_use]
49    pub fn operation_id_required() -> Self {
50        Self::new(
51            ErrorCode::OperationIdRequired,
52            "operation_id is required for this command".to_string(),
53        )
54    }
55
56    // 500 – Broken invariant or impossible internal state.
57    pub fn invariant(message: impl Into<String>) -> Self {
58        Self::new(ErrorCode::InvariantViolation, message.into())
59    }
60
61    // 429 / 507 – Resource, quota, or capacity exhaustion.
62    pub fn exhausted(message: impl Into<String>) -> Self {
63        Self::new(ErrorCode::ResourceExhausted, message.into())
64    }
65
66    // 404 – Requested resource was not found.
67    pub fn not_found(message: impl Into<String>) -> Self {
68        Self::new(ErrorCode::NotFound, message.into())
69    }
70
71    // 401 – Caller is unauthenticated or has an invalid identity.
72    pub fn unauthorized(message: impl Into<String>) -> Self {
73        Self::new(ErrorCode::Unauthorized, message.into())
74    }
75
76    // 503 – Service is temporarily unavailable due to runtime controls.
77    pub fn unavailable(message: impl Into<String>) -> Self {
78        Self::new(ErrorCode::Unavailable, message.into())
79    }
80
81    // 503 – Root proof retrieval was not run in a direct root query context.
82    #[must_use]
83    pub fn root_data_certificate_unavailable() -> Self {
84        Self::new(
85            ErrorCode::RootDataCertificateUnavailable,
86            "root data certificate unavailable for delegation proof retrieval".to_string(),
87        )
88    }
89}
90
91impl Display for Error {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        write!(f, "[{:?}] {}", self.code, self.message)
94    }
95}
96
97impl From<AccessError> for Error {
98    fn from(err: AccessError) -> Self {
99        Self::from(InternalError::from(err))
100    }
101}
102
103//
104// ErrorCode
105//
106// Stable public error codes.
107//
108
109#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
110#[non_exhaustive]
111#[remain::sorted]
112pub enum ErrorCode {
113    AuthMaterialStale,
114    AuthProofExpired,
115    Conflict,
116    Forbidden,
117    Internal,
118    InternalRpcMalformed,
119    InvalidInput,
120    InvariantViolation,
121    NotFound,
122    OperationIdRequired,
123    PolicyInstanceRequiresSingletonWithDirectory,
124    PolicyReplicaRequiresSingletonWithScaling,
125    PolicyRoleAlreadyRegistered,
126    PolicyShardRequiresSingletonWithSharding,
127    PolicySingletonAlreadyRegisteredUnderParent,
128    ResourceExhausted,
129    RootDataCertificateUnavailable,
130    Unauthorized,
131    Unavailable,
132}