Skip to main content

injectable_rs_runtime/
error.rs

1//! Error types for the injectable framework.
2
3use std::fmt;
4
5/// Errors that can occur during dependency resolution.
6#[derive(Debug, Clone)]
7pub enum InjectableError {
8    /// A circular dependency was detected during resolution.
9    CircularDependency {
10        /// The type where the cycle was detected.
11        type_name: &'static str,
12        /// The dependency chain leading to the cycle.
13        chain: Vec<&'static str>,
14    },
15    /// A required dependency was not found in the container.
16    MissingDependency {
17        /// The name of the missing dependency type.
18        type_name: &'static str,
19    },
20    /// Constructor invocation failed.
21    ConstructionFailed {
22        /// The type that failed to construct.
23        type_name: &'static str,
24        /// A description of the failure.
25        reason: String,
26    },
27    /// A lifecycle hook (`post_construct` / `pre_destruct`) failed.
28    LifecycleHookFailed {
29        /// The type whose hook failed.
30        type_name: &'static str,
31        /// Which hook failed.
32        hook: &'static str,
33        /// A description of the failure.
34        reason: String,
35    },
36    /// One or more `pre_destruct` hooks failed during container shutdown.
37    ///
38    /// All destructors are still called (best-effort cleanup), but
39    /// this error collects any failures that occurred. Inspect
40    /// `errors` for details on each individual failure.
41    ShutdownFailed {
42        /// The individual errors from failed `pre_destruct` hooks.
43        errors: Vec<InjectableError>,
44    },
45    /// Container has not been built yet.
46    ContainerNotBuilt,
47    /// The dependency graph is structurally invalid.
48    ///
49    /// Returned only from `Container::builder().build()` when circular
50    /// dependencies, missing dependencies, scope mismatches, or duplicate
51    /// registrations are detected at build time.
52    ///
53    /// Semantically distinct from `ConstructionFailed` (which is a runtime
54    /// provider error): `GraphValidationFailed` means the type wiring is wrong
55    /// and must be fixed in code, not retried.
56    GraphValidationFailed {
57        /// Human-readable description of each validation error.
58        errors: Vec<String>,
59    },
60}
61
62impl fmt::Display for InjectableError {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        match self {
65            Self::CircularDependency { type_name, chain } => {
66                write!(f, "circular dependency detected for `{type_name}`: ")?;
67                for (i, t) in chain.iter().enumerate() {
68                    if i > 0 {
69                        write!(f, " -> ")?;
70                    }
71                    write!(f, "{t}")?;
72                }
73                write!(f, " -> {type_name}")
74            }
75            Self::MissingDependency { type_name } => {
76                write!(
77                    f,
78                    "missing dependency: no provider registered for `{type_name}`"
79                )
80            }
81            Self::ConstructionFailed { type_name, reason } => {
82                write!(f, "construction of `{type_name}` failed: {reason}")
83            }
84            Self::LifecycleHookFailed {
85                type_name,
86                hook,
87                reason,
88            } => {
89                write!(
90                    f,
91                    "lifecycle hook `{hook}` on `{type_name}` failed: {reason}"
92                )
93            }
94            Self::ShutdownFailed { errors } => {
95                write!(
96                    f,
97                    "container shutdown failed with {} error(s):",
98                    errors.len()
99                )?;
100                for (i, err) in errors.iter().enumerate() {
101                    write!(f, "\n  {}. {err}", i + 1)?;
102                }
103                Ok(())
104            }
105            Self::ContainerNotBuilt => write!(f, "container has not been built"),
106            Self::GraphValidationFailed { errors } => {
107                write!(f, "dependency graph validation failed:")?;
108                for err in errors {
109                    write!(f, "\n  - {err}")?;
110                }
111                Ok(())
112            }
113        }
114    }
115}
116
117impl std::error::Error for InjectableError {}
118
119/// A specialized `Result` type for injectable operations.
120pub type InjectableResult<T> = Result<T, InjectableError>;
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn display_circular_dependency() {
128        let e = InjectableError::CircularDependency {
129            type_name: "Foo",
130            chain: vec!["Foo", "Bar"],
131        };
132        let s = e.to_string();
133        assert!(s.contains("circular dependency"));
134        assert!(s.contains("Foo"));
135        assert!(s.contains("Bar"));
136        assert!(s.contains(" -> "));
137    }
138
139    #[test]
140    fn display_missing_dependency() {
141        let e = InjectableError::MissingDependency {
142            type_name: "Database",
143        };
144        let s = e.to_string();
145        assert!(s.contains("missing dependency"));
146        assert!(s.contains("Database"));
147    }
148
149    #[test]
150    fn display_construction_failed() {
151        let e = InjectableError::ConstructionFailed {
152            type_name: "Pool",
153            reason: "connection refused".to_string(),
154        };
155        let s = e.to_string();
156        assert!(s.contains("Pool"));
157        assert!(s.contains("connection refused"));
158    }
159
160    #[test]
161    fn display_lifecycle_hook_failed() {
162        let e = InjectableError::LifecycleHookFailed {
163            type_name: "Db",
164            hook: "post_construct",
165            reason: "migration error".to_string(),
166        };
167        let s = e.to_string();
168        assert!(s.contains("post_construct"));
169        assert!(s.contains("Db"));
170        assert!(s.contains("migration error"));
171    }
172
173    #[test]
174    fn display_shutdown_failed_single() {
175        let inner = InjectableError::LifecycleHookFailed {
176            type_name: "X",
177            hook: "pre_destruct",
178            reason: "oops".to_string(),
179        };
180        let e = InjectableError::ShutdownFailed {
181            errors: vec![inner],
182        };
183        let s = e.to_string();
184        assert!(s.contains("shutdown failed"));
185        assert!(s.contains("1 error"));
186        assert!(s.contains("oops"));
187    }
188
189    #[test]
190    fn display_shutdown_failed_multiple() {
191        let errs = vec![
192            InjectableError::MissingDependency { type_name: "A" },
193            InjectableError::MissingDependency { type_name: "B" },
194        ];
195        let e = InjectableError::ShutdownFailed { errors: errs };
196        let s = e.to_string();
197        assert!(s.contains("2 error"));
198    }
199
200    #[test]
201    fn display_container_not_built() {
202        let e = InjectableError::ContainerNotBuilt;
203        let s = e.to_string();
204        assert!(s.contains("not been built"));
205    }
206
207    #[test]
208    fn display_graph_validation_failed() {
209        let e = InjectableError::GraphValidationFailed {
210            errors: vec!["err1".to_string(), "err2".to_string()],
211        };
212        let s = e.to_string();
213        assert!(s.contains("graph validation failed"));
214        assert!(s.contains("err1"));
215        assert!(s.contains("err2"));
216    }
217
218    #[test]
219    fn error_trait_impl() {
220        let e = InjectableError::ContainerNotBuilt;
221        let _: &dyn std::error::Error = &e;
222    }
223
224    #[test]
225    fn clone_and_debug() {
226        let e = InjectableError::MissingDependency { type_name: "X" };
227        let e2 = e.clone();
228        let _ = format!("{e2:?}");
229    }
230}