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