backbone_core/
module_registry.rs1use std::collections::{HashMap, HashSet};
48use std::sync::Arc;
49
50use crate::module::{BackboneModule, MigrationInfo, SeedInfo};
51
52#[derive(Debug, thiserror::Error)]
54pub enum ModuleRegistryError {
55 #[error("Circular dependency detected: {0}")]
56 CircularDependency(String),
57
58 #[error("Unknown dependency '{dependency}' for module '{module}'")]
59 UnknownDependency {
60 module: String,
61 dependency: String,
62 },
63
64 #[error("Module '{0}' is already registered")]
65 DuplicateModule(String),
66}
67
68pub type ModuleRegistryResult<T> = Result<T, ModuleRegistryError>;
70
71pub struct ModuleRegistry {
79 modules: HashMap<String, Arc<dyn BackboneModule>>,
80 order: Option<Vec<String>>,
81}
82
83impl ModuleRegistry {
84 pub fn new() -> Self {
86 Self {
87 modules: HashMap::new(),
88 order: None,
89 }
90 }
91
92 pub fn register<M: BackboneModule + 'static>(&mut self, module: M) -> ModuleRegistryResult<()> {
100 let name = module.name().to_string();
101
102 if self.modules.contains_key(&name) {
103 return Err(ModuleRegistryError::DuplicateModule(name));
104 }
105
106 self.modules.insert(name, Arc::new(module));
107 self.order = None; Ok(())
109 }
110
111 pub fn register_arc(&mut self, module: Arc<dyn BackboneModule>) -> ModuleRegistryResult<()> {
113 let name = module.name().to_string();
114
115 if self.modules.contains_key(&name) {
116 return Err(ModuleRegistryError::DuplicateModule(name));
117 }
118
119 self.modules.insert(name, module);
120 self.order = None;
121 Ok(())
122 }
123
124 pub fn len(&self) -> usize {
126 self.modules.len()
127 }
128
129 pub fn is_empty(&self) -> bool {
131 self.modules.is_empty()
132 }
133
134 pub fn get(&self, name: &str) -> Option<Arc<dyn BackboneModule>> {
136 self.modules.get(name).cloned()
137 }
138
139 pub fn module_names(&self) -> Vec<String> {
141 self.modules.keys().cloned().collect()
142 }
143
144 pub fn modules_ordered(&mut self) -> ModuleRegistryResult<Vec<Arc<dyn BackboneModule>>> {
152 if self.order.is_none() {
154 self.order = Some(self.topological_sort()?);
155 }
156
157 let order = self.order.as_ref().unwrap();
158 Ok(order
159 .iter()
160 .filter_map(|name| self.modules.get(name).cloned())
161 .collect())
162 }
163
164 pub fn all_migration_paths(&mut self) -> ModuleRegistryResult<Vec<MigrationInfo>> {
169 let modules = self.modules_ordered()?;
170 Ok(modules
171 .iter()
172 .filter_map(|m| m.migration_info())
173 .collect())
174 }
175
176 pub fn all_seed_paths(&mut self) -> ModuleRegistryResult<Vec<SeedInfo>> {
178 let modules = self.modules_ordered()?;
179 Ok(modules
180 .iter()
181 .filter_map(|m| m.seed_info())
182 .collect())
183 }
184
185 fn topological_sort(&self) -> ModuleRegistryResult<Vec<String>> {
187 let mut result = Vec::new();
188 let mut visited = HashSet::new();
189 let mut in_progress = HashSet::new();
190
191 let module_names: HashSet<_> = self.modules.keys().cloned().collect();
193
194 for module in self.modules.values() {
196 for dep in module.dependencies() {
197 if !module_names.contains(dep) {
198 return Err(ModuleRegistryError::UnknownDependency {
199 module: module.name().to_string(),
200 dependency: dep.to_string(),
201 });
202 }
203 }
204 }
205
206 for name in self.modules.keys() {
208 self.visit(name, &mut visited, &mut in_progress, &mut result)?;
209 }
210
211 Ok(result)
212 }
213
214 fn visit(
216 &self,
217 name: &str,
218 visited: &mut HashSet<String>,
219 in_progress: &mut HashSet<String>,
220 result: &mut Vec<String>,
221 ) -> ModuleRegistryResult<()> {
222 if visited.contains(name) {
223 return Ok(());
224 }
225
226 if in_progress.contains(name) {
227 return Err(ModuleRegistryError::CircularDependency(name.to_string()));
228 }
229
230 in_progress.insert(name.to_string());
231
232 if let Some(module) = self.modules.get(name) {
234 for dep in module.dependencies() {
235 self.visit(dep, visited, in_progress, result)?;
236 }
237 }
238
239 in_progress.remove(name);
240 visited.insert(name.to_string());
241 result.push(name.to_string());
242
243 Ok(())
244 }
245
246 pub async fn boot_all(&mut self) -> anyhow::Result<()> {
248 let modules = self.modules_ordered()?;
249 for module in modules {
250 tracing::info!("Booting module: {} v{}", module.name(), module.version());
251 module.on_boot().await?;
252 }
253 Ok(())
254 }
255
256 pub async fn shutdown_all(&mut self) -> anyhow::Result<()> {
258 let mut modules = self.modules_ordered()?;
259 modules.reverse(); for module in modules {
262 tracing::info!("Shutting down module: {}", module.name());
263 if let Err(e) = module.on_shutdown().await {
264 tracing::warn!("Error shutting down module {}: {}", module.name(), e);
265 }
266 }
267 Ok(())
268 }
269
270 pub async fn health_check_all(&self) -> HashMap<String, bool> {
272 let mut results = HashMap::new();
273
274 for (name, module) in &self.modules {
275 let healthy = module.health_check().await;
276 results.insert(name.clone(), healthy);
277 }
278
279 results
280 }
281
282 #[allow(clippy::print_literal)]
284 pub fn print_summary(&self) {
285 println!("\n📦 Registered Modules:");
286 println!(" {0:<20} {1:<12} {2}", "Name", "Version", "Dependencies");
287 println!(" {}", "-".repeat(60));
288
289 for (name, module) in &self.modules {
290 let deps = module.dependencies();
291 let deps_str = if deps.is_empty() {
292 "-".to_string()
293 } else {
294 deps.join(", ")
295 };
296 println!(" {:<20} {:<12} {}", name, module.version(), deps_str);
297 }
298 println!();
299 }
300}
301
302impl Default for ModuleRegistry {
303 fn default() -> Self {
304 Self::new()
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311 use crate::module::BackboneModule;
312 use std::path::PathBuf;
313
314 struct TestModuleA;
315 struct TestModuleB;
316 struct TestModuleC;
317
318 impl BackboneModule for TestModuleA {
319 fn name(&self) -> &'static str { "module_a" }
320 fn version(&self) -> &'static str { "1.0.0" }
321 fn dependencies(&self) -> Vec<&'static str> { vec![] }
322 fn migrations_path(&self) -> Option<PathBuf> {
323 Some(PathBuf::from("libs/modules/a/migrations"))
324 }
325 }
326
327 impl BackboneModule for TestModuleB {
328 fn name(&self) -> &'static str { "module_b" }
329 fn version(&self) -> &'static str { "1.0.0" }
330 fn dependencies(&self) -> Vec<&'static str> { vec!["module_a"] }
331 fn migrations_path(&self) -> Option<PathBuf> {
332 Some(PathBuf::from("libs/modules/b/migrations"))
333 }
334 }
335
336 impl BackboneModule for TestModuleC {
337 fn name(&self) -> &'static str { "module_c" }
338 fn version(&self) -> &'static str { "1.0.0" }
339 fn dependencies(&self) -> Vec<&'static str> { vec!["module_a", "module_b"] }
340 fn migrations_path(&self) -> Option<PathBuf> {
341 Some(PathBuf::from("libs/modules/c/migrations"))
342 }
343 }
344
345 #[test]
346 fn test_register_modules() {
347 let mut registry = ModuleRegistry::new();
348
349 assert!(registry.register(TestModuleA).is_ok());
350 assert!(registry.register(TestModuleB).is_ok());
351
352 assert_eq!(registry.len(), 2);
353 }
354
355 #[test]
356 fn test_duplicate_module() {
357 let mut registry = ModuleRegistry::new();
358
359 assert!(registry.register(TestModuleA).is_ok());
360
361 let result = registry.register(TestModuleA);
363 assert!(result.is_err());
364 }
365
366 #[test]
367 fn test_dependency_order() {
368 let mut registry = ModuleRegistry::new();
369
370 registry.register(TestModuleC).unwrap();
372 registry.register(TestModuleB).unwrap();
373 registry.register(TestModuleA).unwrap();
374
375 let ordered = registry.modules_ordered().unwrap();
376 let names: Vec<_> = ordered.iter().map(|m| m.name()).collect();
377
378 let pos_a = names.iter().position(|&n| n == "module_a").unwrap();
380 let pos_b = names.iter().position(|&n| n == "module_b").unwrap();
381 let pos_c = names.iter().position(|&n| n == "module_c").unwrap();
382
383 assert!(pos_a < pos_b);
384 assert!(pos_b < pos_c);
385 }
386
387 #[test]
388 fn test_migration_paths() {
389 let mut registry = ModuleRegistry::new();
390
391 registry.register(TestModuleA).unwrap();
392 registry.register(TestModuleB).unwrap();
393 registry.register(TestModuleC).unwrap();
394
395 let paths = registry.all_migration_paths().unwrap();
396 assert_eq!(paths.len(), 3);
397
398 assert_eq!(paths[0].module, "module_a");
400 }
401
402 struct CircularA;
403 struct CircularB;
404
405 impl BackboneModule for CircularA {
406 fn name(&self) -> &'static str { "circular_a" }
407 fn version(&self) -> &'static str { "1.0.0" }
408 fn dependencies(&self) -> Vec<&'static str> { vec!["circular_b"] }
409 fn migrations_path(&self) -> Option<PathBuf> { None }
410 }
411
412 impl BackboneModule for CircularB {
413 fn name(&self) -> &'static str { "circular_b" }
414 fn version(&self) -> &'static str { "1.0.0" }
415 fn dependencies(&self) -> Vec<&'static str> { vec!["circular_a"] }
416 fn migrations_path(&self) -> Option<PathBuf> { None }
417 }
418
419 #[test]
420 fn test_circular_dependency_detection() {
421 let mut registry = ModuleRegistry::new();
422
423 registry.register(CircularA).unwrap();
424 registry.register(CircularB).unwrap();
425
426 let result = registry.modules_ordered();
427 assert!(result.is_err());
428
429 if let Err(ModuleRegistryError::CircularDependency(_)) = result {
430 } else {
432 panic!("Expected CircularDependency error");
433 }
434 }
435
436 #[test]
437 fn test_unknown_dependency() {
438 let mut registry = ModuleRegistry::new();
439
440 registry.register(TestModuleB).unwrap();
442
443 let result = registry.modules_ordered();
444 assert!(result.is_err());
445
446 if let Err(ModuleRegistryError::UnknownDependency { module, dependency }) = result {
447 assert_eq!(module, "module_b");
448 assert_eq!(dependency, "module_a");
449 } else {
450 panic!("Expected UnknownDependency error");
451 }
452 }
453}