use std::any::TypeId;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DiError {
#[error(
"Service not found: {type_name} (not registered in this scope chain; \
was it registered in a different scope, or removed by clear()? \
Container::debug_registrations() lists what is registered)"
)]
NotFound {
type_name: &'static str,
type_id: TypeId,
},
#[error("Circular dependency detected while resolving: {type_name}")]
CircularDependency { type_name: &'static str },
#[error("Failed to create service {type_name}: {reason}")]
CreationFailed {
type_name: &'static str,
reason: String,
},
#[error("Container is locked - cannot register new services")]
Locked,
#[error("Service already registered: {type_name}")]
AlreadyRegistered { type_name: &'static str },
#[error("Parent scope has been dropped")]
ParentDropped,
#[error("Internal DI error: {0}")]
Internal(String),
}
impl DiError {
#[inline]
pub fn not_found<T: 'static>() -> Self {
Self::NotFound {
type_name: std::any::type_name::<T>(),
type_id: TypeId::of::<T>(),
}
}
#[inline]
pub fn creation_failed<T: 'static>(reason: impl Into<String>) -> Self {
Self::CreationFailed {
type_name: std::any::type_name::<T>(),
reason: reason.into(),
}
}
#[inline]
pub fn already_registered<T: 'static>() -> Self {
Self::AlreadyRegistered {
type_name: std::any::type_name::<T>(),
}
}
#[inline]
pub fn circular<T: 'static>() -> Self {
Self::CircularDependency {
type_name: std::any::type_name::<T>(),
}
}
}
impl Clone for DiError {
fn clone(&self) -> Self {
match self {
Self::NotFound { type_name, type_id } => Self::NotFound {
type_name,
type_id: *type_id,
},
Self::CircularDependency { type_name } => Self::CircularDependency { type_name },
Self::CreationFailed { type_name, reason } => Self::CreationFailed {
type_name,
reason: reason.clone(),
},
Self::Locked => Self::Locked,
Self::AlreadyRegistered { type_name } => Self::AlreadyRegistered { type_name },
Self::ParentDropped => Self::ParentDropped,
Self::Internal(s) => Self::Internal(s.clone()),
}
}
}
pub type Result<T> = std::result::Result<T, DiError>;
#[cfg(test)]
mod tests {
use super::*;
#[allow(dead_code)]
struct MissingService;
#[test]
fn test_not_found_message_contains_type_name_and_hint() {
let err = DiError::not_found::<MissingService>();
let message = err.to_string();
assert!(message.contains("MissingService"));
assert!(message.contains("different scope"));
assert!(message.contains("clear()"));
assert!(message.contains("Container::debug_registrations()"));
}
#[test]
fn test_not_found_carries_type_id() {
let err = DiError::not_found::<MissingService>();
match err {
DiError::NotFound { type_id, .. } => {
assert_eq!(type_id, TypeId::of::<MissingService>());
}
other => panic!("expected NotFound, got: {other}"),
}
}
}