use std::fmt;
#[derive(Debug, Clone)]
pub enum InjectableError {
CircularDependency {
type_name: &'static str,
chain: Vec<&'static str>,
},
MissingDependency {
type_name: &'static str,
},
ConstructionFailed {
type_name: &'static str,
reason: String,
},
LifecycleHookFailed {
type_name: &'static str,
hook: &'static str,
reason: String,
},
ShutdownFailed {
errors: Vec<InjectableError>,
},
ContainerNotBuilt,
GraphValidationFailed {
errors: Vec<String>,
},
}
impl fmt::Display for InjectableError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CircularDependency { type_name, chain } => {
write!(f, "circular dependency detected for `{type_name}`: ")?;
for (i, t) in chain.iter().enumerate() {
if i > 0 {
write!(f, " -> ")?;
}
write!(f, "{t}")?;
}
write!(f, " -> {type_name}")
}
Self::MissingDependency { type_name } => {
write!(
f,
"missing dependency: no provider registered for `{type_name}`"
)
}
Self::ConstructionFailed { type_name, reason } => {
write!(f, "construction of `{type_name}` failed: {reason}")
}
Self::LifecycleHookFailed {
type_name,
hook,
reason,
} => {
write!(
f,
"lifecycle hook `{hook}` on `{type_name}` failed: {reason}"
)
}
Self::ShutdownFailed { errors } => {
write!(
f,
"container shutdown failed with {} error(s):",
errors.len()
)?;
for (i, err) in errors.iter().enumerate() {
write!(f, "\n {}. {err}", i + 1)?;
}
Ok(())
}
Self::ContainerNotBuilt => write!(f, "container has not been built"),
Self::GraphValidationFailed { errors } => {
write!(f, "dependency graph validation failed:")?;
for err in errors {
write!(f, "\n - {err}")?;
}
Ok(())
}
}
}
}
impl std::error::Error for InjectableError {}
pub type InjectableResult<T> = Result<T, InjectableError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_circular_dependency() {
let e = InjectableError::CircularDependency {
type_name: "Foo",
chain: vec!["Foo", "Bar"],
};
let s = e.to_string();
assert!(s.contains("circular dependency"));
assert!(s.contains("Foo"));
assert!(s.contains("Bar"));
assert!(s.contains(" -> "));
}
#[test]
fn display_missing_dependency() {
let e = InjectableError::MissingDependency {
type_name: "Database",
};
let s = e.to_string();
assert!(s.contains("missing dependency"));
assert!(s.contains("Database"));
}
#[test]
fn display_construction_failed() {
let e = InjectableError::ConstructionFailed {
type_name: "Pool",
reason: "connection refused".to_string(),
};
let s = e.to_string();
assert!(s.contains("Pool"));
assert!(s.contains("connection refused"));
}
#[test]
fn display_lifecycle_hook_failed() {
let e = InjectableError::LifecycleHookFailed {
type_name: "Db",
hook: "post_construct",
reason: "migration error".to_string(),
};
let s = e.to_string();
assert!(s.contains("post_construct"));
assert!(s.contains("Db"));
assert!(s.contains("migration error"));
}
#[test]
fn display_shutdown_failed_single() {
let inner = InjectableError::LifecycleHookFailed {
type_name: "X",
hook: "pre_destruct",
reason: "oops".to_string(),
};
let e = InjectableError::ShutdownFailed {
errors: vec![inner],
};
let s = e.to_string();
assert!(s.contains("shutdown failed"));
assert!(s.contains("1 error"));
assert!(s.contains("oops"));
}
#[test]
fn display_shutdown_failed_multiple() {
let errs = vec![
InjectableError::MissingDependency { type_name: "A" },
InjectableError::MissingDependency { type_name: "B" },
];
let e = InjectableError::ShutdownFailed { errors: errs };
let s = e.to_string();
assert!(s.contains("2 error"));
}
#[test]
fn display_container_not_built() {
let e = InjectableError::ContainerNotBuilt;
let s = e.to_string();
assert!(s.contains("not been built"));
}
#[test]
fn display_graph_validation_failed() {
let e = InjectableError::GraphValidationFailed {
errors: vec!["err1".to_string(), "err2".to_string()],
};
let s = e.to_string();
assert!(s.contains("graph validation failed"));
assert!(s.contains("err1"));
assert!(s.contains("err2"));
}
#[test]
fn error_trait_impl() {
let e = InjectableError::ContainerNotBuilt;
let _: &dyn std::error::Error = &e;
}
#[test]
fn clone_and_debug() {
let e = InjectableError::MissingDependency { type_name: "X" };
let e2 = e.clone();
let _ = format!("{e2:?}");
}
}