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 #[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 tracing::error!("Internal server error: {err:?}");
52 (
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>;