1use axum::{
2 http::StatusCode,
3 response::{IntoResponse, Response},
4 Json,
5};
6use serde_json::json;
7
8#[derive(Debug, thiserror::Error)]
12pub enum HttpError {
13 #[error("not found")]
14 NotFound,
15
16 #[error("bad request: {0}")]
17 BadRequest(String),
18
19 #[error("unauthorized")]
20 Unauthorized,
21
22 #[error("forbidden")]
23 Forbidden,
24
25 #[error("conflict: {0}")]
26 Conflict(String),
27
28 #[error("database error: {0}")]
29 Database(#[from] rusqlite::Error),
30
31 #[error("database pool error: {0}")]
32 Pool(#[from] r2d2::Error),
33
34 #[error("internal error: {0}")]
35 Internal(#[from] anyhow::Error),
36}
37
38impl HttpError {
39 fn status(&self) -> StatusCode {
40 match self {
41 HttpError::NotFound => StatusCode::NOT_FOUND,
42 HttpError::BadRequest(_) => StatusCode::BAD_REQUEST,
43 HttpError::Unauthorized => StatusCode::UNAUTHORIZED,
44 HttpError::Forbidden => StatusCode::FORBIDDEN,
45 HttpError::Conflict(_) => StatusCode::CONFLICT,
46 HttpError::Database(rusqlite::Error::QueryReturnedNoRows) => StatusCode::NOT_FOUND,
47 HttpError::Database(_) => StatusCode::INTERNAL_SERVER_ERROR,
48 HttpError::Pool(_) => StatusCode::INTERNAL_SERVER_ERROR,
49 HttpError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
50 }
51 }
52}
53
54impl IntoResponse for HttpError {
55 fn into_response(self) -> Response {
56 let status = self.status();
57 if status == StatusCode::INTERNAL_SERVER_ERROR {
58 tracing::error!(error = %self, "internal error");
59 }
60 (status, Json(json!({ "error": self.to_string() }))).into_response()
61 }
62}
63
64pub type Result<T> = std::result::Result<T, HttpError>;