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    pub fn load<M>(&self, module: M) -> Result<LazyLoadedModule>
103    where
104        M: Module,
105    {
106        self.load_arc(Arc::new(module))
107    }
108
109    /// Load a shared module on demand and return its provider container.
110    pub fn load_arc(&self, module: Arc<dyn Module>) -> Result<LazyLoadedModule> {
111        let mut visiting = Vec::new();
112        self.load_arc_inner(module, &mut visiting)
113    }
114
115    /// Load a module with async singleton provider factories on demand.
116    pub async fn load_async<M>(&self, module: M) -> Result<LazyLoadedModule>
117    where
118        M: Module,
119    {
120        self.load_arc_async(Arc::new(module)).await
121    }
122
123    /// Load a shared module with async singleton provider factories on demand.
124    pub async fn load_arc_async(&self, module: Arc<dyn Module>) -> Result<LazyLoadedModule> {
125        let mut visiting = Vec::new();
126        self.load_arc_async_inner(module, &mut visiting).await
127    }
128
129    fn load_arc_inner(
130        &self,
131        module: Arc<dyn Module>,
132        visiting: &mut Vec<String>,
133    ) -> Result<LazyLoadedModule> {
134        let name = validate_lazy_module_name(module.name())?;
135        if let Some(cached) = self.registry.cached(name)? {
136            return Ok(cached);
137        }
138
139        enter_lazy_module(visiting, name)?;
140        let result = self.build_lazy_module(module, name, visiting);
141        visiting.pop();
142        result
143    }
144
145    fn build_lazy_module(
146        &self,
147        module: Arc<dyn Module>,
148        name: &str,
149        visiting: &mut Vec<String>,
150    ) -> Result<LazyLoadedModule> {
151        let mut imported_modules = Vec::new();
152        for imported in module.imports() {
153            imported_modules.push(self.load_arc_inner(imported, visiting)?);
154        }
155
156        let module_ref = self.create_module_ref(&imported_modules)?;
157        for provider in module.providers()? {
158            module_ref.register(provider)?;
159        }
160        module_ref.initialize_local_singletons()?;
161
162        let exports = self.create_exports(&module_ref, module.exports()?)?;
163        if module.is_global() {
164            self.export_global(&exports)?;
165        }
166
167        let loaded = LazyLoadedModule::new(name.to_string(), module_ref, exports);
168        self.registry.cache_module(loaded)
169    }
170
171    fn load_arc_async_inner<'a>(
172        &'a self,
173        module: Arc<dyn Module>,
174        visiting: &'a mut Vec<String>,
175    ) -> BoxFuture<'a, Result<LazyLoadedModule>> {
176        Box::pin(async move {
177            let name = validate_lazy_module_name(module.name())?;
178            if let Some(cached) = self.registry.cached(name)? {
179                return Ok(cached);
180            }
181
182            enter_lazy_module(visiting, name)?;
183            let result = self.build_lazy_module_async(module, name, visiting).await;
184            visiting.pop();
185            result
186        })
187    }
188
189    fn build_lazy_module_async<'a>(
190        &'a self,
191        module: Arc<dyn Module>,
192        name: &'a str,
193        visiting: &'a mut Vec<String>,
194    ) -> BoxFuture<'a, Result<LazyLoadedModule>> {
195        Box::pin(async move {
196            let mut imported_modules = Vec::new();
197            for imported in module.imports() {
198                imported_modules.push(self.load_arc_async_inner(imported, visiting).await?);
199            }
200
201            let module_ref = self.create_module_ref(&imported_modules)?;
202            for provider in module.providers()? {
203                module_ref.register_async(provider).await?;
204            }
205            module_ref.initialize_local_singletons_async().await?;
206
207            let exports = self.create_exports(&module_ref, module.exports()?)?;
208            if module.is_global() {
209                self.export_global(&exports)?;
210            }
211
212            let loaded = LazyLoadedModule::new(name.to_string(), module_ref, exports);
213            self.registry.cache_module(loaded)
214        })
215    }
216
217    fn create_module_ref(&self, imported_modules: &[LazyLoadedModule]) -> Result<ModuleRef> {
218        let module_ref = ModuleRef::new();
219        module_ref.add_visible_scope(self.registry.global_ref.clone())?;
220        for imported in imported_modules {
221            module_ref.add_visible_scope(imported.exports.clone())?;
222        }
223        Ok(module_ref)
224    }
225
226    fn create_exports(
227        &self,
228        module_ref: &ModuleRef,
229        tokens: Vec<ProviderToken>,
230    ) -> Result<ModuleRef> {
231        let exports = ModuleRef::new();
232        for token in tokens {
233            exports.export_from(module_ref, &token)?;
234        }
235        Ok(exports)
236    }
237
238    fn export_global(&self, exports: &ModuleRef) -> Result<()> {
239        for token in exports.local_tokens()? {
240            self.registry.global_ref.export_from(exports, &token)?;
241        }
242        Ok(())
243    }
244}
245
246impl fmt::Debug for LazyModuleLoader {
247    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248        f.debug_struct("LazyModuleLoader").finish_non_exhaustive()
249    }
250}
251
252struct LazyModuleRegistry {
253    global_ref: ModuleRef,
254    modules: RwLock<BTreeMap<String, LazyLoadedModule>>,
255}
256
257impl LazyModuleRegistry {
258    fn new(global_ref: ModuleRef) -> Self {
259        Self {
260            global_ref,
261            modules: RwLock::new(BTreeMap::new()),
262        }
263    }
264
265    fn cached(&self, name: &str) -> Result<Option<LazyLoadedModule>> {
266        Ok(self.read_modules()?.get(name).cloned())
267    }
268
269    fn seed_module(&self, module: LazyLoadedModule) -> Result<()> {
270        self.write_modules()?.insert(module.name.clone(), module);
271        Ok(())
272    }
273
274    fn cache_module(&self, module: LazyLoadedModule) -> Result<LazyLoadedModule> {
275        let mut modules = self.write_modules()?;
276        if let Some(cached) = modules.get(module.name()).cloned() {
277            return Ok(cached);
278        }
279        modules.insert(module.name.clone(), module.clone());
280        Ok(module)
281    }
282
283    fn read_modules(
284        &self,
285    ) -> Result<std::sync::RwLockReadGuard<'_, BTreeMap<String, LazyLoadedModule>>> {
286        self.modules
287            .read()
288            .map_err(|_| BootError::Internal("lazy module registry lock is poisoned".to_string()))
289    }
290
291    fn write_modules(
292        &self,
293    ) -> Result<std::sync::RwLockWriteGuard<'_, BTreeMap<String, LazyLoadedModule>>> {
294        self.modules
295            .write()
296            .map_err(|_| BootError::Internal("lazy module registry lock is poisoned".to_string()))
297    }
298}
299
300fn validate_lazy_module_name(name: &'static str) -> Result<&'static str> {
301    if name.trim().is_empty() {
302        return Err(BootError::EmptyModuleName);
303    }
304    Ok(name)
305}
306
307fn enter_lazy_module(visiting: &mut Vec<String>, name: &str) -> Result<()> {
308    if let Some(index) = visiting.iter().position(|active| active == name) {
309        let mut chain = visiting[index..].to_vec();
310        chain.push(name.to_string());
311        return Err(BootError::Internal(format!(
312            "cyclic lazy module import detected: {}",
313            chain.join(" -> ")
314        )));
315    }
316
317    visiting.push(name.to_string());
318    Ok(())
319}