use crate::error::RichError;
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Hexserror {
Domain(std::boxed::Box<crate::error::domain_error::DomainError>),
Port(std::boxed::Box<crate::error::port_error::PortError>),
Adapter(std::boxed::Box<crate::error::adapter_error::AdapterError>),
Validation(std::boxed::Box<crate::error::validation_error::ValidationError>),
NotFound(std::boxed::Box<crate::error::not_found_error::NotFoundError>),
Conflict(std::boxed::Box<crate::error::conflict_error::ConflictError>),
}
impl Hexserror {
pub fn domain(code: &str, message: &str) -> Self {
Self::Domain(std::boxed::Box::new(
crate::error::domain_error::DomainError::new(code, message),
))
}
pub fn port(code: &str, message: &str) -> Self {
Self::Port(std::boxed::Box::new(
crate::error::port_error::PortError::new(code, message),
))
}
pub fn adapter(code: &str, message: &str) -> Self {
Self::Adapter(std::boxed::Box::new(
crate::error::adapter_error::AdapterError::new(code, message),
))
}
pub fn validation(message: &str) -> Self {
Self::Validation(std::boxed::Box::new(
crate::error::validation_error::ValidationError::new(
crate::error::codes::validation::INVALID_FORMAT,
message,
),
))
}
pub fn validation_field(message: &str, field: &str) -> Self {
Self::Validation(std::boxed::Box::new(
crate::error::validation_error::ValidationError::new(
crate::error::codes::validation::REQUIRED_FIELD,
message,
)
.with_field(field),
))
}
pub fn not_found(resource: &str, id: &str) -> Self {
Self::NotFound(std::boxed::Box::new(
crate::error::not_found_error::NotFoundError::new(resource, id),
))
}
pub fn conflict(message: &str) -> Self {
Self::Conflict(std::boxed::Box::new(
crate::error::conflict_error::ConflictError::new(message),
))
}
pub fn with_next_step(self, step: &str) -> Self {
match self {
Self::Domain(err) => Self::Domain(std::boxed::Box::new(err.with_next_step(step))),
Self::Port(err) => Self::Port(std::boxed::Box::new(err.with_next_step(step))),
Self::Adapter(err) => Self::Adapter(std::boxed::Box::new(err.with_next_step(step))),
Self::Validation(err) => Self::Validation(std::boxed::Box::new(err.with_next_step(step))),
Self::NotFound(err) => Self::NotFound(std::boxed::Box::new(err.with_next_step(step))),
Self::Conflict(err) => Self::Conflict(std::boxed::Box::new(err.with_next_step(step))),
}
}
pub fn with_next_steps(self, steps: &[&str]) -> Self {
match self {
Self::Domain(err) => Self::Domain(std::boxed::Box::new(err.with_next_steps(steps))),
Self::Port(err) => Self::Port(std::boxed::Box::new(err.with_next_steps(steps))),
Self::Adapter(err) => Self::Adapter(std::boxed::Box::new(err.with_next_steps(steps))),
Self::Validation(err) => Self::Validation(std::boxed::Box::new(err.with_next_steps(steps))),
Self::NotFound(err) => Self::NotFound(std::boxed::Box::new(err.with_next_steps(steps))),
Self::Conflict(err) => Self::Conflict(std::boxed::Box::new(err.with_next_steps(steps))),
}
}
pub fn with_suggestion(self, suggestion: &str) -> Self {
match self {
Self::Domain(err) => Self::Domain(std::boxed::Box::new(err.with_suggestion(suggestion))),
Self::Port(err) => Self::Port(std::boxed::Box::new(err.with_suggestion(suggestion))),
Self::Adapter(err) => Self::Adapter(std::boxed::Box::new(err.with_suggestion(suggestion))),
Self::Validation(err) => {
Self::Validation(std::boxed::Box::new(err.with_suggestion(suggestion)))
}
Self::NotFound(err) => Self::NotFound(std::boxed::Box::new(err.with_suggestion(suggestion))),
Self::Conflict(err) => Self::Conflict(std::boxed::Box::new(err.with_suggestion(suggestion))),
}
}
pub fn with_suggestions(self, suggestions: &[&str]) -> Self {
match self {
Self::Domain(err) => Self::Domain(std::boxed::Box::new(err.with_suggestions(suggestions))),
Self::Port(err) => Self::Port(std::boxed::Box::new(err.with_suggestions(suggestions))),
Self::Adapter(err) => Self::Adapter(std::boxed::Box::new(err.with_suggestions(suggestions))),
Self::Validation(err) => {
Self::Validation(std::boxed::Box::new(err.with_suggestions(suggestions)))
}
Self::NotFound(err) => {
Self::NotFound(std::boxed::Box::new(err.with_suggestions(suggestions)))
}
Self::Conflict(err) => {
Self::Conflict(std::boxed::Box::new(err.with_suggestions(suggestions)))
}
}
}
pub fn with_field(self, field: &str) -> Self {
match self {
Self::Validation(err) => Self::Validation(std::boxed::Box::new(err.with_field(field))),
other => other,
}
}
pub fn with_existing_id(self, id: &str) -> Self {
match self {
Self::Conflict(err) => Self::Conflict(std::boxed::Box::new(err.with_existing_id(id))),
other => other,
}
}
pub fn with_source(self, source: impl std::error::Error + Send + Sync + 'static) -> Self {
match self {
Self::Domain(err) => Self::Domain(std::boxed::Box::new(err.with_source(source))),
Self::Port(err) => Self::Port(std::boxed::Box::new(err.with_source(source))),
Self::Adapter(err) => Self::Adapter(std::boxed::Box::new(err.with_source(source))),
other => other,
}
}
}
impl std::fmt::Display for Hexserror {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Domain(err) => write!(f, "{err}"),
Self::Port(err) => write!(f, "{err}"),
Self::Adapter(err) => write!(f, "{err}"),
Self::Validation(err) => write!(f, "{err}"),
Self::NotFound(err) => write!(f, "{err}"),
Self::Conflict(err) => write!(f, "{err}"),
}
}
}
impl std::error::Error for Hexserror {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Domain(err) => err.source(),
Self::Port(err) => err.source(),
Self::Adapter(err) => err.source(),
Self::Validation(err) => err.source(),
Self::NotFound(err) => err.source(),
Self::Conflict(err) => err.source(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error;
#[test]
fn test_domain_error_creation() {
let err = Hexserror::domain("E_HEX_001", "Test error");
assert!(matches!(err, Hexserror::Domain(_)));
}
#[test]
fn test_validation_error_creation() {
let err = Hexserror::validation("Invalid input");
assert!(matches!(err, Hexserror::Validation(_)));
}
#[test]
fn test_not_found_error_creation() {
let err = Hexserror::not_found("User", "123");
assert!(matches!(err, Hexserror::NotFound(_)));
}
#[test]
fn test_error_display() {
let err = Hexserror::validation("Test message");
let display = format!("{err}");
assert!(display.contains("Test message"));
}
#[test]
fn test_builder_next_step() {
let err = Hexserror::domain("E_TEST", "Test error").with_next_step("Do this first");
if let Hexserror::Domain(domain_err) = err {
assert_eq!(domain_err.next_steps.len(), 1);
assert_eq!(domain_err.next_steps[0], "Do this first");
} else {
panic!("Expected Domain error");
}
}
#[test]
fn test_builder_multiple_steps() {
let err = Hexserror::domain("E_TEST", "Test error").with_next_steps(&["Step 1", "Step 2"]);
if let Hexserror::Domain(domain_err) = err {
assert_eq!(domain_err.next_steps.len(), 2);
} else {
panic!("Expected Domain error");
}
}
#[test]
fn test_builder_suggestions() {
let err = Hexserror::domain("E_TEST", "Test error")
.with_suggestion("Try this")
.with_suggestions(&["Or this", "Or that"]);
if let Hexserror::Domain(domain_err) = err {
assert_eq!(domain_err.suggestions.len(), 3);
} else {
panic!("Expected Domain error");
}
}
#[test]
fn test_validation_with_field() {
let err = Hexserror::validation("Invalid value").with_field("email");
if let Hexserror::Validation(val_err) = err {
assert_eq!(val_err.field, Some(String::from("email")));
} else {
panic!("Expected Validation error");
}
}
#[test]
fn test_conflict_with_existing_id() {
let err = Hexserror::conflict("Resource exists").with_existing_id("123");
if let Hexserror::Conflict(conf_err) = err {
assert_eq!(conf_err.existing_id, Some(String::from("123")));
} else {
panic!("Expected Conflict error");
}
}
#[test]
fn test_error_source_chaining() {
let inner = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let domain_err = crate::error::domain_error::DomainError::new("E_HEX_001", "Failed to load")
.with_source(inner);
let err = Hexserror::Domain(std::boxed::Box::new(domain_err));
assert!(err.source().is_some());
}
#[test]
fn test_not_found_retains_next_step() {
let err = Hexserror::not_found("User", "123").with_next_step("Verify the ID and try again");
if let Hexserror::NotFound(nf) = &err {
assert_eq!(
nf.next_steps,
vec![String::from("Verify the ID and try again")]
);
} else {
panic!("expected NotFound");
}
assert!(format!("{err}").contains("Verify the ID and try again"));
}
#[test]
fn test_with_source_attaches_cause_on_layer_variant() {
let inner = std::io::Error::new(std::io::ErrorKind::NotFound, "boom");
let err = Hexserror::adapter("E_HEX_200", "db down").with_source(inner);
assert!(err.source().is_some());
}
#[test]
fn test_validation_and_conflict_retain_guidance() {
let v = Hexserror::validation("bad").with_suggestion("use a valid email");
if let Hexserror::Validation(ve) = &v {
assert_eq!(ve.suggestions, vec![String::from("use a valid email")]);
} else {
panic!("expected Validation");
}
let c = Hexserror::conflict("dup").with_next_steps(&["merge", "rename"]);
if let Hexserror::Conflict(ce) = &c {
assert_eq!(ce.next_steps.len(), 2);
} else {
panic!("expected Conflict");
}
}
}