use std::fmt;
#[derive(Debug, thiserror::Error)]
pub enum CoreError {
#[error(transparent)]
Validation(#[from] ValidationError),
#[error(transparent)]
Transition(#[from] TransitionError),
#[error(transparent)]
Sovereignty(#[from] SovereigntyError),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ValidationError {
pub field: String,
pub message: String,
}
impl fmt::Display for ValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "validation error on '{}': {}", self.field, self.message)
}
}
impl std::error::Error for ValidationError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransitionError {
pub from: String,
pub event: String,
}
impl fmt::Display for TransitionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"invalid transition: cannot apply '{}' in state '{}'",
self.event, self.from
)
}
}
impl std::error::Error for TransitionError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SovereigntyError {
pub message: String,
}
impl fmt::Display for SovereigntyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "sovereignty violation: {}", self.message)
}
}
impl std::error::Error for SovereigntyError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validation_error_display_is_actionable() {
let err = ValidationError {
field: "project_name".to_string(),
message: "must not be empty".to_string(),
};
let msg = err.to_string();
assert!(msg.contains("project_name"), "should mention the field");
assert!(
msg.contains("must not be empty"),
"should mention the reason"
);
}
#[test]
fn transition_error_display_contains_state_and_event() {
let err = TransitionError {
from: "Running".to_string(),
event: "ProvisionStarted".to_string(),
};
let msg = err.to_string();
assert!(msg.contains("Running"));
assert!(msg.contains("ProvisionStarted"));
}
#[test]
fn sovereignty_error_display_is_descriptive() {
let err = SovereigntyError {
message: "DE cannot replicate to FR".to_string(),
};
assert!(err.to_string().contains("DE cannot replicate to FR"));
}
#[test]
fn core_error_from_validation() {
let val_err = ValidationError {
field: "name".to_string(),
message: "too short".to_string(),
};
let core_err: CoreError = val_err.into();
assert!(matches!(core_err, CoreError::Validation(_)));
}
#[test]
fn core_error_from_transition() {
let trans_err = TransitionError {
from: "Pending".to_string(),
event: "UpdateCompleted".to_string(),
};
let core_err: CoreError = trans_err.into();
assert!(matches!(core_err, CoreError::Transition(_)));
}
#[test]
fn core_error_from_sovereignty() {
let sov_err = SovereigntyError {
message: "cross-country replication".to_string(),
};
let core_err: CoreError = sov_err.into();
assert!(matches!(core_err, CoreError::Sovereignty(_)));
}
}