1use crate::{BootError, BoxFuture, Module, ModuleRef, ProviderToken, Result};
2use std::collections::BTreeMap;
3use std::fmt;
4use std::sync::{Arc, RwLock};
5
6#[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 pub fn name(&self) -> &str {
25 &self.name
26 }
27
28 pub fn module_ref(&self) -> &ModuleRef {
30 &self.module_ref
31 }
32
33 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 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 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 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#[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 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 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 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 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}