Skip to main content

mentedb_server/
error.rs

1//! API error types with automatic HTTP status code mapping.
2
3use axum::http::StatusCode;
4use axum::response::{IntoResponse, Response};
5use serde_json::json;
6
7/// Unified API error type.
8#[derive(Debug)]
9#[allow(dead_code)]
10pub enum ApiError {
11    BadRequest(String),
12    NotFound(String),
13    Conflict(String),
14    Unauthorized(String),
15    Forbidden(String),
16    RateLimited,
17    Internal(String),
18    ServiceUnavailable(String),
19    /// The request's deadline passed before the work finished.
20    GatewayTimeout(String),
21}
22
23impl std::fmt::Display for ApiError {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        match self {
26            Self::BadRequest(msg) => write!(f, "bad request: {msg}"),
27            Self::NotFound(msg) => write!(f, "not found: {msg}"),
28            Self::Conflict(msg) => write!(f, "conflict: {msg}"),
29            Self::Unauthorized(msg) => write!(f, "unauthorized: {msg}"),
30            Self::Forbidden(msg) => write!(f, "forbidden: {msg}"),
31            Self::RateLimited => write!(f, "rate limited"),
32            Self::Internal(msg) => write!(f, "internal error: {msg}"),
33            Self::ServiceUnavailable(msg) => write!(f, "service unavailable: {msg}"),
34            Self::GatewayTimeout(msg) => write!(f, "deadline exceeded: {msg}"),
35        }
36    }
37}
38
39impl IntoResponse for ApiError {
40    fn into_response(self) -> Response {
41        let (status, message) = match &self {
42            Self::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
43            Self::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
44            Self::Conflict(msg) => (StatusCode::CONFLICT, msg.clone()),
45            Self::Unauthorized(msg) => (StatusCode::UNAUTHORIZED, msg.clone()),
46            Self::Forbidden(msg) => (StatusCode::FORBIDDEN, msg.clone()),
47            Self::GatewayTimeout(msg) => (StatusCode::GATEWAY_TIMEOUT, msg.clone()),
48            Self::RateLimited => (
49                StatusCode::TOO_MANY_REQUESTS,
50                "too many requests".to_string(),
51            ),
52            Self::Internal(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg.clone()),
53            Self::ServiceUnavailable(msg) => (StatusCode::SERVICE_UNAVAILABLE, msg.clone()),
54        };
55
56        let body = json!({ "error": message });
57        (status, axum::Json(body)).into_response()
58    }
59}