megacommerce_shared/models/
errors.rs

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  /// Convert to proto-generated struct
263  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
305/// Convert from proto-generated struct
306pub 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
327/// Convert proto params to HashMaps
328pub 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
346// Implement std::fmt::Display for error formatting
347impl 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}