Skip to content
Clone

feat(project): add project error types

zhenyi committed
parent 3e4f692 commit 75a6f4c Browse files

1 file changed

+101 −0

Added lib/project/error.rs View lib/project/error.rs
@@ -0,0 +1,101 @@
1use axum::http::StatusCode;
2use axum::response::{IntoResponse, Response};
3use export::data::{AppResponseData, NoData};
4use thiserror::Error;
5
6pub type ProjectResult<T> = Result<T, ProjectError>;
7
8#[derive(Debug, Error)]
9pub enum ProjectError {
10 #[error("bad request: {0}")]
11 BadRequest(String),
12 #[error("unauthorized: {0}")]
13 Unauthorized(String),
14 #[error("forbidden: {0}")]
15 Forbidden(String),
16 #[error("not found: {0}")]
17 NotFound(String),
18 #[error("conflict: {0}")]
19 Conflict(String),
20 #[error("unprocessable: {0}")]
21 Unprocessable(String),
22 #[error("rate limited: {0}")]
23 RateLimited(String),
24 #[error("internal: {0}")]
25 Internal(String),
26
27 #[error("database error: {0}")]
28 Database(#[from] db::conn::SqlxError),
29 #[error("uuid: {0}")]
30 Uuid(#[from] uuid::Error),
31 #[error("serde_json: {0}")]
32 Json(#[from] serde_json::Error),
33 #[error("cache: {0}")]
34 Cache(String),
35 #[error("space: {0}")]
36 Space(String),
37}
38
39impl From<axum::extract::rejection::JsonRejection> for ProjectError {
40 fn from(re: axum::extract::rejection::JsonRejection) -> Self {
41 ProjectError::BadRequest(re.body_text())
42 }
43}
44
45impl From<axum::extract::rejection::PathRejection> for ProjectError {
46 fn from(re: axum::extract::rejection::PathRejection) -> Self {
47 ProjectError::BadRequest(re.body_text())
48 }
49}
50
51impl From<axum::extract::rejection::QueryRejection> for ProjectError {
52 fn from(re: axum::extract::rejection::QueryRejection) -> Self {
53 ProjectError::BadRequest(re.body_text())
54 }
55}
56
57impl ProjectError {
58 fn status(&self) -> StatusCode {
59 match self {
60 ProjectError::BadRequest(_) => StatusCode::BAD_REQUEST,
61 ProjectError::Unauthorized(_) => StatusCode::UNAUTHORIZED,
62 ProjectError::Forbidden(_) => StatusCode::FORBIDDEN,
63 ProjectError::NotFound(_) => StatusCode::NOT_FOUND,
64 ProjectError::Conflict(_) => StatusCode::CONFLICT,
65 ProjectError::Unprocessable(_) => StatusCode::UNPROCESSABLE_ENTITY,
66 ProjectError::RateLimited(_) => StatusCode::TOO_MANY_REQUESTS,
67 ProjectError::Internal(_)
68 | ProjectError::Database(_)
69 | ProjectError::Uuid(_)
70 | ProjectError::Json(_)
71 | ProjectError::Cache(_)
72 | ProjectError::Space(_) => StatusCode::INTERNAL_SERVER_ERROR,
73 }
74 }
75
76 fn is_user_facing(&self) -> bool {
77 matches!(
78 self,
79 ProjectError::BadRequest(_)
80 | ProjectError::Unauthorized(_)
81 | ProjectError::Forbidden(_)
82 | ProjectError::NotFound(_)
83 | ProjectError::Conflict(_)
84 | ProjectError::Unprocessable(_)
85 | ProjectError::RateLimited(_)
86 )
87 }
88}
89
90impl IntoResponse for ProjectError {
91 fn into_response(self) -> Response {
92 let status = self.status();
93 let msg = if self.is_user_facing() {
94 self.to_string()
95 } else {
96 tracing::error!(error = %self, "project internal error");
97 "internal error".to_string()
98 };
99 AppResponseData::<NoData>::err_plain(status.as_u16() as i32, msg).into_response()
100 }
101}