1use std::{collections::HashMap, error::Error, fmt, sync::Arc};
2
3use derive_more::Display;
4use megacommerce_proto::{AppError as AppErrorProto, NestedStringMap, StringMap};
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use tonic::Code;
8
9use super::{
10 context::Context,
11 translate::{tr, TranslateFunc},
12};
13
14pub type BoxedErr = Box<dyn Error + Sync + Send>;
15pub type OptionalErr = Option<BoxedErr>;
16pub type OptionalParams = Option<HashMap<String, Value>>;
17
18pub const MSG_ID_ERR_INTERNAL: &str = "server.internal.error";
19pub const MSG_ERR_INTERNAL: &str =
20 "Sorry, Unexpected internal server error. Our team has been notified. Please try again";
21
22#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
23#[serde(rename_all = "snake_case")]
24pub enum ErrorType {
25 NoRows,
26 UniqueViolation,
27 ForeignKeyViolation,
28 NotNullViolation,
29 JsonMarshal,
30 JsonUnmarshal,
31 Connection,
32 Privileges,
33 Internal,
34 DBConnectionError,
35 DBInsertError,
36 DBUpdateError,
37 DBDeleteError,
38 ConfigError,
39 HttpRequestError,
40 HttpResponseError,
41 HttpEmptyResponse,
42 MissingField,
43 InvalidData,
44 TimedOut,
45 TaskFailed,
46 Base64Invalid,
47 RegexInvalid,
48 InvalidNumber,
49}
50
51impl fmt::Display for ErrorType {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 match self {
54 ErrorType::DBConnectionError => write!(f, "db_connection_error"),
55 ErrorType::DBInsertError => write!(f, "db_insert_error"),
56 ErrorType::DBUpdateError => write!(f, "db_update_error"),
57 ErrorType::DBDeleteError => write!(f, "db_delete_error"),
58 ErrorType::NoRows => write!(f, "no_rows"),
59 ErrorType::UniqueViolation => write!(f, "unique_violation"),
60 ErrorType::ForeignKeyViolation => write!(f, "foreign_key_violation"),
61 ErrorType::NotNullViolation => write!(f, "not_null_violation"),
62 ErrorType::JsonMarshal => write!(f, "json_marshal"),
63 ErrorType::JsonUnmarshal => write!(f, "json_unmarshal"),
64 ErrorType::Connection => write!(f, "connection_exception"),
65 ErrorType::Privileges => write!(f, "insufficient_privilege"),
66 ErrorType::ConfigError => write!(f, "config_error"),
67 ErrorType::Internal => write!(f, "internal_error"),
68 ErrorType::HttpRequestError => write!(f, "http_request_error"),
69 ErrorType::HttpResponseError => write!(f, "http_response_error"),
70 ErrorType::HttpEmptyResponse => write!(f, "http_empty_response"),
71 ErrorType::MissingField => write!(f, "missing_field"),
72 ErrorType::InvalidData => write!(f, "invalid_data"),
73 ErrorType::TimedOut => write!(f, "timed_out"),
74 ErrorType::TaskFailed => write!(f, "task_failed"),
75 ErrorType::Base64Invalid => write!(f, "base64_invalid"),
76 ErrorType::RegexInvalid => write!(f, "regex_invalid"),
77 ErrorType::InvalidNumber => write!(f, "invalid_number"),
78 }
79 }
80}
81
82#[derive(Debug)]
83pub struct SimpleError {
84 pub message: String,
85 pub _type: ErrorType,
86 pub err: BoxedErr,
87}
88
89#[derive(Debug, Display)]
90#[display("InternalError: {path}: {msg}, temp: {temp}, err: {err_type} {err}")]
91pub struct InternalError {
92 pub err: Box<dyn Error + Send + Sync>,
93 pub err_type: ErrorType,
94 pub temp: bool,
95 pub msg: String,
96 pub path: String,
97}
98
99impl InternalError {
100 pub fn new(path: String, err: BoxedErr, err_type: ErrorType, temp: bool, msg: String) -> Self {
101 Self { err, err_type, temp, msg, path }
102 }
103}
104
105impl Error for InternalError {
106 fn source(&self) -> Option<&(dyn Error + 'static)> {
107 Some(self.err.as_ref())
108 }
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct AppErrorError {
113 pub id: String,
114 pub params: Option<HashMap<String, Value>>,
115}
116
117#[derive(Debug, Default)]
118pub struct AppErrorErrors {
119 pub err: OptionalErr,
120 pub errors_internal: Option<HashMap<String, AppErrorError>>,
121 pub errors_nested_internal: Option<HashMap<String, HashMap<String, AppErrorError>>>,
122}
123
124#[derive(Debug)]
125pub struct AppError {
126 pub ctx: Arc<Context>,
127 pub id: String,
128 pub path: String,
129 pub message: String,
130 pub detailes: String,
131 pub request_id: Option<String>,
132 pub status_code: i32,
133 pub tr_params: OptionalParams,
134 pub skip_translation: bool,
135 pub error: OptionalErr,
136 pub errors: Option<HashMap<String, String>>,
137 pub errors_nested: Option<HashMap<String, HashMap<String, String>>>,
138 pub errors_internal: Option<HashMap<String, AppErrorError>>,
139 pub errors_nested_internal: Option<HashMap<String, HashMap<String, AppErrorError>>>,
140}
141
142impl AppError {
143 pub fn new(
144 ctx: Arc<Context>,
145 path: impl Into<String>,
146 id: impl Into<String>,
147 id_params: OptionalParams,
148 details: impl Into<String>,
149 status_code: i32,
150 mut errors: Option<AppErrorErrors>,
151 ) -> Self {
152 if errors.is_none() {
153 errors =
154 Some(AppErrorErrors { err: None, errors_internal: None, errors_nested_internal: None });
155 };
156
157 let unwraped = {
158 let e = errors.unwrap();
159 (e.err, e.errors_internal, e.errors_nested_internal)
160 };
161
162 let mut err = Self {
163 ctx,
164 id: id.into(),
165 path: path.into(),
166 message: "".to_string(),
167 detailes: details.into(),
168 request_id: None,
169 status_code,
170 tr_params: id_params,
171 skip_translation: false,
172 error: unwraped.0,
173 errors: None,
174 errors_nested: None,
175 errors_internal: unwraped.1,
176 errors_nested_internal: unwraped.2,
177 };
178
179 let boxed_tr = Box::new(|lang: &str, id: &str, params: &HashMap<String, serde_json::Value>| {
180 let params_option = if params.is_empty() { None } else { Some(params.clone()) };
181 tr(lang, id, params_option).map_err(|e| Box::new(e) as Box<dyn Error>)
182 });
183
184 err.translate(Some(boxed_tr));
185 err
186 }
187
188 pub fn error_string(&self) -> String {
189 let mut s = String::new();
190
191 if !self.path.is_empty() {
192 s.push_str(&self.path);
193 s.push_str(": ");
194 }
195
196 if !self.message.is_empty() {
197 s.push_str(&self.message);
198 }
199
200 if !self.detailes.is_empty() {
201 s.push_str(&format!(", {}", self.detailes));
202 }
203
204 if let Some(ref err) = self.error {
205 s.push_str(&format!(", {}", err.to_string()));
206 }
207
208 s
209 }
210
211 pub fn translate(&mut self, tf: Option<TranslateFunc>) {
212 if self.skip_translation {
213 return;
214 }
215
216 if let Some(tf) = tf {
217 let empty = HashMap::new();
218 let params = self.tr_params.as_ref().unwrap_or(&empty);
219 if let Ok(translated) = tf(&self.ctx.accept_language, &self.id, params) {
220 self.message = translated;
221 return;
222 }
223 }
224 self.message = self.id.clone();
225 }
226
227 pub fn unwrap(&self) -> Option<&(dyn Error + Send + Sync)> {
228 self.error.as_deref()
229 }
230
231 pub fn wrap(mut self, err: Box<dyn Error + Send + Sync>) -> Self {
232 self.error = Some(err);
233 self
234 }
235
236 pub fn wipe_detailed(&mut self) {
237 self.error = None;
238 self.detailes.clear();
239 }
240
241 pub fn default() -> Self {
242 Self {
243 ctx: Arc::new(Context::default()),
244 path: String::new(),
245 id: String::new(),
246 message: String::new(),
247 detailes: String::new(),
248 request_id: None,
249 status_code: Code::Ok as i32,
250 tr_params: None,
251 skip_translation: false,
252 error: None,
253 errors: None,
254 errors_nested: None,
255 errors_internal: None,
256 errors_nested_internal: None,
257 }
258 }
259
260 pub fn to_proto(&self) -> AppErrorProto {
262 let mut nested = HashMap::new();
263 if let Some(errors) = &self.errors_nested {
264 for (k, v) in errors {
265 nested.insert(k.clone(), StringMap { values: v.clone() });
266 }
267 }
268
269 AppErrorProto {
270 id: self.id.clone(),
271 r#where: self.path.clone(),
272 message: self.message.clone(),
273 detailed_error: self.detailes.clone(),
274 status_code: self.status_code as i32,
275 skip_translation: self.skip_translation,
276 request_id: self.request_id.clone().unwrap_or_default(),
277 errors: Some(StringMap { values: self.errors.clone().unwrap_or_default() }),
278 errors_nested: Some(NestedStringMap { data: nested }),
279 }
280 }
281
282 pub fn to_internal(self, ctx: Arc<Context>, path: String) -> Self {
283 let errors = AppErrorErrors { err: self.error, ..Default::default() };
284 Self::new(
285 ctx,
286 path,
287 MSG_ID_ERR_INTERNAL,
288 None,
289 self.detailes,
290 Code::Internal.into(),
291 Some(errors),
292 )
293 }
294}
295
296pub fn app_error_from_proto_app_error(ctx: Arc<Context>, ae: &AppErrorProto) -> AppError {
298 let (errors, nested) = convert_proto_params(ae);
299
300 AppError {
301 ctx,
302 id: ae.id.clone(),
303 path: ae.r#where.clone(),
304 message: ae.message.clone(),
305 detailes: ae.detailed_error.clone(),
306 request_id: Some(ae.request_id.clone()).filter(|s| !s.is_empty()),
307 status_code: ae.status_code as i32,
308 tr_params: None,
309 skip_translation: ae.skip_translation,
310 error: None,
311 errors,
312 errors_nested: nested,
313 errors_internal: None,
314 errors_nested_internal: None,
315 }
316}
317
318pub fn convert_proto_params(
320 ae: &AppErrorProto,
321) -> (Option<HashMap<String, String>>, Option<HashMap<String, HashMap<String, String>>>) {
322 let mut shallow = HashMap::new();
323 let mut nested = HashMap::new();
324
325 if let Some(ref p) = ae.errors {
326 shallow.extend(p.values.clone());
327 }
328 if let Some(ref n) = ae.errors_nested {
329 for (k, v) in &n.data {
330 nested.insert(k.clone(), v.values.clone());
331 }
332 }
333
334 (Some(shallow), Some(nested))
335}
336
337impl fmt::Display for AppError {
339 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340 write!(f, "{}", self.error_string())
341 }
342}
343
344impl Error for AppError {
345 fn source(&self) -> Option<&(dyn Error + 'static)> {
346 self.error.as_ref().map(|e| e.as_ref() as &(dyn Error + 'static))
347 }
348}