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}