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