use crate::application::error::ApplicationError;
use crate::cli::error::CliError;
use crate::domain::error::DomainError;
pub trait ErrorContext<T> {
fn with_context<F>(self, f: F) -> Result<T, DomainError>
where
F: FnOnce() -> String;
fn context(self, msg: &'static str) -> Result<T, DomainError>;
}
pub trait ApplicationErrorContext<T> {
fn with_app_context<F>(self, f: F) -> Result<T, ApplicationError>
where
F: FnOnce() -> String;
fn app_context(self, msg: &'static str) -> Result<T, ApplicationError>;
}
pub trait CliErrorContext<T> {
fn with_cli_context<F>(self, f: F) -> Result<T, CliError>
where
F: FnOnce() -> String;
fn cli_context(self, msg: &'static str) -> Result<T, CliError>;
}
impl<T, E> ErrorContext<T> for Result<T, E>
where
E: Into<DomainError>,
{
fn with_context<F>(self, f: F) -> Result<T, DomainError>
where
F: FnOnce() -> String,
{
self.map_err(|e| e.into().context(f()))
}
fn context(self, msg: &'static str) -> Result<T, DomainError> {
self.map_err(|e| e.into().context(msg))
}
}
impl<T, E> ApplicationErrorContext<T> for Result<T, E>
where
E: Into<ApplicationError>,
{
fn with_app_context<F>(self, f: F) -> Result<T, ApplicationError>
where
F: FnOnce() -> String,
{
self.map_err(|e| e.into().context(f()))
}
fn app_context(self, msg: &'static str) -> Result<T, ApplicationError> {
self.map_err(|e| e.into().context(msg))
}
}
impl<T, E> CliErrorContext<T> for Result<T, E>
where
E: Into<CliError>,
{
fn with_cli_context<F>(self, f: F) -> Result<T, CliError>
where
F: FnOnce() -> String,
{
self.map_err(|e| e.into().context(f()))
}
fn cli_context(self, msg: &'static str) -> Result<T, CliError> {
self.map_err(|e| e.into().context(msg))
}
}
pub trait ErrorConversion {
fn to_domain_error(self, context: &'static str) -> DomainError;
fn to_app_error(self, context: &'static str) -> ApplicationError;
fn to_cli_error(self, context: &'static str) -> CliError;
}
impl ErrorConversion for std::io::Error {
fn to_domain_error(self, context: &'static str) -> DomainError {
DomainError::Io(self).context(context)
}
fn to_app_error(self, context: &'static str) -> ApplicationError {
ApplicationError::Domain(DomainError::Io(self)).context(context)
}
fn to_cli_error(self, context: &'static str) -> CliError {
CliError::Io(self).context(context)
}
}
impl ErrorConversion for serde_json::Error {
fn to_domain_error(self, context: &'static str) -> DomainError {
DomainError::DeserializationError(self.to_string()).context(context)
}
fn to_app_error(self, context: &'static str) -> ApplicationError {
ApplicationError::Domain(DomainError::DeserializationError(self.to_string()))
.context(context)
}
fn to_cli_error(self, context: &'static str) -> CliError {
CliError::Application(ApplicationError::Domain(DomainError::DeserializationError(
self.to_string(),
)))
.context(context)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io;
#[test]
fn given_io_error_when_add_context_then_returns_formatted_error() {
let result: Result<(), io::Error> =
Err(io::Error::new(io::ErrorKind::NotFound, "file not found"));
let contextual_result = result.context("reading configuration");
assert!(contextual_result.is_err());
assert!(contextual_result
.unwrap_err()
.to_string()
.contains("reading configuration"));
}
#[test]
fn given_application_error_when_add_context_then_returns_contextual_error() {
let result: Result<(), ApplicationError> =
Err(ApplicationError::Other("test error".to_string()));
let contextual_result = result.app_context("during operation");
assert!(contextual_result.is_err());
assert!(contextual_result
.unwrap_err()
.to_string()
.contains("during operation"));
}
#[test]
fn given_error_when_convert_with_context_then_preserves_message() {
let io_error = io::Error::new(io::ErrorKind::PermissionDenied, "access denied");
let domain_error = io_error.to_domain_error("file operation");
assert!(domain_error.to_string().contains("file operation"));
assert!(domain_error.to_string().contains("access denied"));
}
}