1use std::fmt::Display;
2
3use axum::http::StatusCode;
4use axum::response::{Html, IntoResponse, Response};
5use axum::Json;
6
7#[derive(Debug)]
10pub struct AppError {
11 pub code: StatusCode,
12 pub message: String,
13}
14
15impl Display for AppError {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 write!(f, "Code: {}; {};", self.code.as_u16(), self.message)
18 }
19}
20
21impl AppError {
22 pub fn new(code: StatusCode, message: impl ToString) -> Self {
23 Self {
24 code,
25 message: message.to_string(),
26 }
27 }
28
29 pub fn not_found() -> Self {
30 Self {
31 code: StatusCode::NOT_FOUND,
32 message: "Not Found".to_string(),
33 }
34 }
35
36 pub fn server_error(message: impl ToString) -> Self {
37 Self {
38 code: StatusCode::INTERNAL_SERVER_ERROR,
39 message: message.to_string(),
40 }
41 }
42
43 pub fn bad_request(message: impl ToString) -> Self {
44 Self {
45 code: StatusCode::BAD_REQUEST,
46 message: message.to_string(),
47 }
48 }
49
50 pub fn from(obj: impl ToString) -> Self {
52 Self {
53 code: StatusCode::INTERNAL_SERVER_ERROR,
54 message: obj.to_string(),
55 }
56 }
57
58 pub fn code<T: ToString>(code: StatusCode) -> impl Fn(T) -> Self {
60 move |obj| Self {
61 code,
62 message: obj.to_string(),
63 }
64 }
65}
66
67impl IntoResponse for AppError {
68 fn into_response(self) -> Response {
69 (self.code, self.message).into_response()
70 }
71}
72
73pub type AppResult<T> = Result<T, AppError>;
75
76pub type JsonResult<T> = AppResult<Json<T>>;
78
79pub fn json_ok<T>(obj: T) -> JsonResult<T> {
81 Ok(Json(obj))
82}
83
84pub type HtmlResult = AppResult<Html<String>>;
86
87pub fn html_ok(s: impl ToString) -> HtmlResult {
89 Ok(Html(s.to_string()))
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn test_fmt() {
98 let err = AppError {
99 code: StatusCode::OK,
100 message: "ok".to_string(),
101 };
102
103 assert_eq!(err.to_string(), "Code: 200; ok;");
104 }
105
106 #[test]
108 fn test_from() {
109 let err2: AppError = AppError::from("hi");
110
111 assert_eq!(err2.message, "hi");
112 assert_eq!(err2.code, StatusCode::INTERNAL_SERVER_ERROR);
113 }
114
115 #[test]
117 fn test_json() {
118 let resp: JsonResult<String> = json_ok("hi".to_string());
119 assert_eq!(resp.unwrap().to_string(), "hi");
120 }
121
122 #[test]
123 fn test_traits() {
124 assert_eq!(AppError::new(StatusCode::FORBIDDEN, "hi").message, "hi");
125 assert_eq!(
126 AppError::new(StatusCode::FORBIDDEN, "hi".to_string()).message,
127 "hi"
128 );
129 }
130
131 #[test]
132 fn test_code() {
133 let r: Result<(), String> = Err("hi".to_string());
134 let mapped = r.map_err(AppError::code(StatusCode::METHOD_NOT_ALLOWED));
135
136 assert!(mapped.is_err());
137
138 let e = mapped.unwrap_err();
139
140 assert_eq!(e.code, StatusCode::METHOD_NOT_ALLOWED);
141 assert_eq!(e.message, "hi");
142 }
143}