1use axum::http::StatusCode;
6use axum::response::{IntoResponse, Response};
7use serde_json::json;
8
9pub enum ApiError {
11 Storage(tuitbot_core::error::StorageError),
13 NotFound(String),
15 BadRequest(String),
17 Conflict(String),
19 Internal(String),
21 Forbidden(String),
23}
24
25impl From<tuitbot_core::error::StorageError> for ApiError {
26 fn from(err: tuitbot_core::error::StorageError) -> Self {
27 match err {
28 tuitbot_core::error::StorageError::AlreadyReviewed { id, current_status } => {
29 Self::Conflict(format!(
30 "item {id} has already been reviewed (current status: {current_status})"
31 ))
32 }
33 other => Self::Storage(other),
34 }
35 }
36}
37
38impl From<crate::account::AccountError> for ApiError {
39 fn from(err: crate::account::AccountError) -> Self {
40 match err.status {
41 StatusCode::FORBIDDEN => Self::Forbidden(err.message),
42 StatusCode::NOT_FOUND => Self::NotFound(err.message),
43 _ => Self::Internal(err.message),
44 }
45 }
46}
47
48impl IntoResponse for ApiError {
49 fn into_response(self) -> Response {
50 let (status, message) = match self {
51 Self::Storage(e) => {
52 tracing::error!("storage error: {e}");
53 (StatusCode::INTERNAL_SERVER_ERROR, e.to_string())
54 }
55 Self::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
56 Self::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
57 Self::Conflict(msg) => (StatusCode::CONFLICT, msg),
58 Self::Internal(msg) => {
59 tracing::error!("internal error: {msg}");
60 (StatusCode::INTERNAL_SERVER_ERROR, msg)
61 }
62 Self::Forbidden(msg) => (StatusCode::FORBIDDEN, msg),
63 };
64
65 let body = axum::Json(json!({ "error": message }));
66 (status, body).into_response()
67 }
68}