Skip to main content

dependency_injector/
error.rs

1//! Error types for dependency injection
2
3use std::any::TypeId;
4use thiserror::Error;
5
6/// Errors that can occur during dependency injection operations
7#[derive(Error, Debug)]
8pub enum DiError {
9    /// Service was not found in the container
10    ///
11    /// The container stores services by [`TypeId`] only, so this error can
12    /// name the missing type but not the registered ones. Call
13    /// `Container::debug_registrations()` on the resolving container to log
14    /// what is actually registered in each scope of the chain.
15    #[error(
16        "Service not found: {type_name} (not registered in this scope chain; \
17         was it registered in a different scope, or removed by clear()? \
18         Container::debug_registrations() lists what is registered)"
19    )]
20    NotFound {
21        type_name: &'static str,
22        type_id: TypeId,
23    },
24
25    /// Circular dependency detected during resolution
26    #[error("Circular dependency detected while resolving: {type_name}")]
27    CircularDependency { type_name: &'static str },
28
29    /// Factory failed to create service
30    #[error("Failed to create service {type_name}: {reason}")]
31    CreationFailed {
32        type_name: &'static str,
33        reason: String,
34    },
35
36    /// Container is locked and cannot be modified
37    #[error("Container is locked - cannot register new services")]
38    Locked,
39
40    /// Attempted to register duplicate service
41    #[error("Service already registered: {type_name}")]
42    AlreadyRegistered { type_name: &'static str },
43
44    /// Parent scope was dropped
45    #[error("Parent scope has been dropped")]
46    ParentDropped,
47
48    /// Internal error
49    #[error("Internal DI error: {0}")]
50    Internal(String),
51}
52
53impl DiError {
54    /// Create a `NotFound` error for a type
55    #[inline]
56    pub fn not_found<T: 'static>() -> Self {
57        Self::NotFound {
58            type_name: std::any::type_name::<T>(),
59            type_id: TypeId::of::<T>(),
60        }
61    }
62
63    /// Create a `CreationFailed` error
64    #[inline]
65    pub fn creation_failed<T: 'static>(reason: impl Into<String>) -> Self {
66        Self::CreationFailed {
67            type_name: std::any::type_name::<T>(),
68            reason: reason.into(),
69        }
70    }
71
72    /// Create an `AlreadyRegistered` error
73    #[inline]
74    pub fn already_registered<T: 'static>() -> Self {
75        Self::AlreadyRegistered {
76            type_name: std::any::type_name::<T>(),
77        }
78    }
79
80    /// Create a `CircularDependency` error
81    #[inline]
82    pub fn circular<T: 'static>() -> Self {
83        Self::CircularDependency {
84            type_name: std::any::type_name::<T>(),
85        }
86    }
87}
88
89impl Clone for DiError {
90    fn clone(&self) -> Self {
91        match self {
92            Self::NotFound { type_name, type_id } => Self::NotFound {
93                type_name,
94                type_id: *type_id,
95            },
96            Self::CircularDependency { type_name } => Self::CircularDependency { type_name },
97            Self::CreationFailed { type_name, reason } => Self::CreationFailed {
98                type_name,
99                reason: reason.clone(),
100            },
101            Self::Locked => Self::Locked,
102            Self::AlreadyRegistered { type_name } => Self::AlreadyRegistered { type_name },
103            Self::ParentDropped => Self::ParentDropped,
104            Self::Internal(s) => Self::Internal(s.clone()),
105        }
106    }
107}
108
109/// Result type alias for DI operations
110pub type Result<T> = std::result::Result<T, DiError>;
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[allow(dead_code)]
117    struct MissingService;
118
119    #[test]
120    fn test_not_found_message_contains_type_name_and_hint() {
121        let err = DiError::not_found::<MissingService>();
122        let message = err.to_string();
123
124        // Names the missing type
125        assert!(message.contains("MissingService"));
126        // Explains the likely causes
127        assert!(message.contains("different scope"));
128        assert!(message.contains("clear()"));
129        // Points at the diagnostic helper
130        assert!(message.contains("Container::debug_registrations()"));
131    }
132
133    #[test]
134    fn test_not_found_carries_type_id() {
135        let err = DiError::not_found::<MissingService>();
136        match err {
137            DiError::NotFound { type_id, .. } => {
138                assert_eq!(type_id, TypeId::of::<MissingService>());
139            }
140            other => panic!("expected NotFound, got: {other}"),
141        }
142    }
143}