Skip to main content

nestforge_core/
module.rs

1use anyhow::Result;
2use axum::Router;
3use std::{collections::HashSet, future::Future, sync::Arc};
4
5use crate::{
6    framework_log_event, Container, DocumentedController, RegisterProvider, RouteDocumentation,
7};
8
9pub type LifecycleHook = fn(&Container) -> Result<()>;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct ModuleGraphEntry {
13    pub name: &'static str,
14    pub imports: Vec<&'static str>,
15    pub exports: Vec<&'static str>,
16    pub controller_count: usize,
17    pub is_global: bool,
18}
19
20#[derive(Debug, Clone, Default, PartialEq, Eq)]
21pub struct ModuleGraphReport {
22    pub modules: Vec<ModuleGraphEntry>,
23}
24
25impl ModuleGraphReport {
26    pub fn module(&self, name: &str) -> Option<&ModuleGraphEntry> {
27        self.modules.iter().find(|module| module.name == name)
28    }
29}
30
31/*
32ControllerBasePath = metadata implemented by #[controller("/...")]
33*/
34pub trait ControllerBasePath {
35    fn base_path() -> &'static str;
36}
37
38/*
39ControllerDefinition = generated by #[routes] for a controller
40*/
41pub trait ControllerDefinition: Send + Sync + 'static {
42    fn router() -> Router<Container>;
43}
44
45#[derive(Clone)]
46pub struct ModuleRef {
47    pub name: &'static str,
48    pub register: Arc<dyn Fn(&Container) -> Result<()> + Send + Sync>,
49    pub controllers: Arc<dyn Fn() -> Vec<Router<Container>> + Send + Sync>,
50    pub route_docs: Arc<dyn Fn() -> Vec<RouteDocumentation> + Send + Sync>,
51    pub on_module_init: Arc<dyn Fn() -> Vec<LifecycleHook> + Send + Sync>,
52    pub on_module_destroy: Arc<dyn Fn() -> Vec<LifecycleHook> + Send + Sync>,
53    pub on_application_bootstrap: Arc<dyn Fn() -> Vec<LifecycleHook> + Send + Sync>,
54    pub on_application_shutdown: Arc<dyn Fn() -> Vec<LifecycleHook> + Send + Sync>,
55    pub imports: Arc<dyn Fn() -> Vec<ModuleRef> + Send + Sync>,
56    pub exports: Arc<dyn Fn() -> Vec<&'static str> + Send + Sync>,
57    pub is_global: Arc<dyn Fn() -> bool + Send + Sync>,
58}
59
60impl ModuleRef {
61    pub fn of<M: ModuleDefinition>() -> Self {
62        Self {
63            name: M::module_name(),
64            register: Arc::new(M::register),
65            controllers: Arc::new(M::controllers),
66            route_docs: Arc::new(M::route_docs),
67            on_module_init: Arc::new(M::on_module_init),
68            on_module_destroy: Arc::new(M::on_module_destroy),
69            on_application_bootstrap: Arc::new(M::on_application_bootstrap),
70            on_application_shutdown: Arc::new(M::on_application_shutdown),
71            imports: Arc::new(M::imports),
72            exports: Arc::new(M::exports),
73            is_global: Arc::new(M::is_global),
74        }
75    }
76
77    pub fn dynamic(
78        name: &'static str,
79        register: impl Fn(&Container) -> Result<()> + Send + Sync + 'static,
80    ) -> Self {
81        Self {
82            name,
83            register: Arc::new(register),
84            controllers: Arc::new(Vec::new),
85            route_docs: Arc::new(Vec::new),
86            on_module_init: Arc::new(Vec::new),
87            on_module_destroy: Arc::new(Vec::new),
88            on_application_bootstrap: Arc::new(Vec::new),
89            on_application_shutdown: Arc::new(Vec::new),
90            imports: Arc::new(Vec::new),
91            exports: Arc::new(Vec::new),
92            is_global: Arc::new(|| false),
93        }
94    }
95
96    pub fn builder(name: &'static str) -> DynamicModuleBuilder {
97        DynamicModuleBuilder::new(name)
98    }
99
100    pub fn with_imports(
101        mut self,
102        imports: impl Fn() -> Vec<ModuleRef> + Send + Sync + 'static,
103    ) -> Self {
104        self.imports = Arc::new(imports);
105        self
106    }
107
108    pub fn with_exports(
109        mut self,
110        exports: impl Fn() -> Vec<&'static str> + Send + Sync + 'static,
111    ) -> Self {
112        self.exports = Arc::new(exports);
113        self
114    }
115
116    pub fn with_controllers(
117        mut self,
118        controllers: impl Fn() -> Vec<Router<Container>> + Send + Sync + 'static,
119    ) -> Self {
120        self.controllers = Arc::new(controllers);
121        self
122    }
123
124    pub fn with_route_docs(
125        mut self,
126        route_docs: impl Fn() -> Vec<RouteDocumentation> + Send + Sync + 'static,
127    ) -> Self {
128        self.route_docs = Arc::new(route_docs);
129        self
130    }
131
132    pub fn with_module_init_hooks(
133        mut self,
134        hooks: impl Fn() -> Vec<LifecycleHook> + Send + Sync + 'static,
135    ) -> Self {
136        self.on_module_init = Arc::new(hooks);
137        self
138    }
139
140    pub fn with_module_destroy_hooks(
141        mut self,
142        hooks: impl Fn() -> Vec<LifecycleHook> + Send + Sync + 'static,
143    ) -> Self {
144        self.on_module_destroy = Arc::new(hooks);
145        self
146    }
147
148    pub fn with_application_bootstrap_hooks(
149        mut self,
150        hooks: impl Fn() -> Vec<LifecycleHook> + Send + Sync + 'static,
151    ) -> Self {
152        self.on_application_bootstrap = Arc::new(hooks);
153        self
154    }
155
156    pub fn with_application_shutdown_hooks(
157        mut self,
158        hooks: impl Fn() -> Vec<LifecycleHook> + Send + Sync + 'static,
159    ) -> Self {
160        self.on_application_shutdown = Arc::new(hooks);
161        self
162    }
163
164    pub fn as_global(mut self) -> Self {
165        self.is_global = Arc::new(|| true);
166        self
167    }
168
169    pub fn with_is_global(mut self, is_global: bool) -> Self {
170        self.is_global = Arc::new(move || is_global);
171        self
172    }
173}
174
175type RegistrationStep = Arc<dyn Fn(&Container) -> Result<()> + Send + Sync>;
176
177pub struct DynamicModuleBuilder {
178    name: &'static str,
179    registration_steps: Vec<RegistrationStep>,
180    imports: Vec<ModuleRef>,
181    exports: Vec<&'static str>,
182    controllers: Vec<Router<Container>>,
183    route_docs: Vec<RouteDocumentation>,
184    on_module_init: Vec<LifecycleHook>,
185    on_module_destroy: Vec<LifecycleHook>,
186    on_application_bootstrap: Vec<LifecycleHook>,
187    on_application_shutdown: Vec<LifecycleHook>,
188    is_global: bool,
189}
190
191impl DynamicModuleBuilder {
192    pub fn new(name: &'static str) -> Self {
193        Self {
194            name,
195            registration_steps: Vec::new(),
196            imports: Vec::new(),
197            exports: Vec::new(),
198            controllers: Vec::new(),
199            route_docs: Vec::new(),
200            on_module_init: Vec::new(),
201            on_module_destroy: Vec::new(),
202            on_application_bootstrap: Vec::new(),
203            on_application_shutdown: Vec::new(),
204            is_global: false,
205        }
206    }
207
208    pub fn register(
209        mut self,
210        register: impl Fn(&Container) -> Result<()> + Send + Sync + 'static,
211    ) -> Self {
212        self.registration_steps.push(Arc::new(register));
213        self
214    }
215
216    pub fn register_provider<P>(self, provider: P) -> Self
217    where
218        P: RegisterProvider + Send + 'static,
219    {
220        let provider = Arc::new(std::sync::Mutex::new(Some(provider)));
221        self.register(move |container| {
222            let provider = provider
223                .lock()
224                .map_err(|_| anyhow::anyhow!("Dynamic module provider lock poisoned"))?
225                .take()
226                .ok_or_else(|| anyhow::anyhow!("Dynamic module provider already registered"))?;
227            provider.register(container)
228        })
229    }
230
231    pub fn provider_value<T>(self, value: T) -> Self
232    where
233        T: Clone + Send + Sync + 'static,
234    {
235        self.register(move |container| {
236            container.register(value.clone())?;
237            Ok(())
238        })
239    }
240
241    pub fn provider_factory<T, F>(self, factory: F) -> Self
242    where
243        T: Send + Sync + 'static,
244        F: Fn(&Container) -> Result<T> + Send + Sync + 'static,
245    {
246        self.register(move |container| {
247            container.register(factory(container)?)?;
248            Ok(())
249        })
250    }
251
252    pub fn provider_async<T, F, Fut>(self, factory: F) -> Self
253    where
254        T: Send + Sync + 'static,
255        F: Fn(Container) -> Fut + Send + Sync + 'static,
256        Fut: Future<Output = Result<T>> + Send + 'static,
257    {
258        self.register(move |container| {
259            let value = block_on_dynamic_registration(factory(container.clone()))?;
260            container.register(value)?;
261            Ok(())
262        })
263    }
264
265    pub fn import(mut self, module: ModuleRef) -> Self {
266        self.imports.push(module);
267        self
268    }
269
270    pub fn export<T>(mut self) -> Self
271    where
272        T: 'static,
273    {
274        self.exports.push(std::any::type_name::<T>());
275        self
276    }
277
278    pub fn controller<C>(mut self) -> Self
279    where
280        C: ControllerDefinition + DocumentedController,
281    {
282        self.controllers.push(C::router());
283        self.route_docs.extend(C::route_docs());
284        self
285    }
286
287    pub fn on_module_init(mut self, hook: LifecycleHook) -> Self {
288        self.on_module_init.push(hook);
289        self
290    }
291
292    pub fn on_module_destroy(mut self, hook: LifecycleHook) -> Self {
293        self.on_module_destroy.push(hook);
294        self
295    }
296
297    pub fn on_application_bootstrap(mut self, hook: LifecycleHook) -> Self {
298        self.on_application_bootstrap.push(hook);
299        self
300    }
301
302    pub fn on_application_shutdown(mut self, hook: LifecycleHook) -> Self {
303        self.on_application_shutdown.push(hook);
304        self
305    }
306
307    pub fn global(mut self) -> Self {
308        self.is_global = true;
309        self
310    }
311
312    pub fn build(self) -> ModuleRef {
313        let registration_steps = Arc::new(self.registration_steps);
314        let imports = self.imports;
315        let exports = self.exports;
316        let controllers = self.controllers;
317        let route_docs = self.route_docs;
318        let on_module_init = self.on_module_init;
319        let on_module_destroy = self.on_module_destroy;
320        let on_application_bootstrap = self.on_application_bootstrap;
321        let on_application_shutdown = self.on_application_shutdown;
322        let is_global = self.is_global;
323
324        ModuleRef::dynamic(self.name, move |container| {
325            for step in registration_steps.iter() {
326                step(container)?;
327            }
328            Ok(())
329        })
330        .with_imports(move || imports.clone())
331        .with_exports(move || exports.clone())
332        .with_controllers(move || controllers.clone())
333        .with_route_docs(move || route_docs.clone())
334        .with_module_init_hooks(move || on_module_init.clone())
335        .with_module_destroy_hooks(move || on_module_destroy.clone())
336        .with_application_bootstrap_hooks(move || on_application_bootstrap.clone())
337        .with_application_shutdown_hooks(move || on_application_shutdown.clone())
338        .with_is_global(is_global)
339    }
340}
341
342/*
343ModuleDefinition = app module contract (manual for now)
344*/
345pub trait ModuleDefinition: Send + Sync + 'static {
346    fn module_name() -> &'static str {
347        std::any::type_name::<Self>()
348    }
349
350    fn register(container: &Container) -> Result<()>;
351
352    fn imports() -> Vec<ModuleRef> {
353        Vec::new()
354    }
355
356    fn is_global() -> bool {
357        false
358    }
359
360    fn exports() -> Vec<&'static str> {
361        Vec::new()
362    }
363
364    fn controllers() -> Vec<Router<Container>> {
365        Vec::new()
366    }
367
368    fn route_docs() -> Vec<RouteDocumentation> {
369        Vec::new()
370    }
371
372    fn on_module_init() -> Vec<LifecycleHook> {
373        Vec::new()
374    }
375
376    fn on_module_destroy() -> Vec<LifecycleHook> {
377        Vec::new()
378    }
379
380    fn on_application_bootstrap() -> Vec<LifecycleHook> {
381        Vec::new()
382    }
383
384    fn on_application_shutdown() -> Vec<LifecycleHook> {
385        Vec::new()
386    }
387}
388
389pub struct InitializedModule {
390    pub controllers: Vec<Router<Container>>,
391    module_init_hooks: Vec<LifecycleHook>,
392    module_destroy_hooks: Vec<LifecycleHook>,
393    application_bootstrap_hooks: Vec<LifecycleHook>,
394    application_shutdown_hooks: Vec<LifecycleHook>,
395}
396
397impl InitializedModule {
398    pub fn run_module_init(&self, container: &Container) -> Result<()> {
399        run_lifecycle_hooks("module_init", &self.module_init_hooks, container)
400    }
401
402    pub fn run_module_destroy(&self, container: &Container) -> Result<()> {
403        run_lifecycle_hooks("module_destroy", &self.module_destroy_hooks, container)
404    }
405
406    pub fn run_application_bootstrap(&self, container: &Container) -> Result<()> {
407        run_lifecycle_hooks(
408            "application_bootstrap",
409            &self.application_bootstrap_hooks,
410            container,
411        )
412    }
413
414    pub fn run_application_shutdown(&self, container: &Container) -> Result<()> {
415        run_lifecycle_hooks(
416            "application_shutdown",
417            &self.application_shutdown_hooks,
418            container,
419        )
420    }
421}
422
423pub fn initialize_module_runtime<M: ModuleDefinition>(
424    container: &Container,
425) -> Result<InitializedModule> {
426    let mut state = ModuleGraphState::default();
427    visit_module(ModuleRef::of::<M>(), container, &mut state)?;
428    state.module_destroy_hooks.reverse();
429    state.application_shutdown_hooks.reverse();
430    Ok(InitializedModule {
431        controllers: state.controllers,
432        module_init_hooks: state.module_init_hooks,
433        module_destroy_hooks: state.module_destroy_hooks,
434        application_bootstrap_hooks: state.application_bootstrap_hooks,
435        application_shutdown_hooks: state.application_shutdown_hooks,
436    })
437}
438
439pub fn initialize_module_graph<M: ModuleDefinition>(
440    container: &Container,
441) -> Result<Vec<Router<Container>>> {
442    Ok(initialize_module_runtime::<M>(container)?.controllers)
443}
444
445pub fn collect_module_route_docs<M: ModuleDefinition>() -> Result<Vec<RouteDocumentation>> {
446    let mut state = DocumentationGraphState::default();
447    visit_module_docs(ModuleRef::of::<M>(), &mut state)?;
448    Ok(state.route_docs)
449}
450
451pub fn collect_module_graph<M: ModuleDefinition>() -> Result<ModuleGraphReport> {
452    let mut state = ModuleIntrospectionState::default();
453    visit_module_graph(ModuleRef::of::<M>(), &mut state)?;
454    Ok(ModuleGraphReport {
455        modules: state.modules,
456    })
457}
458
459#[derive(Default)]
460struct ModuleGraphState {
461    visited: HashSet<&'static str>,
462    visiting: HashSet<&'static str>,
463    stack: Vec<&'static str>,
464    controllers: Vec<Router<Container>>,
465    module_init_hooks: Vec<LifecycleHook>,
466    module_destroy_hooks: Vec<LifecycleHook>,
467    application_bootstrap_hooks: Vec<LifecycleHook>,
468    application_shutdown_hooks: Vec<LifecycleHook>,
469    global_modules: HashSet<&'static str>,
470}
471
472#[derive(Default)]
473struct DocumentationGraphState {
474    visited: HashSet<&'static str>,
475    visiting: HashSet<&'static str>,
476    stack: Vec<&'static str>,
477    route_docs: Vec<RouteDocumentation>,
478}
479
480#[derive(Default)]
481struct ModuleIntrospectionState {
482    visited: HashSet<&'static str>,
483    visiting: HashSet<&'static str>,
484    stack: Vec<&'static str>,
485    modules: Vec<ModuleGraphEntry>,
486}
487
488fn visit_module(
489    module: ModuleRef,
490    container: &Container,
491    state: &mut ModuleGraphState,
492) -> Result<()> {
493    if state.visited.contains(module.name) {
494        return Ok(());
495    }
496
497    if state.visiting.contains(module.name) {
498        let mut cycle = state.stack.clone();
499        cycle.push(module.name);
500        anyhow::bail!("Detected module import cycle: {}", cycle.join(" -> "));
501    }
502
503    state.visiting.insert(module.name);
504    state.stack.push(module.name);
505    framework_log_event("module_register", &[("module", module.name.to_string())]);
506
507    for imported in (module.imports)() {
508        visit_module(imported, container, state)?;
509    }
510
511    (module.register)(container)
512        .map_err(|err| anyhow::anyhow!("Failed to register module `{}`: {}", module.name, err))?;
513
514    state.controllers.extend((module.controllers)());
515    state.module_init_hooks.extend((module.on_module_init)());
516    state
517        .module_destroy_hooks
518        .extend((module.on_module_destroy)());
519    state
520        .application_bootstrap_hooks
521        .extend((module.on_application_bootstrap)());
522    state
523        .application_shutdown_hooks
524        .extend((module.on_application_shutdown)());
525
526    if (module.is_global)() {
527        state.global_modules.insert(module.name);
528    }
529
530    for export in (module.exports)() {
531        let is_registered = container.is_type_registered_name(export).map_err(|err| {
532            anyhow::anyhow!(
533                "Failed to verify exports for module `{}`: {}",
534                module.name,
535                err
536            )
537        })?;
538        if !is_registered {
539            anyhow::bail!(
540                "Module `{}` exports `{}` but that provider is not registered in the container",
541                module.name,
542                export
543            );
544        }
545    }
546
547    state.stack.pop();
548    state.visiting.remove(module.name);
549    state.visited.insert(module.name);
550
551    Ok(())
552}
553
554fn run_lifecycle_hooks(
555    phase: &'static str,
556    hooks: &[LifecycleHook],
557    container: &Container,
558) -> Result<()> {
559    for hook in hooks {
560        framework_log_event("lifecycle_hook_run", &[("phase", phase.to_string())]);
561        hook(container)?;
562    }
563
564    Ok(())
565}
566
567fn block_on_dynamic_registration<T, Fut>(future: Fut) -> Result<T>
568where
569    T: Send + 'static,
570    Fut: Future<Output = Result<T>> + Send + 'static,
571{
572    if tokio::runtime::Handle::try_current().is_ok() {
573        return std::thread::spawn(move || {
574            tokio::runtime::Builder::new_current_thread()
575                .enable_all()
576                .build()
577                .map_err(anyhow::Error::new)?
578                .block_on(future)
579        })
580        .join()
581        .map_err(|_| anyhow::anyhow!("Dynamic module async registration thread panicked"))?;
582    }
583
584    tokio::runtime::Builder::new_current_thread()
585        .enable_all()
586        .build()
587        .map_err(anyhow::Error::new)?
588        .block_on(future)
589}
590
591fn visit_module_docs(module: ModuleRef, state: &mut DocumentationGraphState) -> Result<()> {
592    if state.visited.contains(module.name) {
593        return Ok(());
594    }
595
596    if state.visiting.contains(module.name) {
597        let mut cycle = state.stack.clone();
598        cycle.push(module.name);
599        anyhow::bail!(
600            "Detected module import cycle while collecting route docs: {}",
601            cycle.join(" -> ")
602        );
603    }
604
605    state.visiting.insert(module.name);
606    state.stack.push(module.name);
607
608    for imported in (module.imports)() {
609        visit_module_docs(imported, state)?;
610    }
611
612    state.route_docs.extend((module.route_docs)());
613    state.stack.pop();
614    state.visiting.remove(module.name);
615    state.visited.insert(module.name);
616    Ok(())
617}
618
619fn visit_module_graph(module: ModuleRef, state: &mut ModuleIntrospectionState) -> Result<()> {
620    if state.visited.contains(module.name) {
621        return Ok(());
622    }
623
624    if state.visiting.contains(module.name) {
625        let mut cycle = state.stack.clone();
626        cycle.push(module.name);
627        anyhow::bail!(
628            "Detected module import cycle while collecting module graph: {}",
629            cycle.join(" -> ")
630        );
631    }
632
633    state.visiting.insert(module.name);
634    state.stack.push(module.name);
635
636    let imports = (module.imports)();
637    let import_names = imports.iter().map(|import| import.name).collect::<Vec<_>>();
638
639    for imported in imports {
640        visit_module_graph(imported, state)?;
641    }
642
643    state.modules.push(ModuleGraphEntry {
644        name: module.name,
645        imports: import_names,
646        exports: (module.exports)(),
647        controller_count: (module.controllers)().len(),
648        is_global: (module.is_global)(),
649    });
650
651    state.stack.pop();
652    state.visiting.remove(module.name);
653    state.visited.insert(module.name);
654    Ok(())
655}
656
657#[cfg(test)]
658mod tests {
659    use super::*;
660    use std::sync::{Arc, Mutex};
661
662    struct ImportedConfig;
663    struct AppService;
664
665    struct ImportedModule;
666    impl ModuleDefinition for ImportedModule {
667        fn register(container: &Container) -> Result<()> {
668            container.register(ImportedConfig)?;
669            Ok(())
670        }
671    }
672
673    struct AppModule;
674    impl ModuleDefinition for AppModule {
675        fn imports() -> Vec<ModuleRef> {
676            vec![ModuleRef::of::<ImportedModule>()]
677        }
678
679        fn register(container: &Container) -> Result<()> {
680            let imported = container.resolve::<ImportedConfig>()?;
681            container.register(AppService::from(imported))?;
682            Ok(())
683        }
684    }
685
686    impl From<std::sync::Arc<ImportedConfig>> for AppService {
687        fn from(_: std::sync::Arc<ImportedConfig>) -> Self {
688            Self
689        }
690    }
691
692    #[test]
693    fn registers_imported_modules_before_local_providers() {
694        let container = Container::new();
695        let result = initialize_module_graph::<AppModule>(&container);
696
697        assert!(result.is_ok(), "module graph registration should succeed");
698        assert!(container.resolve::<ImportedConfig>().is_ok());
699        assert!(container.resolve::<AppService>().is_ok());
700    }
701
702    struct SharedImportedModule;
703    impl ModuleDefinition for SharedImportedModule {
704        fn register(container: &Container) -> Result<()> {
705            container.register(SharedMarker)?;
706            Ok(())
707        }
708    }
709
710    struct SharedMarker;
711    struct LeftModule;
712    struct RightModule;
713    struct RootModule;
714
715    impl ModuleDefinition for LeftModule {
716        fn imports() -> Vec<ModuleRef> {
717            vec![ModuleRef::of::<SharedImportedModule>()]
718        }
719
720        fn register(_container: &Container) -> Result<()> {
721            Ok(())
722        }
723    }
724
725    impl ModuleDefinition for RightModule {
726        fn imports() -> Vec<ModuleRef> {
727            vec![ModuleRef::of::<SharedImportedModule>()]
728        }
729
730        fn register(_container: &Container) -> Result<()> {
731            Ok(())
732        }
733    }
734
735    impl ModuleDefinition for RootModule {
736        fn imports() -> Vec<ModuleRef> {
737            vec![
738                ModuleRef::of::<LeftModule>(),
739                ModuleRef::of::<RightModule>(),
740            ]
741        }
742
743        fn register(_container: &Container) -> Result<()> {
744            Ok(())
745        }
746    }
747
748    #[test]
749    fn deduplicates_shared_imported_modules() {
750        let container = Container::new();
751        let result = initialize_module_graph::<RootModule>(&container);
752
753        assert!(
754            result.is_ok(),
755            "shared imported modules should only register once"
756        );
757        assert!(container.resolve::<SharedMarker>().is_ok());
758    }
759
760    struct CycleA;
761    struct CycleB;
762
763    impl ModuleDefinition for CycleA {
764        fn imports() -> Vec<ModuleRef> {
765            vec![ModuleRef::of::<CycleB>()]
766        }
767
768        fn register(_container: &Container) -> Result<()> {
769            Ok(())
770        }
771    }
772
773    impl ModuleDefinition for CycleB {
774        fn imports() -> Vec<ModuleRef> {
775            vec![ModuleRef::of::<CycleA>()]
776        }
777
778        fn register(_container: &Container) -> Result<()> {
779            Ok(())
780        }
781    }
782
783    #[test]
784    fn detects_module_import_cycles() {
785        let container = Container::new();
786        let err = initialize_module_graph::<CycleA>(&container).unwrap_err();
787
788        assert!(
789            err.to_string().contains("Detected module import cycle"),
790            "error should include cycle detection message"
791        );
792    }
793
794    struct MissingDependency;
795    struct BrokenModule;
796
797    impl ModuleDefinition for BrokenModule {
798        fn register(container: &Container) -> Result<()> {
799            let _ = container.resolve_in_module::<MissingDependency>(Self::module_name())?;
800            Ok(())
801        }
802    }
803
804    #[test]
805    fn module_registration_error_includes_module_and_type_context() {
806        let container = Container::new();
807        let err = initialize_module_graph::<BrokenModule>(&container).unwrap_err();
808        let message = err.to_string();
809
810        assert!(message.contains("Failed to register module"));
811        assert!(message.contains("BrokenModule"));
812        assert!(message.contains("MissingDependency"));
813    }
814
815    #[derive(Clone)]
816    struct HookLog(Arc<Mutex<Vec<&'static str>>>);
817
818    fn push_hook(container: &Container, label: &'static str) -> Result<()> {
819        let log = container.resolve::<HookLog>()?;
820        log.0
821            .lock()
822            .expect("hook log should be writable")
823            .push(label);
824        Ok(())
825    }
826
827    fn hook_init(container: &Container) -> Result<()> {
828        push_hook(container, "module_init")
829    }
830
831    fn hook_bootstrap(container: &Container) -> Result<()> {
832        push_hook(container, "application_bootstrap")
833    }
834
835    fn hook_destroy(container: &Container) -> Result<()> {
836        push_hook(container, "module_destroy")
837    }
838
839    fn hook_shutdown(container: &Container) -> Result<()> {
840        push_hook(container, "application_shutdown")
841    }
842
843    struct HookModule;
844
845    impl ModuleDefinition for HookModule {
846        fn register(container: &Container) -> Result<()> {
847            container.register(HookLog(Arc::new(Mutex::new(Vec::new()))))?;
848            Ok(())
849        }
850
851        fn on_module_init() -> Vec<LifecycleHook> {
852            vec![hook_init]
853        }
854
855        fn on_application_bootstrap() -> Vec<LifecycleHook> {
856            vec![hook_bootstrap]
857        }
858
859        fn on_module_destroy() -> Vec<LifecycleHook> {
860            vec![hook_destroy]
861        }
862
863        fn on_application_shutdown() -> Vec<LifecycleHook> {
864            vec![hook_shutdown]
865        }
866    }
867
868    #[test]
869    fn initialized_runtime_runs_lifecycle_hooks_in_expected_order() {
870        let container = Container::new();
871        let runtime = initialize_module_runtime::<HookModule>(&container)
872            .expect("module runtime should initialize");
873
874        runtime
875            .run_module_init(&container)
876            .expect("module init hooks should run");
877        runtime
878            .run_application_bootstrap(&container)
879            .expect("application bootstrap hooks should run");
880        runtime
881            .run_module_destroy(&container)
882            .expect("module destroy hooks should run");
883        runtime
884            .run_application_shutdown(&container)
885            .expect("application shutdown hooks should run");
886
887        let log = container
888            .resolve::<HookLog>()
889            .expect("hook log should resolve");
890        let entries = log.0.lock().expect("hook log should be readable").clone();
891
892        assert_eq!(
893            entries,
894            vec![
895                "module_init",
896                "application_bootstrap",
897                "module_destroy",
898                "application_shutdown"
899            ]
900        );
901    }
902
903    #[derive(Clone)]
904    struct DynamicConfig {
905        label: &'static str,
906    }
907
908    struct DynamicRootModule;
909
910    impl ModuleDefinition for DynamicRootModule {
911        fn imports() -> Vec<ModuleRef> {
912            let config = DynamicConfig { label: "captured" };
913            vec![ModuleRef::dynamic("DynamicConfigModule", move |container| {
914                container.register(config.clone())?;
915                Ok(())
916            })
917            .with_exports(|| vec![std::any::type_name::<DynamicConfig>()])]
918        }
919
920        fn register(_container: &Container) -> Result<()> {
921            Ok(())
922        }
923    }
924
925    #[test]
926    fn dynamic_module_ref_can_capture_runtime_configuration() {
927        let container = Container::new();
928        initialize_module_graph::<DynamicRootModule>(&container)
929            .expect("dynamic module graph should initialize");
930
931        let config = container
932            .resolve::<DynamicConfig>()
933            .expect("dynamic config should resolve");
934        assert_eq!(config.label, "captured");
935    }
936
937    struct BuilderRootModule;
938
939    impl ModuleDefinition for BuilderRootModule {
940        fn imports() -> Vec<ModuleRef> {
941            vec![ModuleRef::builder("BuilderConfigModule")
942                .provider_value(DynamicConfig { label: "builder" })
943                .export::<DynamicConfig>()
944                .build()]
945        }
946
947        fn register(_container: &Container) -> Result<()> {
948            Ok(())
949        }
950    }
951
952    #[test]
953    fn dynamic_module_builder_registers_typed_exports() {
954        let container = Container::new();
955        initialize_module_graph::<BuilderRootModule>(&container)
956            .expect("builder-based dynamic module should initialize");
957
958        let config = container
959            .resolve::<DynamicConfig>()
960            .expect("builder config should resolve");
961        assert_eq!(config.label, "builder");
962    }
963
964    #[derive(Clone)]
965    struct AsyncConfig {
966        label: &'static str,
967    }
968
969    struct AsyncRootModule;
970
971    impl ModuleDefinition for AsyncRootModule {
972        fn imports() -> Vec<ModuleRef> {
973            vec![ModuleRef::builder("AsyncConfigModule")
974                .provider_async(|_container| async { Ok(AsyncConfig { label: "async" }) })
975                .export::<AsyncConfig>()
976                .build()]
977        }
978
979        fn register(_container: &Container) -> Result<()> {
980            Ok(())
981        }
982    }
983
984    #[test]
985    fn dynamic_module_builder_supports_async_provider_registration() {
986        let container = Container::new();
987        initialize_module_graph::<AsyncRootModule>(&container)
988            .expect("async dynamic module should initialize");
989
990        let config = container
991            .resolve::<AsyncConfig>()
992            .expect("async config should resolve");
993        assert_eq!(config.label, "async");
994    }
995
996    #[test]
997    fn collects_module_graph_report_entries() {
998        let report = collect_module_graph::<RootModule>().expect("module graph should collect");
999        let root = report
1000            .module(std::any::type_name::<RootModule>())
1001            .expect("root module should exist");
1002
1003        assert_eq!(root.imports.len(), 2);
1004        assert_eq!(report.modules.len(), 4);
1005    }
1006}