rapid_rs/
error.rs

1use axum::{
2    http::StatusCode,
3    response::{IntoResponse, Response},
4    Json,
5};
6use serde::Serialize;
7use thiserror::Error;
8
9/// Standard API error type
10#[derive(Debug, Error)]
11pub enum ApiError {
12    #[error("Not found: {0}")]
13    NotFound(String),
14
15    #[error("Bad request: {0}")]
16    BadRequest(String),
17
18    #[error("Unauthorized")]
19    Unauthorized,
20
21    #[error("Forbidden")]
22    Forbidden,
23
24    #[error("Internal server error: {0}")]
25    InternalServerError(String),
26
27    #[error("Validation error: {0}")]
28    ValidationError(String),
29
30    #[error("Database error: {0}")]
31    DatabaseError(#[from] sqlx::Error),
32}
33
34impl ApiError {
35    fn status_code(&self) -> StatusCode {
36        match self {
37            ApiError::NotFound(_) => StatusCode::NOT_FOUND,
38            ApiError::BadRequest(_) => StatusCode::BAD_REQUEST,
39            ApiError::Unauthorized => StatusCode::UNAUTHORIZED,
40            ApiError::Forbidden => StatusCode::FORBIDDEN,
41            ApiError::ValidationError(_) => StatusCode::UNPROCESSABLE_ENTITY,
42            ApiError::InternalServerError(_) => StatusCode::INTERNAL_SERVER_ERROR,
43            ApiError::DatabaseError(_) => StatusCode::INTERNAL_SERVER_ERROR,
44        }
45    }
46
47    fn error_code(&self) -> &str {
48        match self {
49            ApiError::NotFound(_) => "NOT_FOUND",
50            ApiError::BadRequest(_) => "BAD_REQUEST",
51            ApiError::Unauthorized => "UNAUTHORIZED",
52            ApiError::Forbidden => "FORBIDDEN",
53            ApiError::ValidationError(_) => "VALIDATION_ERROR",
54            ApiError::InternalServerError(_) => "INTERNAL_SERVER_ERROR",
55            ApiError::DatabaseError(_) => "DATABASE_ERROR",
56        }
57    }
58}
59
60#[derive(Serialize)]
61struct ErrorResponse {
62    code: String,
63    message: String,
64    #[serde(skip_serializing_if = "Option::is_none")]
65    details: Option<String>,
66}
67
68impl IntoResponse for ApiError {
69    fn into_response(self) -> Response {
70        let status_code = self.status_code();
71        let error_code = self.error_code().to_string();
72        let message = self.to_string();
73
74        // Log the error
75        tracing::error!(
76            error_code = %error_code,
77            status = %status_code,
78            message = %message,
79            "API error occurred"
80        );
81
82        let error_response = ErrorResponse {
83            code: error_code,
84            message,
85            details: None,
86        };
87
88        (status_code, Json(error_response)).into_response()
89    }
90}
91
92/// Convenient Result type for API handlers
93pub type ApiResult<T> = Result<Json<T>, ApiError>;