Skip to main content

sui_id_core/
errors.rs

1//! Core domain error type.
2//!
3//! Variants are categorised so that the HTTP layer can map them to the
4//! correct `ApiErrorCode` without inspecting the underlying cause.
5
6use sui_id_shared::errors::ApiErrorCode;
7use thiserror::Error;
8
9#[derive(Debug, Error)]
10pub enum CoreError {
11    #[error("invalid credentials")]
12    InvalidCredentials,
13
14    #[error("authentication required")]
15    Unauthenticated,
16
17    #[error("forbidden")]
18    Forbidden,
19
20    #[error("resource not found")]
21    NotFound,
22
23    #[error("conflict: {0}")]
24    Conflict(String),
25
26    #[error("invalid request: {0}")]
27    BadRequest(String),
28
29    #[error("system not yet initialized")]
30    NotInitialized,
31
32    #[error("system already initialized")]
33    AlreadyInitialized,
34
35    #[error("OAuth/OIDC protocol error: {code}")]
36    Protocol {
37        code: ProtocolError,
38        description: String,
39    },
40
41    #[error(transparent)]
42    Store(#[from] sui_id_store::StoreError),
43
44    #[error("password hashing failure")]
45    Password,
46
47    #[error("JWT processing failure")]
48    Jwt,
49
50    #[error("internal failure")]
51    Internal,
52
53    /// Emitted during process initialisation when a required configuration
54    /// value is invalid. Causes a fast startup abort with a clear message.
55    #[error("configuration error: {0}")]
56    ConfigError(String),
57}
58
59/// Subset of the OAuth 2.0 / OIDC error vocabulary that sui-id may emit.
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum ProtocolError {
62    InvalidRequest,
63    InvalidClient,
64    InvalidGrant,
65    UnauthorizedClient,
66    UnsupportedGrantType,
67    InvalidScope,
68    UnsupportedResponseType,
69    AccessDenied,
70    ServerError,
71    /// RFC 6749 extension: returned when the service is temporarily unable
72    /// to handle the request (e.g. rate-limiting). Maps to HTTP 429.
73    TemporarilyUnavailable,
74}
75
76impl ProtocolError {
77    pub const fn as_str(self) -> &'static str {
78        match self {
79            Self::InvalidRequest => "invalid_request",
80            Self::InvalidClient => "invalid_client",
81            Self::InvalidGrant => "invalid_grant",
82            Self::UnauthorizedClient => "unauthorized_client",
83            Self::UnsupportedGrantType => "unsupported_grant_type",
84            Self::InvalidScope => "invalid_scope",
85            Self::UnsupportedResponseType => "unsupported_response_type",
86            Self::AccessDenied => "access_denied",
87            Self::ServerError => "server_error",
88            Self::TemporarilyUnavailable => "temporarily_unavailable",
89        }
90    }
91}
92
93impl std::fmt::Display for ProtocolError {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        f.write_str(self.as_str())
96    }
97}
98
99impl CoreError {
100    /// Map this error to a stable wire-level error code.
101    pub fn api_code(&self) -> ApiErrorCode {
102        match self {
103            Self::InvalidCredentials | Self::Unauthenticated => ApiErrorCode::Unauthorized,
104            Self::Forbidden => ApiErrorCode::Forbidden,
105            Self::NotFound => ApiErrorCode::NotFound,
106            Self::Conflict(_) => ApiErrorCode::Conflict,
107            Self::BadRequest(_) => ApiErrorCode::BadRequest,
108            Self::NotInitialized | Self::AlreadyInitialized => ApiErrorCode::InvalidState,
109            Self::Protocol { .. } => ApiErrorCode::Protocol,
110            Self::Store(sui_id_store::StoreError::NotFound) => ApiErrorCode::NotFound,
111            Self::Store(sui_id_store::StoreError::Conflict) => ApiErrorCode::Conflict,
112            Self::Store(_) | Self::Password | Self::Jwt | Self::Internal | Self::ConfigError(_) => ApiErrorCode::Internal,
113        }
114    }
115}
116
117pub type CoreResult<T> = Result<T, CoreError>;