1use axum::http::StatusCode;
2use axum::response::{IntoResponse, Response};
3use serde_json::json;
4
5#[derive(Debug)]
7pub enum ApiError {
8 Awa(awa_model::AwaError),
9 ReadOnly,
10 NotFound(String),
11 Unauthorized(String),
12}
13
14impl From<awa_model::AwaError> for ApiError {
15 fn from(err: awa_model::AwaError) -> Self {
16 ApiError::Awa(err)
17 }
18}
19
20impl From<sqlx::Error> for ApiError {
21 fn from(err: sqlx::Error) -> Self {
22 ApiError::Awa(err.into())
23 }
24}
25
26impl From<serde_json::Error> for ApiError {
27 fn from(err: serde_json::Error) -> Self {
28 ApiError::Awa(awa_model::AwaError::Validation(err.to_string()))
29 }
30}
31
32impl From<std::sync::Arc<crate::cache::CacheError>> for ApiError {
33 fn from(err: std::sync::Arc<crate::cache::CacheError>) -> Self {
34 let cache_err = std::sync::Arc::try_unwrap(err).unwrap_or_else(|arc| (*arc).clone());
38 ApiError::Awa(
39 std::sync::Arc::try_unwrap(cache_err.0).unwrap_or_else(|arc| {
40 awa_model::AwaError::Database(sqlx::Error::Protocol(arc.to_string()))
41 }),
42 )
43 }
44}
45
46impl IntoResponse for ApiError {
47 fn into_response(self) -> Response {
48 let (status, message) = match self {
49 ApiError::ReadOnly => (
50 StatusCode::SERVICE_UNAVAILABLE,
51 "awa serve is connected to a read-only database; admin actions are disabled"
52 .to_string(),
53 ),
54 ApiError::NotFound(message) => (StatusCode::NOT_FOUND, message),
55 ApiError::Unauthorized(message) => (StatusCode::UNAUTHORIZED, message),
56 ApiError::Awa(err) => match &err {
57 awa_model::AwaError::JobNotFound { .. } => (StatusCode::NOT_FOUND, err.to_string()),
58 awa_model::AwaError::CallbackNotFound { .. } => {
59 (StatusCode::NOT_FOUND, err.to_string())
60 }
61 awa_model::AwaError::Validation(_) => (StatusCode::BAD_REQUEST, err.to_string()),
62 awa_model::AwaError::UniqueConflict { .. } => {
63 (StatusCode::CONFLICT, err.to_string())
64 }
65 awa_model::AwaError::Database(_) | awa_model::AwaError::Serialization(_) => {
66 tracing::error!(error = %err, "internal error");
67 (
68 StatusCode::INTERNAL_SERVER_ERROR,
69 "internal server error".to_string(),
70 )
71 }
72 _ => {
73 tracing::error!(error = %err, "internal error");
74 (
75 StatusCode::INTERNAL_SERVER_ERROR,
76 "internal server error".to_string(),
77 )
78 }
79 },
80 };
81
82 let body = axum::Json(json!({ "error": message }));
83 (status, body).into_response()
84 }
85}
86
87impl ApiError {
88 pub fn read_only() -> Self {
89 ApiError::ReadOnly
90 }
91
92 pub fn unauthorized(message: impl Into<String>) -> Self {
93 ApiError::Unauthorized(message.into())
94 }
95
96 pub fn not_found(message: impl Into<String>) -> Self {
97 ApiError::NotFound(message.into())
98 }
99}