whynot_errors/
app_error.rs

1use std::fmt::Display;
2
3use axum::http::StatusCode;
4use axum::response::{Html, IntoResponse, Response};
5use axum::Json;
6
7/// Global error type
8/// Use in basically all scenarios where an error is needed.
9#[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    /// Create a new `AppError` from any `ToString` with a code 500.
23    /// If you want to customize the code, use the `AppError::code` factory.
24    pub fn new(obj: impl ToString) -> Self {
25        Self {
26            code: StatusCode::INTERNAL_SERVER_ERROR,
27            message: obj.to_string(),
28        }
29    }
30
31    /// FIXME: Remove this prior to version 1
32    #[deprecated]
33    pub fn from(obj: impl ToString) -> Self {
34        Self {
35            code: StatusCode::INTERNAL_SERVER_ERROR,
36            message: obj.to_string(),
37        }
38    }
39
40    /// Return a closure which will accept a ToString to generate an AppError
41    pub fn code<T: ToString>(code: StatusCode) -> impl Fn(T) -> Self {
42        move |obj| Self {
43            code,
44            message: obj.to_string(),
45        }
46    }
47}
48
49impl IntoResponse for AppError {
50    fn into_response(self) -> Response {
51        (self.code, self.message).into_response()
52    }
53}
54
55/// Use this for most functions that return a result
56pub type AppResult<T> = Result<T, AppError>;
57
58/// If you are returning JSON, use this.
59pub type JsonResult<T> = AppResult<Json<T>>;
60
61/// Shortcut to wrap a result in json. Will consume the input.
62pub fn json_ok<T>(obj: T) -> JsonResult<T> {
63    Ok(Json(obj))
64}
65
66/// If you are returning HTML, use this.
67pub type HtmlResult = AppResult<Html<String>>;
68
69/// Shortcut to wrap a result in html. Will consume the input.
70pub fn html_ok(s: impl ToString) -> HtmlResult {
71    Ok(Html(s.to_string()))
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn test_fmt() {
80        let err = AppError {
81            code: StatusCode::OK,
82            message: "ok".to_string(),
83        };
84
85        assert_eq!(err.to_string(), "Code: 200; ok;");
86    }
87
88    /// Test the from method. It should make an error from any object that implements `Display`
89    #[test]
90    fn test_from() {
91        let err2: AppError = AppError::new("hi");
92
93        assert_eq!(err2.message, "hi");
94        assert_eq!(err2.code, StatusCode::INTERNAL_SERVER_ERROR);
95    }
96
97    /// Test that the types are all correct for `json_ok`.
98    #[test]
99    fn test_json() {
100        let resp: JsonResult<String> = json_ok("hi".to_string());
101        assert_eq!(resp.unwrap().to_string(), "hi");
102    }
103
104    #[test]
105    fn test_traits() {
106        assert_eq!(AppError::new("hi").message, "hi");
107        assert_eq!(AppError::new("hi".to_string()).message, "hi");
108    }
109
110    #[test]
111    fn test_code() {
112        let r: Result<(), String> = Err("hi".to_string());
113        let mapped = r.map_err(AppError::code(StatusCode::METHOD_NOT_ALLOWED));
114
115        assert!(mapped.is_err());
116
117        let e = mapped.unwrap_err();
118
119        assert_eq!(e.code, StatusCode::METHOD_NOT_ALLOWED);
120        assert_eq!(e.message, "hi");
121    }
122}