use std::io;
use thiserror::Error;
pub type DomainResult<T> = Result<T, DomainError>;
#[derive(Debug, Error)]
pub enum DomainError {
#[error("I/O failure while {context}: {source}")]
Io {
context: &'static str,
#[source]
source: io::Error,
},
#[error("{entity} not found: {id}")]
NotFound {
entity: &'static str,
id: String,
},
#[error("Ambiguous {entity} target '{input}'. Matches: {matches}")]
AmbiguousTarget {
entity: &'static str,
input: String,
matches: String,
},
}
impl DomainError {
pub fn io(context: &'static str, source: io::Error) -> Self {
Self::Io { context, source }
}
pub fn not_found(entity: &'static str, id: impl Into<String>) -> Self {
Self::NotFound {
entity,
id: id.into(),
}
}
pub fn ambiguous_target(entity: &'static str, input: &str, matches: &[String]) -> Self {
Self::AmbiguousTarget {
entity,
input: input.to_string(),
matches: matches.join(", "),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn io_constructor_preserves_context_and_source() {
let source = io::Error::new(io::ErrorKind::PermissionDenied, "no access");
let error = DomainError::io("reading tasks", source);
match error {
DomainError::Io { context, source } => {
assert_eq!(context, "reading tasks");
assert_eq!(source.kind(), io::ErrorKind::PermissionDenied);
assert_eq!(source.to_string(), "no access");
}
other => panic!("expected io variant, got {other:?}"),
}
}
#[test]
fn not_found_constructor_formats_display_message() {
let error = DomainError::not_found("module", "123_core");
match &error {
DomainError::NotFound { entity, id } => {
assert_eq!(*entity, "module");
assert_eq!(id, "123_core");
}
other => panic!("expected not found variant, got {other:?}"),
}
assert_eq!(error.to_string(), "module not found: 123_core");
}
#[test]
fn ambiguous_target_joins_candidates_in_display_message() {
let matches = vec!["001-01_alpha".to_string(), "001-02_alpha-fix".to_string()];
let error = DomainError::ambiguous_target("change", "alpha", &matches);
match &error {
DomainError::AmbiguousTarget {
entity,
input,
matches,
} => {
assert_eq!(*entity, "change");
assert_eq!(input, "alpha");
assert_eq!(matches, "001-01_alpha, 001-02_alpha-fix");
}
other => panic!("expected ambiguous target variant, got {other:?}"),
}
assert_eq!(
error.to_string(),
"Ambiguous change target 'alpha'. Matches: 001-01_alpha, 001-02_alpha-fix"
);
}
}