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  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  /// Convert to proto-generated struct
261  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    let mut errors: HashMap<String, String> = HashMap::new();
270    for (key, value) in self.errors_internal.clone().unwrap_or_default().iter() {
271      let result =
272        tr(&self.ctx.accept_language(), &value.id, value.params.clone()).unwrap_or_default();
273      errors.insert(key.to_string(), result);
274    }
275
276    AppErrorProto {
277      id: self.id.clone(),
278      r#where: self.path.clone(),
279      message: self.message.clone(),
280      detailed_error: self.detailes.clone(),
281      status_code: self.status_code as i32,
282      skip_translation: self.skip_translation,
283      request_id: self.request_id.clone().unwrap_or_default(),
284      errors: Some(StringMap { values: errors }),
285      errors_nested: Some(NestedStringMap { data: nested }),
286    }
287  }
288
289  pub fn to_internal(self, ctx: Arc<Context>, path: String) -> Self {
290    let errors = AppErrorErrors { err: self.error, ..Default::default() };
291    Self::new(
292      ctx,
293      path,
294      MSG_ID_ERR_INTERNAL,
295      None,
296      self.detailes,
297      Code::Internal.into(),
298      Some(errors),
299    )
300  }
301}
302
303/// Convert from proto-generated struct
304pub fn app_error_from_proto_app_error(ctx: Arc<Context>, ae: &AppErrorProto) -> AppError {
305  let (errors, nested) = convert_proto_params(ae);
306
307  AppError {
308    ctx,
309    id: ae.id.clone(),
310    path: ae.r#where.clone(),
311    message: ae.message.clone(),
312    detailes: ae.detailed_error.clone(),
313    request_id: Some(ae.request_id.clone()).filter(|s| !s.is_empty()),
314    status_code: ae.status_code as i32,
315    tr_params: None,
316    skip_translation: ae.skip_translation,
317    error: None,
318    errors,
319    errors_nested: nested,
320    errors_internal: None,
321    errors_nested_internal: None,
322  }
323}
324
325/// Convert proto params to HashMaps
326pub fn convert_proto_params(
327  ae: &AppErrorProto,
328) -> (Option<HashMap<String, String>>, Option<HashMap<String, HashMap<String, String>>>) {
329  let mut shallow = HashMap::new();
330  let mut nested = HashMap::new();
331
332  if let Some(ref p) = ae.errors {
333    shallow.extend(p.values.clone());
334  }
335  if let Some(ref n) = ae.errors_nested {
336    for (k, v) in &n.data {
337      nested.insert(k.clone(), v.values.clone());
338    }
339  }
340
341  (Some(shallow), Some(nested))
342}
343
344// Implement std::fmt::Display for error formatting
345impl fmt::Display for AppError {
346  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
347    write!(f, "{}", self.error_string())
348  }
349}
350
351impl Error for AppError {
352  fn source(&self) -> Option<&(dyn Error + 'static)> {
353    self.error.as_ref().map(|e| e.as_ref() as &(dyn Error + 'static))
354  }
355}