1use axum::{http::StatusCode, response::Redirect};
2
3#[derive(Debug, thiserror::Error)]
4pub enum ApiError {
5 #[error("database error: {0:?}")]
6 Database(#[from] sea_orm::DbErr),
7
8 #[error("encountered malformed object: {0}")]
9 Malformed(#[from] apb::FieldErr),
10
11 #[error("http signature error: {0:?}")]
12 HttpSignature(#[from] httpsign::HttpSignatureError),
13
14 #[error("outgoing request error: {0:?}")]
15 Reqwest(#[from] reqwest::Error),
16
17 #[error("fetch error: {0:?}")]
20 FetchError(#[from] upub::traits::fetch::RequestError),
21
22 #[error("error interacting with file system: {0:?}")]
23 IO(#[from] std::io::Error),
24
25 #[error("{0}")]
27 Status(StatusCode),
28
29 #[error("redirecting to {0}")]
32 Redirect(String),
33}
34
35impl ApiError {
36 pub fn bad_request() -> Self {
37 Self::Status(axum::http::StatusCode::BAD_REQUEST)
38 }
39
40 pub fn unprocessable() -> Self {
41 Self::Status(axum::http::StatusCode::UNPROCESSABLE_ENTITY)
42 }
43
44 pub fn not_found() -> Self {
45 Self::Status(axum::http::StatusCode::NOT_FOUND)
46 }
47
48 pub fn forbidden() -> Self {
49 Self::Status(axum::http::StatusCode::FORBIDDEN)
50 }
51
52 pub fn unauthorized() -> Self {
53 Self::Status(axum::http::StatusCode::UNAUTHORIZED)
54 }
55
56 pub fn not_modified() -> Self {
57 Self::Status(axum::http::StatusCode::NOT_MODIFIED)
58 }
59
60 pub fn internal_server_error() -> Self {
61 Self::Status(axum::http::StatusCode::INTERNAL_SERVER_ERROR)
62 }
63}
64
65pub type ApiResult<T> = Result<T, ApiError>;
66
67impl From<axum::http::StatusCode> for ApiError {
68 fn from(value: axum::http::StatusCode) -> Self {
69 ApiError::Status(value)
70 }
71}
72
73impl axum::response::IntoResponse for ApiError {
74 fn into_response(self) -> axum::response::Response {
75 tracing::debug!("emitting error response: {self:?}");
78 let descr = self.to_string();
79 match self {
80 ApiError::Redirect(to) => Redirect::to(&to).into_response(),
81 ApiError::Status(status) => status.into_response(),
82 ApiError::Database(e) => (
83 StatusCode::SERVICE_UNAVAILABLE,
84 axum::Json(serde_json::json!({
85 "error": "database",
86 "inner": format!("{e:#?}"),
87 }))
88 ).into_response(),
89 ApiError::Reqwest(x) => (
90 x.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
91 axum::Json(serde_json::json!({
92 "error": "request",
93 "status": x.status().map(|s| s.to_string()).unwrap_or_default(),
94 "url": x.url().map(|x| x.to_string()).unwrap_or_default(),
95 "description": descr,
96 "inner": format!("{x:#?}"),
97 }))
98 ).into_response(),
99 ApiError::FetchError(pull) => (
100 StatusCode::INTERNAL_SERVER_ERROR,
101 axum::Json(serde_json::json!({
102 "error": "fetch",
103 "description": descr,
104 "inner": format!("{pull:#?}"),
105 }))
106 ).into_response(),
107 ApiError::Malformed(x) => (
108 axum::http::StatusCode::BAD_REQUEST,
109 axum::Json(serde_json::json!({
110 "error": "field",
111 "field": x.0.to_string(),
112 "description": descr,
113 }))
114 ).into_response(),
115 x => (
116 StatusCode::INTERNAL_SERVER_ERROR,
117 axum::Json(serde_json::json!({
118 "error": "unknown",
119 "description": descr,
120 "inner": format!("{x:#?}"),
121 }))
122 ).into_response(),
123 }
124 }
125}