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
82impl Display for Error {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        write!(f, "[{:?}] {}", self.code, self.message)
85    }
86}
87
88impl From<AccessError> for Error {
89    fn from(err: AccessError) -> Self {
90        Self::from(InternalError::from(err))
91    }
92}
93
94//
95// ErrorCode
96//
97// Stable public error codes.
98//
99
100#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
101#[non_exhaustive]
102#[remain::sorted]
103pub enum ErrorCode {
104    AuthMaterialStale,
105    AuthProofExpired,
106    Conflict,
107    Forbidden,
108    Internal,
109    InternalRpcMalformed,
110    InvalidInput,
111    InvariantViolation,
112    NotFound,
113    OperationIdRequired,
114    PolicyInstanceRequiresSingletonWithDirectory,
115    PolicyReplicaRequiresSingletonWithScaling,
116    PolicyRoleAlreadyRegistered,
117    PolicyShardRequiresSingletonWithSharding,
118    PolicySingletonAlreadyRegisteredUnderParent,
119    ResourceExhausted,
120    Unauthorized,
121    Unavailable,
122}