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