Skip to main content

ograf_core/
error.rs

1use axum::{
2    http::StatusCode,
3    response::{IntoResponse, Response},
4    Json,
5};
6use serde_json::json;
7
8#[derive(thiserror::Error, Debug)]
9pub enum AppError {
10    #[error("Not found: {0}")]
11    NotFound(String),
12    #[error("Bad request: {0}")]
13    BadRequest(String),
14    #[error("Forbidden: {0}")]
15    Forbidden(String),
16    #[error("Conflict: {0}")]
17    Conflict(String),
18    #[error("Renderer not connected: {0}")]
19    RendererNotConnected(String),
20    #[error("Renderer did not respond in time: {0}")]
21    Timeout(String),
22    #[error("Renderer overloaded: {0}")]
23    RendererOverloaded(String),
24    /// The renderer answered, but with a statusCode indicating the action
25    /// failed (eg 550 when a GraphicInstance's own action method threw).
26    #[error("Graphic action failed ({status_code}): {message}")]
27    GraphicAction { status_code: u16, message: String },
28    #[error(transparent)]
29    Internal(#[from] anyhow::Error),
30}
31
32impl IntoResponse for AppError {
33    fn into_response(self) -> Response {
34        let (status, message) = match &self {
35            AppError::NotFound(_) => (StatusCode::NOT_FOUND, self.to_string()),
36            AppError::BadRequest(_) => (StatusCode::BAD_REQUEST, self.to_string()),
37            AppError::Forbidden(_) => (StatusCode::FORBIDDEN, self.to_string()),
38            AppError::Conflict(_) => (StatusCode::CONFLICT, self.to_string()),
39            AppError::RendererNotConnected(_) => {
40                (StatusCode::SERVICE_UNAVAILABLE, self.to_string())
41            }
42            AppError::Timeout(_) => (StatusCode::GATEWAY_TIMEOUT, self.to_string()),
43            AppError::RendererOverloaded(_) => (StatusCode::TOO_MANY_REQUESTS, self.to_string()),
44            AppError::GraphicAction { status_code, .. } => {
45                let status =
46                    StatusCode::from_u16(*status_code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
47                (status, self.to_string())
48            }
49            AppError::Internal(err) => {
50                // Log full error internally for debugging
51                tracing::error!("Internal server error: {err:?}");
52                // Return generic message to user (don't leak file paths, etc.)
53                (
54                    StatusCode::INTERNAL_SERVER_ERROR,
55                    "Internal server error".to_string(),
56                )
57            }
58        };
59
60        (status, Json(json!({ "error": message }))).into_response()
61    }
62}
63
64pub type Result<T> = std::result::Result<T, AppError>;