injectable_rs_runtime/
error.rs1use std::fmt;
4
5#[derive(Debug, Clone)]
7pub enum InjectableError {
8 CircularDependency {
10 type_name: &'static str,
12 chain: Vec<&'static str>,
14 },
15 MissingDependency {
17 type_name: &'static str,
19 },
20 ConstructionFailed {
22 type_name: &'static str,
24 reason: String,
26 },
27 LifecycleHookFailed {
29 type_name: &'static str,
31 hook: &'static str,
33 reason: String,
35 },
36 ShutdownFailed {
42 errors: Vec<InjectableError>,
44 },
45 ContainerNotBuilt,
47 GraphValidationFailed {
57 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
119pub 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}