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    AuthKeyUnknown,
105    AuthMaterialStale,
106    AuthProofExpired,
107    Conflict,
108    Forbidden,
109    Internal,
110    InternalRpcMalformed,
111    InvalidInput,
112    InvariantViolation,
113    NotFound,
114    OperationIdRequired,
115    PolicyInstanceRequiresSingletonWithDirectory,
116    PolicyReplicaRequiresSingletonWithScaling,
117    PolicyRoleAlreadyRegistered,
118    PolicyShardRequiresSingletonWithSharding,
119    PolicySingletonAlreadyRegisteredUnderParent,
120    ResourceExhausted,
121    Unauthorized,
122    Unavailable,
123}