use crate::{core::convert_error, DomainReason, ErrorCode, StructError};
use std::fmt::Display;
pub trait ErrorConv<T, R: DomainReason>: Sized {
fn err_conv(self) -> Result<T, StructError<R>>;
}
pub trait ConvStructError<R: DomainReason>: Sized {
fn conv(self) -> StructError<R>;
}
pub trait ErrorWrap<T, R: DomainReason>: Sized {
fn err_wrap(self, reason: R) -> Result<T, StructError<R>>;
}
pub trait WrapStructError<R: DomainReason>: Sized {
fn wrap(self, reason: R) -> StructError<R>;
}
impl<T, R1, R2> ErrorConv<T, R2> for Result<T, StructError<R1>>
where
R1: DomainReason,
R2: DomainReason + From<R1>,
{
fn err_conv(self) -> Result<T, StructError<R2>> {
match self {
Ok(o) => Ok(o),
Err(e) => Err(convert_error::<R1, R2>(e)),
}
}
}
impl<R1, R2> ConvStructError<R2> for StructError<R1>
where
R1: DomainReason,
R2: DomainReason + From<R1>,
{
fn conv(self) -> StructError<R2> {
convert_error::<R1, R2>(self)
}
}
impl<T, R1, R2> ErrorWrap<T, R2> for Result<T, StructError<R1>>
where
R1: DomainReason + ErrorCode + Display + std::fmt::Debug + Send + Sync + 'static,
R2: DomainReason,
{
fn err_wrap(self, reason: R2) -> Result<T, StructError<R2>> {
self.map_err(|e| e.wrap(reason))
}
}
impl<R1, R2> WrapStructError<R2> for StructError<R1>
where
R1: DomainReason + ErrorCode + Display + std::fmt::Debug + Send + Sync + 'static,
R2: DomainReason,
{
fn wrap(self, reason: R2) -> StructError<R2> {
StructError::from(reason).with_struct_error_source(self)
}
}
pub trait ToStructError<R>
where
R: DomainReason,
{
fn to_err(self) -> StructError<R>;
fn err_result<T>(self) -> Result<T, StructError<R>>;
}
impl<R> ToStructError<R> for R
where
R: DomainReason,
{
fn to_err(self) -> StructError<R> {
StructError::from(self)
}
fn err_result<T>(self) -> Result<T, StructError<R>> {
Err(StructError::from(self))
}
}
#[cfg(test)]
mod tests {
use std::error::Error as StdError;
use super::*;
use crate::{ErrorCode, StructError, UvsReason};
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
enum TestReason {
#[error("test error")]
TestError,
#[error("{0}")]
Uvs(UvsReason),
}
impl ErrorCode for TestReason {
fn error_code(&self) -> i32 {
match self {
TestReason::TestError => 1001,
TestReason::Uvs(uvs) => uvs.error_code(),
}
}
}
impl From<UvsReason> for TestReason {
fn from(uvs: UvsReason) -> Self {
TestReason::Uvs(uvs)
}
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
enum AnotherReason {
#[error("another error")]
AnotherError,
#[error("{0}")]
Uvs(UvsReason),
}
impl ErrorCode for AnotherReason {
fn error_code(&self) -> i32 {
match self {
AnotherReason::AnotherError => 2001,
AnotherReason::Uvs(uvs) => uvs.error_code(),
}
}
}
impl From<UvsReason> for AnotherReason {
fn from(uvs: UvsReason) -> Self {
AnotherReason::Uvs(uvs)
}
}
impl From<TestReason> for AnotherReason {
fn from(test: TestReason) -> Self {
match test {
TestReason::TestError => AnotherReason::AnotherError,
TestReason::Uvs(uvs) => AnotherReason::Uvs(uvs),
}
}
}
#[test]
fn test_error_conv_trait() {
let original_result: Result<i32, StructError<TestReason>> =
Err(TestReason::TestError.to_err());
let converted_result: Result<i32, StructError<AnotherReason>> = original_result.err_conv();
assert!(converted_result.is_err());
let converted_error = converted_result.unwrap_err();
assert_eq!(converted_error.error_code(), 2001);
let success_result: Result<i32, StructError<TestReason>> = Ok(42);
let converted_success: Result<i32, StructError<AnotherReason>> = success_result.err_conv();
assert!(converted_success.is_ok());
assert_eq!(converted_success.unwrap(), 42);
}
#[test]
fn test_conv_struct_error_trait() {
let original_error: StructError<TestReason> = TestReason::TestError.to_err();
let converted_error: StructError<AnotherReason> = original_error.conv();
assert_eq!(converted_error.error_code(), 2001);
let uvs_error: StructError<TestReason> =
TestReason::Uvs(UvsReason::network_error()).to_err();
let converted_uvs_error: StructError<AnotherReason> = uvs_error.conv();
assert_eq!(converted_uvs_error.error_code(), 202);
}
#[test]
fn test_to_struct_error_trait() {
let reason = TestReason::TestError;
let error: StructError<TestReason> = reason.to_err();
assert_eq!(error.error_code(), 1001);
let reason2 = TestReason::TestError;
let result: Result<String, StructError<TestReason>> = reason2.err_result();
assert!(result.is_err());
let error_from_result = result.unwrap_err();
assert_eq!(error_from_result.error_code(), 1001);
let uvs_reason1 = UvsReason::validation_error();
let uvs_error: StructError<UvsReason> = uvs_reason1.to_err();
assert_eq!(uvs_error.error_code(), 100);
let uvs_reason2 = UvsReason::validation_error();
let uvs_result: Result<i32, StructError<UvsReason>> = uvs_reason2.err_result();
assert!(uvs_result.is_err());
assert_eq!(uvs_result.unwrap_err().error_code(), 100);
}
#[test]
fn test_err_conv_preserves_source() {
let source = std::io::Error::other("db unavailable");
let original: Result<i32, StructError<TestReason>> =
Err(StructError::from(TestReason::TestError).with_source(source));
let converted: Result<i32, StructError<AnotherReason>> = original.err_conv();
let err = converted.unwrap_err();
assert_eq!(err.error_code(), 2001);
assert_eq!(
StdError::source(&err).unwrap().to_string(),
"db unavailable"
);
}
#[test]
fn test_err_wrap_preserves_previous_struct_error_chain() {
let original: Result<i32, StructError<TestReason>> =
Err(StructError::from(TestReason::TestError)
.with_detail("repo layer failed")
.with_source(std::io::Error::other("db unavailable")));
let wrapped: Result<i32, StructError<AnotherReason>> =
original.err_wrap(AnotherReason::AnotherError);
let err = wrapped.unwrap_err();
assert_eq!(err.error_code(), 2001);
assert_eq!(
StdError::source(&err).unwrap().to_string(),
"[1001] test error\n -> Details: repo layer failed\n -> Source: db unavailable"
);
assert_eq!(err.root_cause().unwrap().to_string(), "db unavailable");
assert_eq!(err.source_chain().len(), 2);
assert_eq!(err.source_frames()[0].message, "test error");
assert!(err.source_frames()[0]
.display
.as_ref()
.unwrap()
.contains("repo layer failed"));
assert_eq!(err.source_frames()[0].error_code, Some(1001));
assert_eq!(err.source_frames()[0].reason.as_deref(), Some("test error"));
assert_eq!(
err.source_frames()[0].detail.as_deref(),
Some("repo layer failed")
);
assert_eq!(err.source_frames()[1].message, "db unavailable");
assert!(err.source_frames()[1].is_root_cause);
}
}