standard_error/
lib.rs

1use axum::http::StatusCode;
2use lazy_static::lazy_static;
3use std::collections::HashMap;
4use thiserror::Error;
5
6mod conf;
7pub mod extras;
8mod loader;
9mod locale;
10
11pub use locale::get_current_locale;
12pub use locale::set_current_locale;
13
14pub type StandardErrorMessages = HashMap<String, HashMap<String, String>>;
15#[cfg(feature = "askama")]
16pub use extras::htmlres::HtmlRes;
17pub use extras::interpolate::Interpolate;
18pub use extras::status::Status;
19
20#[derive(Debug, Clone, Error)]
21#[error("Error {err_code} with status {status_code}")]
22pub struct StandardError {
23    pub err_code: String,
24    pub status_code: StatusCode,
25    values: HashMap<String, String>,
26    pub message: String,
27    pub html: Option<String>,
28}
29
30impl StandardError {
31    pub fn new(code: &str) -> Self {
32        StandardError {
33            err_code: code.to_string(),
34            status_code: StatusCode::INTERNAL_SERVER_ERROR,
35            values: HashMap::new(),
36            message: error_messages
37                .get(code)
38                .and_then(|locale_message| locale_message.get(&locale::get_current_locale()))
39                .map_or_else(
40                    || format!("unknown error: {}", &code),
41                    |msg| msg.to_string(),
42                ),
43            html: None,
44        }
45    }
46}
47
48lazy_static! {
49    pub static ref settings: conf::Settings = conf::Settings::new().expect("improperly configured");
50    pub static ref error_messages: StandardErrorMessages =
51        StandardError::load_error_messages().expect("error loading error messages");
52}
53
54#[cfg(test)]
55mod tests {
56    use crate::extras::{interpolate::Interpolate, status::Status};
57    use axum::http::StatusCode;
58    use std::{collections::HashMap, num::ParseIntError};
59
60    use crate::StandardError;
61
62    #[tokio::test]
63    async fn test_question_mark() -> Result<(), StandardError> {
64        async fn foo(a: &str) -> Result<i32, StandardError> {
65            a.parse()
66                .map_err(|_: ParseIntError| StandardError::new("ER-0004"))
67        }
68
69        let res = foo("a").await;
70
71        if let Err(e) = res {
72            assert_eq!(e.status_code, StatusCode::INTERNAL_SERVER_ERROR);
73            assert_eq!(e.message, "Should be an integer".to_string())
74        }
75
76        Ok(())
77    }
78
79    #[tokio::test]
80    async fn test_status_code() -> Result<(), StandardError> {
81        async fn foo(a: &str) -> Result<i32, StandardError> {
82            a.parse().map_err(|_: ParseIntError| {
83                StandardError::new("ER-0004").code(StatusCode::BAD_REQUEST)
84            })
85        }
86
87        let res = foo("a").await;
88
89        if let Err(e) = res {
90            assert_eq!(e.status_code, StatusCode::BAD_REQUEST);
91            assert_eq!(e.message, "Should be an integer".to_string())
92        }
93
94        Ok(())
95    }
96
97    #[tokio::test]
98    async fn test_interpolate_err() -> Result<(), StandardError> {
99        async fn foo(a: &str) -> Result<i32, StandardError> {
100            a.parse().map_err(|e: ParseIntError| {
101                StandardError::new("ER-0005").interpolate_err(e.to_string())
102            })
103        }
104
105        let res = foo("a").await;
106
107        if let Err(e) = res {
108            assert_eq!(e.status_code, StatusCode::INTERNAL_SERVER_ERROR);
109            assert_eq!(
110                e.message,
111                "Should be an integer: invalid digit found in string".to_string()
112            )
113        }
114
115        Ok(())
116    }
117
118    #[tokio::test]
119    async fn test_interpolate_values() -> Result<(), StandardError> {
120        async fn foo(a: &str) -> Result<i32, StandardError> {
121            a.parse().map_err(|_: ParseIntError| {
122                let mut values: HashMap<String, String> = HashMap::new();
123                values.insert("fname".to_string(), "ashu".to_string());
124                values.insert("lname".to_string(), "pednekar".to_string());
125                StandardError::new("ER-0006").interpolate_values(values)
126            })
127        }
128
129        let res = foo("a").await;
130
131        if let Err(e) = res {
132            assert_eq!(e.status_code, StatusCode::INTERNAL_SERVER_ERROR);
133            assert_eq!(
134                e.message,
135                "Should be an integer - fname: ashu | lname: pednekar".to_string()
136            )
137        }
138
139        Ok(())
140    }
141
142    #[tokio::test]
143    async fn test_chain() -> Result<(), StandardError> {
144        async fn foo(a: &str) -> Result<i32, StandardError> {
145            a.parse().map_err(|e: ParseIntError| {
146                let mut values: HashMap<String, String> = HashMap::new();
147                values.insert("fname".to_string(), "ashu".to_string());
148                values.insert("lname".to_string(), "pednekar".to_string());
149                StandardError::new("ER-0007")
150                    .code(StatusCode::IM_A_TEAPOT)
151                    .interpolate_values(values)
152                    .interpolate_err(e.to_string())
153            })
154        }
155
156        let res = foo("a").await;
157
158        if let Err(e) = res {
159            assert_eq!(e.status_code, StatusCode::IM_A_TEAPOT);
160            assert_eq!(e.message, "Should be an integer - fname: ashu | lname: pednekar - invalid digit found in string".to_string())
161        }
162
163        Ok(())
164    }
165}