Skip to main content

a3s_boot/app/
lazy.rs

1use crate::{BootError, BoxFuture, Module, ModuleRef, ProviderToken, Result};
2use std::collections::BTreeMap;
3use std::fmt;
4use std::sync::{Arc, RwLock};
5
6/// Provider-only reference returned by [`LazyModuleLoader`].
7#[derive(Clone)]
8pub struct LazyLoadedModule {
9    name: String,
10    module_ref: ModuleRef,
11    exports: ModuleRef,
12}
13
14impl LazyLoadedModule {
15    fn new(name: String, module_ref: ModuleRef, exports: ModuleRef) -> Self {
16        Self {
17            name,
18            module_ref,
19            exports,
20        }
21    }
22
23    /// Module name reported by the loaded module.
24    pub fn name(&self) -> &str {
25        &self.name
26    }
27
28    /// Provider container for the loaded module.
29    pub fn module_ref(&self) -> &ModuleRef {
30        &self.module_ref
31    }
32
33    /// Resolve a typed provider from the loaded module.
34    pub fn get<T>(&self) -> Result<Arc<T>>
35    where
36        T: Send + Sync + 'static,
37    {
38        self.module_ref.get::<T>()
39    }
40
41    /// Resolve a named provider from the loaded module.
42    pub fn get_named<T>(&self, token: &str) -> Result<Arc<T>>
43    where
44        T: Send + Sync + 'static,
45    {
46        self.module_ref.get_named::<T>(token)
47    }
48
49    /// Resolve a typed provider when it is present in the loaded module graph.
50    pub fn get_optional<T>(&self) -> Result<Option<Arc<T>>>
51    where
52        T: Send + Sync + 'static,
53    {
54        self.module_ref.get_optional::<T>()
55    }
56
57    /// Resolve a named provider when it is present in the loaded module graph.
58    pub fn get_optional_named<T>(&self, token: &str) -> Result<Option<Arc<T>>>
59    where
60        T: Send + Sync + 'static,
61    {
62        self.module_ref.get_optional_named::<T>(token)
63    }
64}
65
66impl fmt::Debug for LazyLoadedModule {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        f.debug_struct("LazyLoadedModule")
69            .field("name", &self.name)
70            .field("module_ref", &self.module_ref)
71            .finish()
72    }
73}
74
75/// Nest-style lazy module loader for provider-only module graphs.
76#[derive(Clone)]
77pub struct LazyModuleLoader {
78    registry: Arc<LazyModuleRegistry>,
79}
80
81impl LazyModuleLoader {
82    pub(crate) fn new(global_ref: ModuleRef) -> Self {
83        Self {
84            registry: Arc::new(LazyModuleRegistry::new(global_ref)),
85        }
86    }
87
88    pub(crate) fn seed_module(
89        &self,
90        name: String,
91        module_ref: ModuleRef,
92        exports: ModuleRef,
93    ) -> Result<()> {
94        self.registry
95            .seed_module(LazyLoadedModule::new(name, module_ref, exports))
96    }
97
98    /// Load a module on demand and return its provider container.
99    ///
100    /// Lazy-loaded modules are provider-only: controllers, routes, gateways,
101    /// middleware, message patterns, and lifecycle hooks are not registered.
102    /// Newly loaded global modules are rejected because changing global
103    /// visibility after singleton initialization would make the existing
104    /// provider graph inconsistent.
105    pub fn load<M>(&self, module: M) -> Result<LazyLoadedModule>
106    where
107        M: Module,
108    {
109        self.load_arc(Arc::new(module))
110    }
111
112    /// Load a shared module on demand and return its provider container.
113    pub fn load_arc(&self, module: Arc<dyn Module>) -> Result<LazyLoadedModule> {
114        let name = validate_lazy_module_name(module.name())?;
115        if let Some(cached) = self.registry.cached(name)? {
116            return Ok(cached);
117        }
118
119        let mut graph = LazyModuleGraph::default();
120        let loaded = self.register_arc_inner(module, &mut graph, false)?;
121        graph.validate()?;
122        graph.initialize()?;
123        self.registry.cache_modules(&graph.pending)?;
124        self.registry
125            .cached(loaded.name())?
126            .ok_or_else(|| missing_cached_lazy_module(loaded.name()))
127    }
128
129    /// Load a module with async singleton provider factories on demand.
130    pub async fn load_async<M>(&self, module: M) -> Result<LazyLoadedModule>
131    where
132        M: Module,
133    {
134        self.load_arc_async(Arc::new(module)).await
135    }
136
137    /// Load a shared module with async singleton provider factories on demand.
138    pub async fn load_arc_async(&self, module: Arc<dyn Module>) -> Result<LazyLoadedModule> {
139        let name = validate_lazy_module_name(module.name())?;
140        if let Some(cached) = self.registry.cached(name)? {
141            return Ok(cached);
142        }
143
144        let mut graph = LazyModuleGraph::default();
145        let loaded = self
146            .register_arc_async_inner(module, &mut graph, false)
147            .await?;
148        graph.validate()?;
149        graph.initialize_async().await?;
150        self.registry.cache_modules(&graph.pending)?;
151        self.registry
152            .cached(loaded.name())?
153            .ok_or_else(|| missing_cached_lazy_module(loaded.name()))
154    }
155
156    fn register_arc_inner(
157        &self,
158        module: Arc<dyn Module>,
159        graph: &mut LazyModuleGraph,
160        allow_active: bool,
161    ) -> Result<LazyLoadedModule> {
162        let name = validate_lazy_module_name(module.name())?;
163        if let Some(cached) = self.registry.cached(name)? {
164            return Ok(cached);
165        }
166        if let Some(registered) = graph.registered.get(name) {
167            return Ok(registered.clone());
168        }
169        if let Some(active) = graph.active.get(name) {
170            if allow_active {
171                return Ok(active.clone());
172            }
173            return Err(cyclic_lazy_module_error(&graph.visiting, name));
174        }
175        reject_lazy_global(module.as_ref(), name)?;
176
177        enter_lazy_module(&mut graph.visiting, name)?;
178        let loaded = LazyLoadedModule::new(name.to_string(), ModuleRef::new(), ModuleRef::new());
179        graph.active.insert(name.to_string(), loaded.clone());
180        let result = self.register_lazy_module(module, &loaded, graph);
181        graph.active.remove(name);
182        graph.visiting.pop();
183        result?;
184
185        graph.registered.insert(name.to_string(), loaded.clone());
186        graph.pending.push(loaded.clone());
187        Ok(loaded)
188    }
189
190    fn register_lazy_module(
191        &self,
192        module: Arc<dyn Module>,
193        loaded: &LazyLoadedModule,
194        graph: &mut LazyModuleGraph,
195    ) -> Result<()> {
196        let mut imported_modules = Vec::new();
197        for imported in module.imports() {
198            imported_modules.push(self.register_arc_inner(imported, graph, false)?);
199        }
200        for imported in module.forward_imports() {
201            imported_modules.push(self.register_arc_inner(imported, graph, true)?);
202        }
203
204        self.prepare_module_ref(&loaded.module_ref, &imported_modules)?;
205        for provider in module.providers()? {
206            reject_lazy_provider_enhancers(&provider, loaded.name())?;
207            loaded.module_ref.register(provider)?;
208        }
209        self.populate_exports(&loaded.exports, &loaded.module_ref, module.exports()?)
210    }
211
212    fn register_arc_async_inner<'a>(
213        &'a self,
214        module: Arc<dyn Module>,
215        graph: &'a mut LazyModuleGraph,
216        allow_active: bool,
217    ) -> BoxFuture<'a, Result<LazyLoadedModule>> {
218        Box::pin(async move {
219            let name = validate_lazy_module_name(module.name())?;
220            if let Some(cached) = self.registry.cached(name)? {
221                return Ok(cached);
222            }
223            if let Some(registered) = graph.registered.get(name) {
224                return Ok(registered.clone());
225            }
226            if let Some(active) = graph.active.get(name) {
227                if allow_active {
228                    return Ok(active.clone());
229                }
230                return Err(cyclic_lazy_module_error(&graph.visiting, name));
231            }
232            reject_lazy_global(module.as_ref(), name)?;
233
234            enter_lazy_module(&mut graph.visiting, name)?;
235            let loaded =
236                LazyLoadedModule::new(name.to_string(), ModuleRef::new(), ModuleRef::new());
237            graph.active.insert(name.to_string(), loaded.clone());
238            let result = self
239                .register_lazy_module_async(module, &loaded, graph)
240                .await;
241            graph.active.remove(name);
242            graph.visiting.pop();
243            result?;
244
245            graph.registered.insert(name.to_string(), loaded.clone());
246            graph.pending.push(loaded.clone());
247            Ok(loaded)
248        })
249    }
250
251    fn register_lazy_module_async<'a>(
252        &'a self,
253        module: Arc<dyn Module>,
254        loaded: &'a LazyLoadedModule,
255        graph: &'a mut LazyModuleGraph,
256    ) -> BoxFuture<'a, Result<()>> {
257        Box::pin(async move {
258            let mut imported_modules = Vec::new();
259            for imported in module.imports() {
260                imported_modules.push(
261                    self.register_arc_async_inner(imported, graph, false)
262                        .await?,
263                );
264            }
265            for imported in module.forward_imports() {
266                imported_modules.push(self.register_arc_async_inner(imported, graph, true).await?);
267            }
268
269            self.prepare_module_ref(&loaded.module_ref, &imported_modules)?;
270            for provider in module.providers()? {
271                reject_lazy_provider_enhancers(&provider, loaded.name())?;
272                loaded.module_ref.register_async(provider).await?;
273            }
274            self.populate_exports(&loaded.exports, &loaded.module_ref, module.exports()?)
275        })
276    }
277
278    fn prepare_module_ref(
279        &self,
280        module_ref: &ModuleRef,
281        imported_modules: &[LazyLoadedModule],
282    ) -> Result<()> {
283        module_ref.add_visible_scope(self.registry.global_ref.clone())?;
284        for imported in imported_modules {
285            module_ref.add_visible_scope(imported.exports.clone())?;
286        }
287        Ok(())
288    }
289
290    fn populate_exports(
291        &self,
292        exports: &ModuleRef,
293        module_ref: &ModuleRef,
294        tokens: Vec<ProviderToken>,
295    ) -> Result<()> {
296        for token in tokens {
297            exports.export_from(module_ref, &token)?;
298        }
299        Ok(())
300    }
301}
302
303impl fmt::Debug for LazyModuleLoader {
304    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305        f.debug_struct("LazyModuleLoader").finish_non_exhaustive()
306    }
307}
308
309struct LazyModuleRegistry {
310    global_ref: ModuleRef,
311    modules: RwLock<BTreeMap<String, LazyLoadedModule>>,
312}
313
314impl LazyModuleRegistry {
315    fn new(global_ref: ModuleRef) -> Self {
316        Self {
317            global_ref,
318            modules: RwLock::new(BTreeMap::new()),
319        }
320    }
321
322    fn cached(&self, name: &str) -> Result<Option<LazyLoadedModule>> {
323        Ok(self.read_modules()?.get(name).cloned())
324    }
325
326    fn seed_module(&self, module: LazyLoadedModule) -> Result<()> {
327        self.write_modules()?.insert(module.name.clone(), module);
328        Ok(())
329    }
330
331    fn cache_modules(&self, pending: &[LazyLoadedModule]) -> Result<()> {
332        let mut modules = self.write_modules()?;
333        for module in pending {
334            modules
335                .entry(module.name.clone())
336                .or_insert_with(|| module.clone());
337        }
338        Ok(())
339    }
340
341    fn read_modules(
342        &self,
343    ) -> Result<std::sync::RwLockReadGuard<'_, BTreeMap<String, LazyLoadedModule>>> {
344        self.modules
345            .read()
346            .map_err(|_| BootError::Internal("lazy module registry lock is poisoned".to_string()))
347    }
348
349    fn write_modules(
350        &self,
351    ) -> Result<std::sync::RwLockWriteGuard<'_, BTreeMap<String, LazyLoadedModule>>> {
352        self.modules
353            .write()
354            .map_err(|_| BootError::Internal("lazy module registry lock is poisoned".to_string()))
355    }
356}
357
358#[derive(Default)]
359struct LazyModuleGraph {
360    registered: BTreeMap<String, LazyLoadedModule>,
361    active: BTreeMap<String, LazyLoadedModule>,
362    pending: Vec<LazyLoadedModule>,
363    visiting: Vec<String>,
364}
365
366impl LazyModuleGraph {
367    fn validate(&self) -> Result<()> {
368        for module in &self.pending {
369            module.module_ref.validate_local_resolution_plans()?;
370        }
371        Ok(())
372    }
373
374    fn initialize(&self) -> Result<()> {
375        for module in &self.pending {
376            module.module_ref.initialize_local_singletons()?;
377        }
378        Ok(())
379    }
380
381    async fn initialize_async(&self) -> Result<()> {
382        for module in &self.pending {
383            module.module_ref.seed_local_async_singletons().await?;
384        }
385        for module in &self.pending {
386            module.module_ref.initialize_local_singletons()?;
387        }
388        Ok(())
389    }
390}
391
392fn validate_lazy_module_name(name: &'static str) -> Result<&'static str> {
393    if name.trim().is_empty() {
394        return Err(BootError::EmptyModuleName);
395    }
396    Ok(name)
397}
398
399fn enter_lazy_module(visiting: &mut Vec<String>, name: &str) -> Result<()> {
400    if let Some(index) = visiting.iter().position(|active| active == name) {
401        return Err(cyclic_lazy_module_error(&visiting[index..], name));
402    }
403
404    visiting.push(name.to_string());
405    Ok(())
406}
407
408fn cyclic_lazy_module_error(visiting: &[String], name: &str) -> BootError {
409    let index = visiting
410        .iter()
411        .position(|active| active == name)
412        .unwrap_or(0);
413    let mut chain = visiting[index..].to_vec();
414    chain.push(name.to_string());
415    BootError::Internal(format!(
416        "cyclic lazy module import detected: {}",
417        chain.join(" -> ")
418    ))
419}
420
421fn reject_lazy_global(module: &dyn Module, name: &str) -> Result<()> {
422    if module.is_global() {
423        return Err(BootError::Internal(format!(
424            "lazy-loaded global module `{name}` would change the finalized application provider graph; register global modules eagerly"
425        )));
426    }
427    Ok(())
428}
429
430fn reject_lazy_provider_enhancers(
431    provider: &crate::ProviderDefinition,
432    module_name: &str,
433) -> Result<()> {
434    if !provider.enhancer_markers().is_empty() {
435        return Err(BootError::Internal(format!(
436            "lazy-loaded module `{module_name}` declares an application-wide provider enhancer; register modules with APP_* providers eagerly"
437        )));
438    }
439    Ok(())
440}
441
442fn missing_cached_lazy_module(name: &str) -> BootError {
443    BootError::Internal(format!(
444        "lazy module `{name}` was initialized but not cached"
445    ))
446}