injectable_rs_graph/
validate.rs1#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum ValidationError {
6 CircularDependency {
11 chain: Vec<String>,
13 },
14
15 MissingDependency {
17 source: String,
19 missing: String,
21 },
22
23 DuplicateNode {
25 name: String,
27 },
28
29 MultipleConstructors {
31 type_name: String,
33 count: usize,
35 },
36
37 DuplicateLifecycleHook {
39 type_name: String,
41 hook: String,
43 },
44
45 InvalidConstructorReturn {
47 type_name: String,
49 expected: String,
51 },
52
53 ScopeMismatch {
58 source: String,
60 source_scope: String,
62 dependency: String,
64 dependency_scope: String,
66 },
67}
68
69impl std::fmt::Display for ValidationError {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 match self {
72 Self::CircularDependency { chain } => {
73 write!(f, "circular dependency detected: ")?;
74 for (i, t) in chain.iter().enumerate() {
75 if i > 0 {
76 write!(f, " -> ")?;
77 }
78 write!(f, "{t}")?;
79 }
80 Ok(())
81 }
82 Self::MissingDependency { source, missing } => {
83 write!(
84 f,
85 "`{source}` depends on `{missing}`, which is not registered"
86 )
87 }
88 Self::DuplicateNode { name } => {
89 write!(f, "duplicate node definition for `{name}`")
90 }
91 Self::MultipleConstructors { type_name, count } => {
92 write!(
93 f,
94 "`{type_name}` has {count} constructors; expected exactly 1"
95 )
96 }
97 Self::DuplicateLifecycleHook { type_name, hook } => {
98 write!(f, "`{type_name}` has duplicate `{hook}` hooks")
99 }
100 Self::InvalidConstructorReturn {
101 type_name,
102 expected,
103 } => {
104 write!(f, "constructor for `{type_name}` must return `{expected}`")
105 }
106 Self::ScopeMismatch {
107 source,
108 source_scope,
109 dependency,
110 dependency_scope,
111 } => {
112 write!(
113 f,
114 "scope mismatch: `{source}` ({source_scope}) depends on `{dependency}` ({dependency_scope}); \
115 wider-scope types cannot depend on narrower-scope types"
116 )
117 }
118 }
119 }
120}
121
122impl std::error::Error for ValidationError {}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn circular_dependency_display() {
130 let e = ValidationError::CircularDependency {
131 chain: vec!["A".to_string(), "B".to_string(), "A".to_string()],
132 };
133 let s = e.to_string();
134 assert!(s.contains("circular dependency"));
135 assert!(s.contains("A -> B -> A"));
136 }
137
138 #[test]
139 fn missing_dependency_display() {
140 let e = ValidationError::MissingDependency {
141 source: "UserService".to_string(),
142 missing: "Database".to_string(),
143 };
144 let s = e.to_string();
145 assert!(s.contains("UserService"));
146 assert!(s.contains("Database"));
147 assert!(s.contains("not registered"));
148 }
149
150 #[test]
151 fn duplicate_node_display() {
152 let e = ValidationError::DuplicateNode {
153 name: "Cache".to_string(),
154 };
155 let s = e.to_string();
156 assert!(s.contains("duplicate"));
157 assert!(s.contains("Cache"));
158 }
159
160 #[test]
161 fn multiple_constructors_display() {
162 let e = ValidationError::MultipleConstructors {
163 type_name: "Foo".to_string(),
164 count: 3,
165 };
166 let s = e.to_string();
167 assert!(s.contains("Foo"));
168 assert!(s.contains("3"));
169 }
170
171 #[test]
172 fn duplicate_lifecycle_hook_display() {
173 let e = ValidationError::DuplicateLifecycleHook {
174 type_name: "Bar".to_string(),
175 hook: "post_construct".to_string(),
176 };
177 let s = e.to_string();
178 assert!(s.contains("Bar"));
179 assert!(s.contains("post_construct"));
180 }
181
182 #[test]
183 fn invalid_constructor_return_display() {
184 let e = ValidationError::InvalidConstructorReturn {
185 type_name: "Baz".to_string(),
186 expected: "Self".to_string(),
187 };
188 let s = e.to_string();
189 assert!(s.contains("Baz"));
190 assert!(s.contains("Self"));
191 }
192
193 #[test]
194 fn scope_mismatch_display() {
195 let e = ValidationError::ScopeMismatch {
196 source: "Singleton".to_string(),
197 source_scope: "singleton".to_string(),
198 dependency: "Transient".to_string(),
199 dependency_scope: "transient".to_string(),
200 };
201 let s = e.to_string();
202 assert!(s.contains("scope mismatch"));
203 assert!(s.contains("singleton"));
204 assert!(s.contains("transient"));
205 }
206
207 #[test]
208 fn error_trait_impl() {
209 let e = ValidationError::DuplicateNode {
210 name: "X".to_string(),
211 };
212 let _: &dyn std::error::Error = &e;
213 }
214
215 #[test]
216 fn clone_and_eq() {
217 let a = ValidationError::DuplicateNode {
218 name: "A".to_string(),
219 };
220 let b = a.clone();
221 assert_eq!(a, b);
222 }
223}