use std::error::Error as StdError;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::result::Result as StdResult;
use crate::errors::Metadata;
pub type Result<T> = StdResult<T, Error>;
pub trait Define {
fn define(&self) -> &str;
}
#[derive(Debug, Clone, PartialEq)]
pub struct Error {
code: String,
message: String,
cause: Option<Box<Error>>,
metadata: Metadata,
}
impl Error {
pub fn new<C, S, M>(code: C, message: S, metadata: M) -> Error
where
C: Define,
S: Into<String>,
M: Into<Option<Metadata>>,
{
let metadata = metadata.into().unwrap_or_else(Metadata::new);
Error {
code: code.define().to_owned(),
message: message.into(),
cause: None,
metadata,
}
}
pub fn wrap<C, S, M>(code: C, err: Error, message: S, metadata: M) -> Error
where
C: Define,
S: Into<String>,
M: Into<Option<Metadata>>,
{
let mut error = Error::new(code, message, metadata);
error.cause = Some(Box::new(err));
error
}
pub fn wrap_raw<C, E, S, M>(code: C, err: &E, message: S, metadata: M) -> Error
where
C: Define,
E: StdError + ?Sized,
S: Into<String>,
M: Into<Option<Metadata>>,
{
Error::new(
code,
format!("{} ({})", message.into(), err.to_string()),
metadata,
)
}
pub fn code(&self) -> &str {
&self.code
}
pub fn message(&self) -> &str {
&self.message
}
pub fn cause(&self) -> Option<&Box<Error>> {
self.cause.as_ref()
}
pub fn metadata(&self) -> &Metadata {
&self.metadata
}
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "{}: {}", self.code, self.message)
}
}
impl StdError for Error {}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::Value;
use std::io::{Error as IoError, ErrorKind as IoErrorKind};
#[derive(PartialEq)]
enum CustomError {
Internal,
Validation,
Application,
}
impl Define for CustomError {
fn define(&self) -> &str {
match self {
CustomError::Internal => "internal",
CustomError::Validation => "validation",
CustomError::Application => "application",
}
}
}
#[test]
fn create_new_error() {
let err = Error::new(CustomError::Internal, "internal error", None);
assert_eq!(err.code().to_string(), "internal");
assert_eq!(err.message(), "internal error");
assert!(err.cause().is_none());
assert_eq!(err.metadata().values().len(), 0);
assert_eq!(err.to_string(), "internal: internal error");
}
#[test]
fn with_metadata() {
let err = Error::new(
CustomError::Validation,
"some error",
Metadata::with("key1", "value1")
.and("key2", "value2")
.and("key3", "value3"),
);
assert_eq!(err.metadata().values().len(), 3);
assert_eq!(
err.metadata().values()["key1"],
Value::String("value1".to_string())
);
assert_eq!(
err.metadata().values()["key2"],
Value::String("value2".to_string())
);
assert_eq!(
err.metadata().values()["key3"],
Value::String("value3".to_string())
);
}
#[test]
fn wrap_error() {
let internal_err = Error::new(
CustomError::Internal,
"internal",
Metadata::with("internal", true),
);
let application_err = Error::wrap(
CustomError::Application,
internal_err.clone(),
"application",
Metadata::with("application", true),
);
assert!(application_err.cause().is_some());
assert_eq!(application_err.cause(), Some(&Box::new(internal_err)));
}
#[test]
fn wrap_raw_error() {
let raw_err = IoError::new(IoErrorKind::NotFound, "raw_err");
let err = Error::wrap_raw(CustomError::Internal, &raw_err, "internal error", None);
assert!(err.cause().is_none());
assert_eq!(err.message(), "internal error (raw_err)");
}
}