Skip to main content

alopex_server/
error.rs

1use axum::http::StatusCode;
2
3/// Server-wide result type.
4pub type Result<T> = std::result::Result<T, ServerError>;
5
6/// Top-level error type for alopex-server.
7#[derive(Debug, thiserror::Error)]
8pub enum ServerError {
9    #[error("invalid config: {0}")]
10    InvalidConfig(String),
11    #[error("bad request: {0}")]
12    BadRequest(String),
13    #[error("unauthorized: {0}")]
14    Unauthorized(String),
15    #[error("not found: {0}")]
16    NotFound(String),
17    #[error("conflict: {0}")]
18    Conflict(String),
19    #[error("payload too large: {0}")]
20    PayloadTooLarge(String),
21    #[error("timeout: {0}")]
22    Timeout(String),
23    #[error("session expired: {0}")]
24    SessionExpired(String),
25    #[error("future distributed execution required: {0}")]
26    FutureDistributedExecutionRequired(String),
27    #[error("capability unavailable: {0}")]
28    CapabilityUnavailable(String),
29    #[error("not implemented: {0}")]
30    NotImplemented(String),
31    #[error("restore integrity mismatch: {0}")]
32    RestoreIntegrityMismatch(String),
33    #[error("sql error: {0}")]
34    Sql(#[from] alopex_sql::SqlError),
35    #[error("core error: {0}")]
36    Core(#[from] alopex_core::Error),
37    #[error("catalog error: {0}")]
38    Catalog(#[from] alopex_sql::catalog::CatalogError),
39    #[error("io error: {0}")]
40    Io(#[from] std::io::Error),
41    #[error("internal error: {0}")]
42    Internal(String),
43}
44
45impl ServerError {
46    /// Map error to HTTP status code.
47    pub fn status_code(&self) -> StatusCode {
48        match self {
49            Self::InvalidConfig(_) | Self::BadRequest(_) => StatusCode::BAD_REQUEST,
50            Self::Sql(err) if err.code() == "ALOPEX-S001" => StatusCode::CONFLICT,
51            Self::Sql(_) => StatusCode::BAD_REQUEST,
52            Self::Unauthorized(_) => StatusCode::UNAUTHORIZED,
53            Self::NotFound(_) => StatusCode::NOT_FOUND,
54            Self::Conflict(_) => StatusCode::CONFLICT,
55            Self::PayloadTooLarge(_) => StatusCode::PAYLOAD_TOO_LARGE,
56            Self::Timeout(_) => StatusCode::REQUEST_TIMEOUT,
57            Self::SessionExpired(_) => StatusCode::GONE,
58            Self::FutureDistributedExecutionRequired(_) => StatusCode::NOT_IMPLEMENTED,
59            Self::CapabilityUnavailable(_) => StatusCode::SERVICE_UNAVAILABLE,
60            Self::NotImplemented(_) => StatusCode::NOT_IMPLEMENTED,
61            Self::RestoreIntegrityMismatch(_) => StatusCode::CONFLICT,
62            Self::Core(_) | Self::Catalog(_) | Self::Io(_) | Self::Internal(_) => {
63                StatusCode::INTERNAL_SERVER_ERROR
64            }
65        }
66    }
67
68    /// Map error to a stable error code for clients.
69    pub fn error_code(&self) -> String {
70        match self {
71            Self::Sql(err) => err.code().to_string(),
72            Self::InvalidConfig(_) => "INVALID_CONFIG".to_string(),
73            Self::BadRequest(_) => "INVALID_REQUEST".to_string(),
74            Self::Unauthorized(_) => "UNAUTHORIZED".to_string(),
75            Self::NotFound(_) => "NOT_FOUND".to_string(),
76            Self::Conflict(_) => "CONFLICT".to_string(),
77            Self::PayloadTooLarge(_) => "PAYLOAD_TOO_LARGE".to_string(),
78            Self::Timeout(_) => "QUERY_TIMEOUT".to_string(),
79            Self::SessionExpired(_) => "SESSION_EXPIRED".to_string(),
80            Self::FutureDistributedExecutionRequired(_) => {
81                "FUTURE_DISTRIBUTED_EXECUTION_REQUIRED".to_string()
82            }
83            Self::CapabilityUnavailable(_) => "CAPABILITY_UNAVAILABLE".to_string(),
84            Self::NotImplemented(_) => "NOT_IMPLEMENTED".to_string(),
85            Self::RestoreIntegrityMismatch(_) => "RESTORE_INTEGRITY_MISMATCH".to_string(),
86            Self::Core(_) | Self::Catalog(_) | Self::Io(_) | Self::Internal(_) => {
87                "INTERNAL".to_string()
88            }
89        }
90    }
91}