use log::{error, warn};
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::{
error::Error,
fmt::{Display, Formatter, Result as FmtResult},
str::FromStr,
};
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum ErrorType {
UndefinedError,
CreationError,
UpdatingError,
FetchingError,
DeletionError,
UseCaseError,
ExecutionError,
InvalidRepositoryError,
InvalidArgumentError,
}
impl ErrorType {
fn default() -> Self {
Self::UndefinedError
}
}
impl Display for ErrorType {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self {
ErrorType::UndefinedError => write!(f, "undefined-error"),
ErrorType::CreationError => write!(f, "creation-error"),
ErrorType::UpdatingError => write!(f, "updating-error"),
ErrorType::FetchingError => write!(f, "fetching-error"),
ErrorType::DeletionError => write!(f, "deletion-error"),
ErrorType::UseCaseError => write!(f, "use-case-error"),
ErrorType::ExecutionError => write!(f, "execution-error"),
ErrorType::InvalidRepositoryError => {
write!(f, "invalid-repository-error")
}
ErrorType::InvalidArgumentError => {
write!(f, "invalid-argument-error")
}
}
}
}
impl FromStr for ErrorType {
type Err = ();
fn from_str(s: &str) -> Result<ErrorType, ()> {
match s {
"undefined-error" => Ok(ErrorType::UndefinedError),
"creation-error" => Ok(ErrorType::CreationError),
"updating-error" => Ok(ErrorType::UpdatingError),
"fetching-error" => Ok(ErrorType::FetchingError),
"deletion-error" => Ok(ErrorType::DeletionError),
"use-case-error" => Ok(ErrorType::UseCaseError),
"execution-error" => Ok(ErrorType::ExecutionError),
"invalid-repository-error" => Ok(ErrorType::InvalidRepositoryError),
"invalid-argument-error" => Ok(ErrorType::InvalidArgumentError),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum ErrorCodes {
Codes(Vec<String>),
Unmapped,
}
impl ErrorCodes {
pub fn default() -> ErrorCodes {
ErrorCodes::Unmapped
}
}
impl Display for ErrorCodes {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self {
ErrorCodes::Codes(codes) => {
write!(f, "{}", codes.join(MappedErrors::codes_delimiter()))
}
ErrorCodes::Unmapped => write!(f, "unmapped"),
}
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct MappedErrors {
msg: String,
error_type: ErrorType,
expected: bool,
codes: ErrorCodes,
}
impl Error for MappedErrors {}
impl Display for MappedErrors {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
let code_key = MappedErrors::code_key();
let error_type_key = MappedErrors::error_type_key();
let code_value = match self.codes.to_owned() {
ErrorCodes::Codes(codes) => codes.join(Self::codes_delimiter()),
ErrorCodes::Unmapped => String::from("none"),
};
write!(
f,
"[{}={}{}{}={}] {}",
code_key,
code_value,
Self::msg_paras_delimiter(),
error_type_key,
self.error_type,
self.msg
)
}
}
impl MappedErrors {
pub fn error_type(&self) -> ErrorType {
self.error_type
}
pub fn msg(&self) -> String {
self.msg.to_owned()
}
pub fn code(&self) -> ErrorCodes {
self.codes.to_owned()
}
pub fn expected(&self) -> bool {
self.expected.to_owned()
}
pub fn has_str_code(&self, code: &str) -> bool {
if code == "none" {
return false;
}
if let ErrorCodes::Codes(inner_code) = &self.codes {
return inner_code.into_iter().any(|i| i.as_str() == code);
};
return false;
}
pub fn is_in(&self, codes: Vec<&str>) -> bool {
for code in codes {
if self.has_str_code(code) {
return true;
}
}
return false;
}
pub fn as_error<T>(self) -> Result<T, Self> {
if let true = self.expected {
warn!("{:?}", &self.to_string());
} else {
error!("{:?}", &self.to_string());
}
Err(self)
}
pub fn with_exp_true(mut self) -> Self {
self.expected = true;
self
}
pub fn with_code(mut self, code: &str) -> Self {
let code = code.to_string();
if code == "none" {
return self;
}
let mut codes = match self.to_owned().codes {
ErrorCodes::Codes(codes) => codes,
ErrorCodes::Unmapped => vec![],
};
codes.push(code);
codes.sort();
codes.dedup();
self.codes = ErrorCodes::Codes(codes);
self
}
pub fn with_previous(mut self, prev: MappedErrors) -> Self {
self.msg = format!(
"[CURRENT_ERROR] {}; [PRECEDING_ERROR] {}",
self.msg,
&prev.to_string()
);
self
}
pub fn with_error_type(mut self, error_type: ErrorType) -> Self {
self.error_type = error_type;
self
}
pub(super) fn default(msg: String) -> Self {
Self {
msg: Self::sanitize_msg(msg),
error_type: ErrorType::default(),
expected: false,
codes: ErrorCodes::default(),
}
}
pub(super) fn new(
msg: String,
exp: Option<bool>,
prev: Option<MappedErrors>,
error_type: ErrorType,
) -> Self {
let exp = exp.unwrap_or(true);
if !exp {
error!("Unexpected error: ({}){}", &error_type, &msg);
} else {
warn!("{:?}", &msg);
}
if prev.is_some() {
let updated_msg = format!(
"[CURRENT_ERROR] {:?}; [PRECEDING_ERROR] {:?}",
msg,
&prev.unwrap().msg
);
return Self::new(updated_msg, Some(exp), None, error_type);
}
Self {
msg,
error_type,
expected: exp,
codes: ErrorCodes::default(),
}
}
fn code_key() -> &'static str {
"codes"
}
pub(self) fn codes_delimiter() -> &'static str {
","
}
pub(self) fn msg_paras_delimiter() -> &'static str {
" "
}
fn error_type_key() -> &'static str {
"error_type"
}
fn sanitize_msg(msg: String) -> String {
msg.as_str().replace(";", ",").to_string()
}
pub fn from_str_msg(msg: String) -> Self {
let pattern = Regex::new(
r"^\[codes=([a-zA-Z0-9,]+)\serror_type=([a-zA-Z-]+)\]\s(.+)$",
)
.unwrap();
if pattern.is_match(&msg) {
let capture = pattern.captures(&msg).unwrap();
let code = &capture[1];
let msg = capture[3].to_string();
let error_type = match ErrorType::from_str(&capture[2]) {
Ok(error_type) => error_type,
Err(_) => ErrorType::UndefinedError,
};
return MappedErrors::new(msg, None, None, error_type)
.with_code(code);
};
MappedErrors::new(msg, None, None, ErrorType::UndefinedError)
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_error_type() {
fn error_dispatcher() -> Result<(), super::MappedErrors> {
Err(super::MappedErrors::new(
"This is a test error".to_string(),
Some(true),
None,
super::ErrorType::UndefinedError,
))
}
fn error_handler() -> Result<(), super::MappedErrors> {
error_dispatcher()?;
Ok(())
}
let response = error_handler().unwrap_err();
assert_eq!(response.error_type(), super::ErrorType::UndefinedError);
}
#[test]
fn test_error_msg() {
fn error_dispatcher() -> Result<(), super::MappedErrors> {
Err(super::MappedErrors::new(
"This is a test error".to_string(),
Some(true),
None,
super::ErrorType::UndefinedError,
))
}
fn error_handler() -> Result<(), super::MappedErrors> {
error_dispatcher()?;
Ok(())
}
let response = error_handler().unwrap_err();
assert_eq!(
response.to_string(),
format!(
"[{}=none{}{}=undefined-error] This is a test error",
super::MappedErrors::code_key(),
super::MappedErrors::msg_paras_delimiter(),
super::MappedErrors::error_type_key()
)
);
}
#[test]
fn test_from_msg() {
let msg = format!(
"[{}=none{}{}=undefined-error] This is a test error",
super::MappedErrors::code_key(),
super::MappedErrors::msg_paras_delimiter(),
super::MappedErrors::error_type_key()
);
let response = super::MappedErrors::from_str_msg(msg.to_string());
let previous = response.to_owned();
assert_eq!(response.to_string(), msg);
let with_previous = response.with_previous(previous);
let from_str_msg =
super::MappedErrors::from_str_msg(with_previous.msg());
assert_eq!(with_previous.msg(), from_str_msg.msg());
}
#[test]
fn test_has_str_code() {
fn error_dispatcher() -> Result<(), super::MappedErrors> {
Err(super::MappedErrors::new(
"This is a test error".to_string(),
Some(true),
None,
super::ErrorType::UndefinedError,
))
}
fn error_handler() -> Result<(), super::MappedErrors> {
error_dispatcher()?;
Ok(())
}
let response = error_handler().unwrap_err();
assert!(!response.has_str_code("none"));
}
#[test]
fn test_is_in() {
fn error_dispatcher(
codes: Option<Vec<String>>,
) -> Result<(), super::MappedErrors> {
if codes.is_some() {
let mut errors = super::MappedErrors::new(
"This is a test error".to_string(),
Some(true),
None,
super::ErrorType::UndefinedError,
);
for code in codes.unwrap() {
errors = errors.with_code(code.as_str());
}
return Err(errors);
}
Err(super::MappedErrors::new(
"This is a test error".to_string(),
Some(true),
None,
super::ErrorType::UndefinedError,
))
}
fn error_handler(
codes: Option<Vec<String>>,
) -> Result<(), super::MappedErrors> {
error_dispatcher(codes)?;
Ok(())
}
let none_response = error_handler(None).unwrap_err();
let some_response = error_handler(Some(vec![
"ID00001".to_string(),
"ID00005".to_string(),
]))
.unwrap_err();
assert!(!none_response.is_in(vec!["none", "ID00001"]));
assert!(!some_response.is_in(vec!["none", "ID00002"]));
assert!(!some_response.is_in(vec!["ID00002", "ID00003"]));
assert!(some_response.is_in(vec!["none", "ID00001"]));
}
}