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