1use std::any::{Any, TypeId};
2use std::collections::{HashMap, HashSet};
3use std::future::Future;
4use std::pin::Pin;
5use std::sync::Arc;
6use std::sync::OnceLock;
7use std::sync::RwLock;
8
9use async_trait::async_trait;
10use axum::Router;
11
12mod admin_snapshot;
13pub mod als;
14pub mod client_ip;
15mod database;
16mod discovery;
17mod execution_context;
18mod guard;
19mod metadata;
20mod module_ref;
21mod pipe;
22mod platform;
23mod route_registry;
24mod strategy;
25mod trace;
26
27#[cfg(feature = "sse")]
28pub mod sse;
29
30pub use admin_snapshot::AdminSnapshot;
31pub use als::{AlsContext, AlsError};
32pub use client_ip::{
33 best_effort_client_ip, rate_limit_key_ip_or_unknown, trusted_hops_from_parts, RateLimitKey,
34 TrustedProxyHops, X_FORWARDED_FOR, X_REAL_IP,
35};
36pub use database::DatabasePing;
37pub use discovery::DiscoveryService;
38pub use execution_context::{
39 ExecutionContext, HostType, HttpExecutionArguments, RpcExecutionArguments, WsExecutionArguments,
40};
41pub use guard::{CanActivate, GuardError};
42pub use metadata::MetadataRegistry;
43pub use module_ref::ModuleRef;
44pub use pipe::{HttpPipeTransform, PipeTransform};
45pub use platform::{AxumHttpEngine, HttpServerEngine};
46pub use route_registry::{
47 OpenApiResponseDesc, OpenApiRouteSpec, RouteConflict, RouteConflictKind, RouteConflictReport,
48 RouteInfo, RouteRegistry,
49};
50pub use strategy::{AuthError, AuthStrategy};
51pub use trace::{current_trace_context, parse_traceparent, with_trace_context, TraceContext};
52
53#[cfg(feature = "sse")]
54pub use sse::{serialize_to_event, IntoSseEvent, SseEvent, SseKeepAlive, SseResponse};
55
56type CustomFactoryFn =
57 std::sync::Arc<dyn Fn(&ProviderRegistry) -> Arc<dyn Any + Send + Sync> + Send + Sync>;
58
59#[derive(Clone, Copy, Debug, PartialEq, Eq)]
67pub enum ProviderScope {
68 Singleton,
70 Transient,
72 Request,
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
79pub struct ProviderSummary {
80 pub type_name: &'static str,
81 pub scope: ProviderScope,
82}
83
84#[derive(Clone)]
85enum ProviderFactory {
86 InjectableFn(fn(&ProviderRegistry) -> Arc<dyn Any + Send + Sync>),
87 Custom(CustomFactoryFn),
88}
89
90#[derive(Clone)]
91struct ProviderEntry {
92 type_name: &'static str,
93 scope: ProviderScope,
94 factory: ProviderFactory,
95 instance: Arc<OnceLock<Arc<dyn Any + Send + Sync>>>,
96 on_module_init: HookFn,
97 on_module_destroy: HookFn,
98 on_application_bootstrap: HookFn,
99 on_before_application_shutdown: HookFn,
102 on_application_shutdown: HookFn,
103}
104
105fn noop_hook<'a>(_registry: &'a ProviderRegistry) -> HookFuture<'a> {
106 Box::pin(async {})
107}
108
109fn create_entry_for_injectable<T: Injectable + Send + Sync + 'static>() -> ProviderEntry {
110 fn factory<T: Injectable + Send + Sync + 'static>(
111 registry: &ProviderRegistry,
112 ) -> Arc<dyn Any + Send + Sync> {
113 T::construct(registry)
114 }
115
116 ProviderEntry {
117 type_name: std::any::type_name::<T>(),
118 scope: T::scope(),
119 factory: ProviderFactory::InjectableFn(factory::<T>),
120 instance: Arc::new(OnceLock::new()),
121 on_module_init: hook_on_module_init::<T>,
122 on_module_destroy: hook_on_module_destroy::<T>,
123 on_application_bootstrap: hook_on_application_bootstrap::<T>,
124 on_before_application_shutdown: hook_on_before_application_shutdown::<T>,
125 on_application_shutdown: hook_on_application_shutdown::<T>,
126 }
127}
128
129pub struct ProviderRegistry {
130 entries: HashMap<TypeId, ProviderEntry>,
131 order: Vec<TypeId>,
134}
135
136#[derive(Clone, Copy, Debug)]
138pub struct HandlerKey(pub &'static str);
139
140impl ProviderRegistry {
141 pub fn new() -> Self {
142 Self {
143 entries: HashMap::new(),
144 order: Vec::new(),
145 }
146 }
147
148 fn insert_entry(&mut self, type_id: TypeId, entry: ProviderEntry) {
149 if !self.entries.contains_key(&type_id) {
150 self.order.push(type_id);
151 }
152 self.entries.insert(type_id, entry);
153 }
154
155 pub fn register<T>(&mut self)
156 where
157 T: Injectable + Send + Sync + 'static,
158 {
159 self.insert_entry(TypeId::of::<T>(), create_entry_for_injectable::<T>());
160 }
161
162 pub fn register_use_value<T: Send + Sync + 'static>(&mut self, value: Arc<T>) {
168 let preset: Arc<dyn Any + Send + Sync> = value;
169 let cell = Arc::new(OnceLock::new());
170 let _ = cell.set(preset.clone());
171 self.insert_entry(
172 TypeId::of::<T>(),
173 ProviderEntry {
174 type_name: std::any::type_name::<T>(),
175 scope: ProviderScope::Singleton,
176 factory: ProviderFactory::Custom(Arc::new(move |_| preset.clone())),
177 instance: cell,
178 on_module_init: noop_hook,
179 on_module_destroy: noop_hook,
180 on_application_bootstrap: noop_hook,
181 on_before_application_shutdown: noop_hook,
182 on_application_shutdown: noop_hook,
183 },
184 );
185 }
186
187 pub fn register_use_factory<T, F>(&mut self, scope: ProviderScope, factory: F)
200 where
201 T: Send + Sync + 'static,
202 F: Fn(&ProviderRegistry) -> Arc<T> + Send + Sync + 'static,
203 {
204 let factory: std::sync::Arc<F> = std::sync::Arc::new(factory);
205 let factory = factory.clone();
206 self.insert_entry(
207 TypeId::of::<T>(),
208 ProviderEntry {
209 type_name: std::any::type_name::<T>(),
210 scope,
211 factory: ProviderFactory::Custom(Arc::new(move |r| {
212 let v = factory(r);
213 v as Arc<dyn Any + Send + Sync>
214 })),
215 instance: Arc::new(OnceLock::new()),
216 on_module_init: noop_hook,
217 on_module_destroy: noop_hook,
218 on_application_bootstrap: noop_hook,
219 on_before_application_shutdown: noop_hook,
220 on_application_shutdown: noop_hook,
221 },
222 );
223 }
224
225 pub fn register_use_value_with_lifecycle<T>(&mut self, value: Arc<T>)
229 where
230 T: ProviderLifecycle + Send + Sync + 'static,
231 {
232 let preset: Arc<dyn Any + Send + Sync> = value;
233 let cell = Arc::new(OnceLock::new());
234 let _ = cell.set(preset.clone());
235 self.insert_entry(
236 TypeId::of::<T>(),
237 ProviderEntry {
238 type_name: std::any::type_name::<T>(),
239 scope: ProviderScope::Singleton,
240 factory: ProviderFactory::Custom(Arc::new(move |_| preset.clone())),
241 instance: cell,
242 on_module_init: lifecycle_on_module_init::<T>,
243 on_module_destroy: lifecycle_on_module_destroy::<T>,
244 on_application_bootstrap: lifecycle_on_application_bootstrap::<T>,
245 on_before_application_shutdown: lifecycle_on_before_application_shutdown::<T>,
246 on_application_shutdown: lifecycle_on_application_shutdown::<T>,
247 },
248 );
249 }
250
251 pub fn register_use_factory_with_lifecycle<T, F>(&mut self, scope: ProviderScope, factory: F)
256 where
257 T: ProviderLifecycle + Send + Sync + 'static,
258 F: Fn(&ProviderRegistry) -> Arc<T> + Send + Sync + 'static,
259 {
260 let factory: std::sync::Arc<F> = std::sync::Arc::new(factory);
261 let factory = factory.clone();
262 self.insert_entry(
263 TypeId::of::<T>(),
264 ProviderEntry {
265 type_name: std::any::type_name::<T>(),
266 scope,
267 factory: ProviderFactory::Custom(Arc::new(move |r| {
268 let v = factory(r);
269 v as Arc<dyn Any + Send + Sync>
270 })),
271 instance: Arc::new(OnceLock::new()),
272 on_module_init: lifecycle_on_module_init::<T>,
273 on_module_destroy: lifecycle_on_module_destroy::<T>,
274 on_application_bootstrap: lifecycle_on_application_bootstrap::<T>,
275 on_before_application_shutdown: lifecycle_on_before_application_shutdown::<T>,
276 on_application_shutdown: lifecycle_on_application_shutdown::<T>,
277 },
278 );
279 }
280
281 #[inline]
283 pub fn register_use_class<T>(&mut self)
284 where
285 T: Injectable + Send + Sync + 'static,
286 {
287 self.register::<T>();
288 }
289
290 pub fn override_provider<T>(&mut self, instance: Arc<T>)
300 where
301 T: Injectable + Send + Sync + 'static,
302 {
303 let preset: Arc<dyn Any + Send + Sync> = instance;
304 let instance_cell = Arc::new(OnceLock::new());
305 let _ = instance_cell.set(preset.clone());
306 let entry = ProviderEntry {
307 type_name: std::any::type_name::<T>(),
308 scope: T::scope(),
309 factory: ProviderFactory::Custom(Arc::new(move |_| preset.clone())),
313 instance: instance_cell,
314 on_module_init: hook_on_module_init::<T>,
315 on_module_destroy: hook_on_module_destroy::<T>,
316 on_application_bootstrap: hook_on_application_bootstrap::<T>,
317 on_before_application_shutdown: hook_on_before_application_shutdown::<T>,
318 on_application_shutdown: hook_on_application_shutdown::<T>,
319 };
320
321 self.insert_entry(TypeId::of::<T>(), entry);
322 }
323
324 fn produce_any(
330 &self,
331 type_id: TypeId,
332 entry: &ProviderEntry,
333 ) -> Option<Arc<dyn Any + Send + Sync>> {
334 match entry.scope {
335 ProviderScope::Singleton => {
336 let _guard = ConstructionGuard::push(type_id, entry.type_name);
337 Some(
338 entry
339 .instance
340 .get_or_init(|| match &entry.factory {
341 ProviderFactory::InjectableFn(f) => f(self),
342 ProviderFactory::Custom(f) => f(self),
343 })
344 .clone(),
345 )
346 }
347 ProviderScope::Transient => {
348 let _guard = ConstructionGuard::push(type_id, entry.type_name);
349 Some(match &entry.factory {
350 ProviderFactory::InjectableFn(f) => f(self),
351 ProviderFactory::Custom(f) => f(self),
352 })
353 }
354 ProviderScope::Request => {
355 let _guard = ConstructionGuard::push(type_id, entry.type_name);
356 REQUEST_SCOPE_CACHE
361 .try_with(|cell| {
362 if let Some(existing) = cell.borrow().get(&type_id).cloned() {
363 return existing;
364 }
365 let value = match &entry.factory {
366 ProviderFactory::InjectableFn(f) => f(self),
367 ProviderFactory::Custom(f) => f(self),
368 };
369 cell.borrow_mut().insert(type_id, value.clone());
370 value
371 })
372 .ok()
373 }
374 }
375 }
376
377 pub fn get<T>(&self) -> Arc<T>
389 where
390 T: Send + Sync + 'static,
391 {
392 self.try_get::<T>().unwrap_or_else(|| {
393 if let Some(entry) = self.entries.get(&TypeId::of::<T>()) {
395 if matches!(entry.scope, ProviderScope::Request) {
396 panic!(
397 "Request-scoped provider `{}` requested outside a request scope; \
398 enable request scope middleware (`use_request_scope`), spawn \
399 background work with `spawn_with_request_scope`, or use \
400 `try_get` to handle absence gracefully",
401 entry.type_name
402 );
403 }
404 }
405 panic!("Provider `{}` not registered", std::any::type_name::<T>())
406 })
407 }
408
409 pub fn try_get<T>(&self) -> Option<Arc<T>>
415 where
416 T: Send + Sync + 'static,
417 {
418 let type_id = TypeId::of::<T>();
419 let entry = self.entries.get(&type_id)?;
420
421 if let Some(parent) =
422 CONSTRUCTION_STACK.with(|stack| stack.borrow().last().map(|(_, id)| *id))
423 {
424 record_provider_dependency(parent, type_id);
425 }
426
427 let any = self.produce_any(type_id, entry)?;
428
429 any.downcast::<T>().ok()
430 }
431
432 pub fn registered_type_ids(&self) -> Vec<TypeId> {
434 self.order.clone()
435 }
436
437 pub fn registered_type_names(&self) -> Vec<&'static str> {
439 self.order
440 .iter()
441 .filter_map(|id| self.entries.get(id).map(|e| e.type_name))
442 .collect()
443 }
444
445 pub fn provider_summaries(&self) -> Vec<ProviderSummary> {
450 self.order
451 .iter()
452 .filter_map(|id| {
453 self.entries.get(id).map(|e| ProviderSummary {
454 type_name: e.type_name,
455 scope: e.scope,
456 })
457 })
458 .collect()
459 }
460
461 pub fn absorb(&mut self, other: ProviderRegistry) {
462 let ProviderRegistry { entries, order } = other;
463 let mut leftover = entries;
464 for type_id in order {
465 if let Some(entry) = leftover.remove(&type_id) {
466 self.insert_entry(type_id, entry);
467 }
468 }
469 for (type_id, entry) in leftover {
470 self.insert_entry(type_id, entry);
471 }
472 }
473
474 pub fn absorb_exported(&mut self, mut other: ProviderRegistry, exported: &[TypeId]) {
475 if exported.is_empty() {
476 return;
477 }
478 let allow = exported.iter().copied().collect::<HashSet<_>>();
479 for type_id in std::mem::take(&mut other.order) {
481 if allow.contains(&type_id) {
482 if let Some(entry) = other.entries.remove(&type_id) {
483 self.insert_entry(type_id, entry);
484 }
485 }
486 }
487 }
488
489 pub fn absorb_exported_from(&mut self, other: &ProviderRegistry, exported: &[TypeId]) {
492 if exported.is_empty() {
493 return;
494 }
495 let allow = exported.iter().copied().collect::<HashSet<_>>();
496 for type_id in &other.order {
497 if allow.contains(type_id) {
498 if let Some(entry) = other.entries.get(type_id) {
499 self.insert_entry(*type_id, entry.clone());
500 }
501 }
502 }
503 }
504
505 pub fn eager_init_singletons(&self) {
508 for type_id in &self.order {
509 let Some(entry) = self.entries.get(type_id) else {
510 continue;
511 };
512 if entry.scope == ProviderScope::Singleton {
513 let _guard = ConstructionGuard::push(*type_id, entry.type_name);
514 let _ = entry.instance.get_or_init(|| match &entry.factory {
515 ProviderFactory::InjectableFn(f) => f(self),
516 ProviderFactory::Custom(f) => f(self),
517 });
518 }
519 }
520 }
521
522 fn ordered_singletons(&self) -> Vec<TypeId> {
528 let singletons: HashSet<TypeId> = self
529 .order
530 .iter()
531 .filter(|id| {
532 self.entries
533 .get(id)
534 .is_some_and(|e| e.scope == ProviderScope::Singleton)
535 })
536 .copied()
537 .collect();
538
539 let deps = provider_dep_graph().read().expect("provider dep graph");
544 let mut incoming: HashMap<TypeId, usize> =
545 singletons.iter().map(|id| (*id, 0usize)).collect();
546 let mut adjacency: HashMap<TypeId, Vec<TypeId>> = HashMap::new();
547 for (from, targets) in deps.iter() {
548 if !singletons.contains(from) {
549 continue;
550 }
551 for to in targets {
552 if singletons.contains(to) {
553 adjacency.entry(*to).or_default().push(*from);
554 *incoming.entry(*from).or_insert(0) += 1;
555 }
556 }
557 }
558 drop(deps);
559
560 use std::cmp::Reverse;
562 let position: HashMap<&TypeId, usize> = self
563 .order
564 .iter()
565 .enumerate()
566 .map(|(i, id)| (id, i))
567 .collect();
568 let mut ready: std::collections::BinaryHeap<Reverse<usize>> = singletons
569 .iter()
570 .filter(|id| incoming[id] == 0)
571 .map(|id| Reverse(position[id]))
572 .collect();
573
574 let mut sorted = Vec::with_capacity(singletons.len());
575 let mut visited = HashSet::new();
576 while let Some(Reverse(pos)) = ready.pop() {
577 let id = self.order[pos];
578 visited.insert(id);
579 sorted.push(id);
580 if let Some(dependents) = adjacency.get(&id) {
581 for to in dependents {
582 let e = incoming.get_mut(to).expect("edge target tracked");
583 *e -= 1;
584 if *e == 0 && !visited.contains(to) {
585 ready.push(Reverse(position[to]));
586 }
587 }
588 }
589 }
590
591 for id in &self.order {
593 if singletons.contains(id) && !visited.contains(id) {
594 sorted.push(*id);
595 }
596 }
597 sorted
598 }
599
600 pub async fn run_on_module_init(&self) {
601 for type_id in self.ordered_singletons() {
602 if let Some(entry) = self.entries.get(&type_id) {
603 (entry.on_module_init)(self).await;
604 }
605 }
606 }
607
608 pub async fn run_on_module_destroy(&self) {
610 for type_id in self.ordered_singletons().into_iter().rev() {
611 if let Some(entry) = self.entries.get(&type_id) {
612 (entry.on_module_destroy)(self).await;
613 }
614 }
615 }
616
617 pub async fn run_on_application_bootstrap(&self) {
618 for type_id in self.ordered_singletons() {
619 if let Some(entry) = self.entries.get(&type_id) {
620 (entry.on_application_bootstrap)(self).await;
621 }
622 }
623 }
624
625 pub async fn run_on_before_application_shutdown(&self) {
629 for type_id in self.ordered_singletons().into_iter().rev() {
630 if let Some(entry) = self.entries.get(&type_id) {
631 (entry.on_before_application_shutdown)(self).await;
632 }
633 }
634 }
635
636 pub async fn run_on_application_shutdown(&self) {
638 for type_id in self.ordered_singletons().into_iter().rev() {
639 if let Some(entry) = self.entries.get(&type_id) {
640 (entry.on_application_shutdown)(self).await;
641 }
642 }
643 }
644}
645
646impl Clone for ProviderRegistry {
647 fn clone(&self) -> Self {
648 Self {
649 entries: self.entries.clone(),
650 order: self.order.clone(),
651 }
652 }
653}
654
655impl Default for ProviderRegistry {
656 fn default() -> Self {
657 Self::new()
658 }
659}
660
661type HookFuture<'a> = Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
662type HookFn = for<'a> fn(&'a ProviderRegistry) -> HookFuture<'a>;
663
664fn hook_on_module_init<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
665where
666 T: Injectable + Send + Sync + 'static,
667{
668 Box::pin(async move {
669 let v = registry.get::<T>();
670 v.on_module_init().await;
671 })
672}
673
674fn hook_on_module_destroy<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
675where
676 T: Injectable + Send + Sync + 'static,
677{
678 Box::pin(async move {
679 let v = registry.get::<T>();
680 v.on_module_destroy().await;
681 })
682}
683
684fn hook_on_application_bootstrap<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
685where
686 T: Injectable + Send + Sync + 'static,
687{
688 Box::pin(async move {
689 let v = registry.get::<T>();
690 v.on_application_bootstrap().await;
691 })
692}
693
694fn hook_on_before_application_shutdown<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
695where
696 T: Injectable + Send + Sync + 'static,
697{
698 Box::pin(async move {
699 let v = registry.get::<T>();
700 v.on_before_application_shutdown().await;
701 })
702}
703
704fn hook_on_application_shutdown<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
705where
706 T: Injectable + Send + Sync + 'static,
707{
708 Box::pin(async move {
709 let v = registry.get::<T>();
710 v.on_application_shutdown().await;
711 })
712}
713
714fn lifecycle_on_module_init<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
717where
718 T: ProviderLifecycle + Send + Sync + 'static,
719{
720 Box::pin(async move {
721 let v = registry.get::<T>();
722 v.on_module_init().await;
723 })
724}
725
726fn lifecycle_on_module_destroy<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
727where
728 T: ProviderLifecycle + Send + Sync + 'static,
729{
730 Box::pin(async move {
731 let v = registry.get::<T>();
732 v.on_module_destroy().await;
733 })
734}
735
736fn lifecycle_on_application_bootstrap<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
737where
738 T: ProviderLifecycle + Send + Sync + 'static,
739{
740 Box::pin(async move {
741 let v = registry.get::<T>();
742 v.on_application_bootstrap().await;
743 })
744}
745
746fn lifecycle_on_before_application_shutdown<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
747where
748 T: ProviderLifecycle + Send + Sync + 'static,
749{
750 Box::pin(async move {
751 let v = registry.get::<T>();
752 v.on_before_application_shutdown().await;
753 })
754}
755
756fn lifecycle_on_application_shutdown<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
757where
758 T: ProviderLifecycle + Send + Sync + 'static,
759{
760 Box::pin(async move {
761 let v = registry.get::<T>();
762 v.on_application_shutdown().await;
763 })
764}
765
766#[async_trait]
776pub trait Injectable: Send + Sync + 'static {
777 fn construct(registry: &ProviderRegistry) -> Arc<Self>;
778
779 fn scope() -> ProviderScope {
781 ProviderScope::Singleton
782 }
783
784 async fn on_module_init(&self) {}
785 async fn on_module_destroy(&self) {}
786 async fn on_application_bootstrap(&self) {}
787 async fn on_before_application_shutdown(&self) {}
791 async fn on_application_shutdown(&self) {}
792}
793
794#[async_trait]
811pub trait ProviderLifecycle: Send + Sync + 'static {
812 async fn on_module_init(&self) {}
813 async fn on_module_destroy(&self) {}
814 async fn on_application_bootstrap(&self) {}
815 async fn on_before_application_shutdown(&self) {}
819 async fn on_application_shutdown(&self) {}
820}
821
822pub trait Controller {
823 fn register(router: Router, registry: &ProviderRegistry) -> Router;
824}
825
826pub trait Module {
827 fn build() -> (ProviderRegistry, Router);
828
829 fn exports() -> Vec<TypeId> {
830 Vec::new()
831 }
832}
833
834pub trait ModuleGraph {
840 fn register_providers(registry: &mut ProviderRegistry);
841 fn register_controllers(router: Router, registry: &ProviderRegistry) -> Router;
842}
843
844pub struct DynamicModule {
852 pub registry: ProviderRegistry,
854 pub router: Router,
855 pub exports: Vec<TypeId>,
857}
858
859impl DynamicModule {
860 pub fn from_module<M: Module>() -> Self {
862 let (registry, router) = M::build();
863 let exports = <M as Module>::exports();
864 Self {
865 registry,
866 router,
867 exports,
868 }
869 }
870
871 pub fn from_router(router: Router) -> Self {
873 Self {
874 registry: ProviderRegistry::new(),
875 router,
876 exports: Vec::new(),
877 }
878 }
879
880 pub fn from_parts(registry: ProviderRegistry, router: Router, exports: Vec<TypeId>) -> Self {
882 Self {
883 registry,
884 router,
885 exports,
886 }
887 }
888
889 pub fn lazy<M: Module + 'static>() -> Self {
892 static CELL: std::sync::OnceLock<DynamicModule> = std::sync::OnceLock::new();
893 CELL.get_or_init(DynamicModule::from_module::<M>).clone()
894 }
895}
896
897impl Clone for DynamicModule {
898 fn clone(&self) -> Self {
899 Self {
900 registry: self.registry.clone(),
901 router: self.router.clone(),
902 exports: self.exports.clone(),
903 }
904 }
905}
906
907pub struct ModuleOptions<O, M> {
912 inner: O,
913 _marker: std::marker::PhantomData<fn() -> M>,
914}
915
916impl<O, M> ModuleOptions<O, M> {
917 pub fn new(inner: O) -> Self {
918 Self {
919 inner,
920 _marker: std::marker::PhantomData,
921 }
922 }
923
924 pub fn get(&self) -> &O {
925 &self.inner
926 }
927
928 pub fn into_inner(self) -> O {
929 self.inner
930 }
931}
932
933impl<O, M> std::ops::Deref for ModuleOptions<O, M> {
934 type Target = O;
935
936 fn deref(&self) -> &Self::Target {
937 &self.inner
938 }
939}
940
941#[async_trait]
942impl<O, M> Injectable for ModuleOptions<O, M>
943where
944 O: Send + Sync + 'static,
945 M: 'static,
946{
947 fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
948 panic!(
949 "ModuleOptions requested but no value was provided. Use ConfigurableModuleBuilder / DynamicModuleBuilder to supply module options."
950 );
951 }
952}
953
954type RegistryOverrideFn = Box<dyn FnOnce(&mut ProviderRegistry) + Send>;
955
956pub struct DynamicModuleBuilder<M>
959where
960 M: Module + ModuleGraph,
961{
962 overrides: Vec<RegistryOverrideFn>,
963 _marker: std::marker::PhantomData<M>,
964}
965
966impl<M> DynamicModuleBuilder<M>
967where
968 M: Module + ModuleGraph,
969{
970 pub fn new() -> Self {
971 Self {
972 overrides: Vec::new(),
973 _marker: std::marker::PhantomData,
974 }
975 }
976
977 pub fn override_provider<T>(mut self, instance: Arc<T>) -> Self
978 where
979 T: Injectable + Send + Sync + 'static,
980 {
981 self.overrides
982 .push(Box::new(move |r| r.override_provider::<T>(instance)));
983 self
984 }
985
986 pub fn build(self) -> DynamicModule {
987 let mut registry = ProviderRegistry::new();
988 M::register_providers(&mut registry);
989 for apply in self.overrides {
990 apply(&mut registry);
991 }
992 let router = M::register_controllers(Router::new(), ®istry);
993 DynamicModule::from_parts(registry, router, M::exports())
994 }
995}
996
997impl<M> Default for DynamicModuleBuilder<M>
998where
999 M: Module + ModuleGraph,
1000{
1001 fn default() -> Self {
1002 Self::new()
1003 }
1004}
1005
1006pub struct ConfigurableModuleBuilder<O> {
1008 _marker: std::marker::PhantomData<O>,
1009}
1010
1011impl<O> ConfigurableModuleBuilder<O>
1012where
1013 O: Send + Sync + 'static,
1014{
1015 pub fn for_root<M>(options: O) -> DynamicModule
1016 where
1017 M: Module + ModuleGraph + 'static,
1018 {
1019 DynamicModuleBuilder::<M>::new()
1020 .override_provider::<ModuleOptions<O, M>>(Arc::new(ModuleOptions::new(options)))
1021 .build()
1022 }
1023
1024 pub async fn for_root_async<M, F, Fut>(factory: F) -> DynamicModule
1025 where
1026 M: Module + ModuleGraph + 'static,
1027 F: FnOnce() -> Fut,
1028 Fut: Future<Output = O>,
1029 {
1030 let options = factory().await;
1031 Self::for_root::<M>(options)
1032 }
1033}
1034
1035thread_local! {
1036 static MODULE_BUILD_STACK: std::cell::RefCell<Vec<(&'static str, TypeId)>> =
1037 const { std::cell::RefCell::new(Vec::new()) };
1038}
1039
1040#[doc(hidden)]
1042pub struct __NestrsModuleBuildGuard {
1043 type_id: TypeId,
1044}
1045
1046impl __NestrsModuleBuildGuard {
1047 pub fn push(type_id: TypeId, type_name: &'static str) -> Self {
1048 let is_cycle = MODULE_BUILD_STACK.with(|stack| {
1049 let mut guard = stack.borrow_mut();
1050 let cycle = guard.iter().any(|(_, id)| *id == type_id);
1051 if !cycle {
1052 guard.push((type_name, type_id));
1053 }
1054 cycle
1055 });
1056
1057 if is_cycle {
1058 __nestrs_panic_circular_module_dependency(type_name);
1059 }
1060
1061 Self { type_id }
1062 }
1063}
1064
1065impl Drop for __NestrsModuleBuildGuard {
1066 fn drop(&mut self) {
1067 MODULE_BUILD_STACK.with(|stack| {
1068 let mut guard = stack.borrow_mut();
1069 if let Some((_, id)) = guard.last() {
1070 if *id == self.type_id {
1071 guard.pop();
1072 }
1073 }
1074 });
1075 }
1076}
1077
1078#[doc(hidden)]
1079pub fn __nestrs_module_stack_contains(type_id: TypeId) -> bool {
1080 MODULE_BUILD_STACK.with(|stack| stack.borrow().iter().any(|(_, id)| *id == type_id))
1081}
1082
1083#[doc(hidden)]
1084pub fn __nestrs_panic_circular_module_dependency(import_type_name: &'static str) -> ! {
1085 let chain = MODULE_BUILD_STACK.with(|stack| {
1086 stack
1087 .borrow()
1088 .iter()
1089 .map(|(name, _)| *name)
1090 .chain(std::iter::once(import_type_name))
1091 .collect::<Vec<_>>()
1092 .join(" -> ")
1093 });
1094
1095 panic!(
1096 "Circular module dependency detected: {chain}. If intentional, mark the NestJS-style back-edge import with `forward_ref::<T>()` (or `forwardRef` alias in the `#[module]` macro). See the nestrs mdBook chapter **Fundamentals** (`docs/src/fundamentals.md`).",
1097 );
1098}
1099
1100tokio::task_local! {
1101 static REQUEST_SCOPE_CACHE: std::cell::RefCell<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>;
1102}
1103
1104pub async fn with_request_scope<Fut, T>(future: Fut) -> T
1119where
1120 Fut: std::future::Future<Output = T>,
1121{
1122 if REQUEST_SCOPE_CACHE.try_with(|_| ()).is_ok() {
1125 return future.await;
1126 }
1127 REQUEST_SCOPE_CACHE
1128 .scope(std::cell::RefCell::new(HashMap::new()), future)
1129 .await
1130}
1131
1132pub fn spawn_with_request_scope<F>(future: F) -> tokio::task::JoinHandle<F::Output>
1159where
1160 F: std::future::Future + Send + 'static,
1161 F::Output: Send + 'static,
1162{
1163 let snapshot = REQUEST_SCOPE_CACHE
1164 .try_with(|cell| cell.borrow().clone())
1165 .ok();
1166 tokio::spawn(async move {
1167 match snapshot {
1168 Some(map) => {
1169 REQUEST_SCOPE_CACHE
1170 .scope(std::cell::RefCell::new(map), future)
1171 .await
1172 }
1173 None => with_request_scope(future).await,
1174 }
1175 })
1176}
1177
1178pub fn request_scope_get(type_id: TypeId) -> Option<Arc<dyn Any + Send + Sync>> {
1182 REQUEST_SCOPE_CACHE
1183 .try_with(|c| c.borrow().get(&type_id).cloned())
1184 .ok()
1185 .flatten()
1186}
1187
1188pub fn request_scope_insert(type_id: TypeId, value: Arc<dyn Any + Send + Sync>) {
1191 let _ = REQUEST_SCOPE_CACHE.try_with(|c| {
1192 c.borrow_mut().insert(type_id, value);
1193 });
1194}
1195
1196tokio::task_local! {
1208 static ABILITY_SLOT: std::cell::RefCell<Option<Arc<dyn Any + Send + Sync>>>;
1209}
1210
1211pub async fn with_ability_erased<F, T>(ability: Arc<dyn Any + Send + Sync>, future: F) -> T
1218where
1219 F: std::future::Future<Output = T>,
1220{
1221 ABILITY_SLOT
1222 .scope(std::cell::RefCell::new(Some(ability)), future)
1223 .await
1224}
1225
1226pub fn current_ability_erased() -> Option<Arc<dyn Any + Send + Sync>> {
1230 ABILITY_SLOT.try_with(|c| c.borrow().clone()).ok().flatten()
1231}
1232
1233tokio::task_local! {
1245 static PRINCIPAL_SLOT: std::cell::RefCell<Option<Arc<dyn Any + Send + Sync>>>;
1246}
1247
1248pub async fn with_principal_erased<F, T>(principal: Arc<dyn Any + Send + Sync>, future: F) -> T
1256where
1257 F: std::future::Future<Output = T>,
1258{
1259 PRINCIPAL_SLOT
1260 .scope(std::cell::RefCell::new(Some(principal)), future)
1261 .await
1262}
1263
1264pub fn current_principal_erased() -> Option<Arc<dyn Any + Send + Sync>> {
1268 PRINCIPAL_SLOT
1269 .try_with(|c| c.borrow().clone())
1270 .ok()
1271 .flatten()
1272}
1273
1274thread_local! {
1275 static CONSTRUCTION_STACK: std::cell::RefCell<Vec<(&'static str, TypeId)>> =
1276 const { std::cell::RefCell::new(Vec::new()) };
1277}
1278
1279struct ConstructionGuard {
1280 type_id: TypeId,
1281}
1282
1283impl ConstructionGuard {
1284 fn push(type_id: TypeId, type_name: &'static str) -> Self {
1285 CONSTRUCTION_STACK.with(|stack| {
1286 let mut guard = stack.borrow_mut();
1287 if guard.iter().any(|(_, id)| *id == type_id) {
1288 let chain = guard
1289 .iter()
1290 .map(|(name, _)| *name)
1291 .chain(std::iter::once(type_name))
1292 .collect::<Vec<_>>()
1293 .join(" -> ");
1294 panic!(
1295 "Circular provider dependency detected: {chain}. Break the cycle with lazy construction (`register_use_factory`), split types, defer work to `on_module_init`, or a `forward_ref`-style module import for module graphs. See the nestrs mdBook chapter **Fundamentals** (`docs/src/fundamentals.md`)."
1296 );
1297 }
1298 guard.push((type_name, type_id));
1299 });
1300 Self { type_id }
1301 }
1302}
1303
1304impl Drop for ConstructionGuard {
1305 fn drop(&mut self) {
1306 CONSTRUCTION_STACK.with(|stack| {
1307 let mut guard = stack.borrow_mut();
1308 if let Some((_, id)) = guard.last() {
1309 if *id == self.type_id {
1310 guard.pop();
1311 }
1312 }
1313 });
1314 }
1315}
1316
1317fn provider_dep_graph() -> &'static RwLock<HashMap<TypeId, Vec<TypeId>>> {
1320 static DEPS: OnceLock<RwLock<HashMap<TypeId, Vec<TypeId>>>> = OnceLock::new();
1321 DEPS.get_or_init(|| RwLock::new(HashMap::new()))
1322}
1323
1324fn record_provider_dependency(from: TypeId, to: TypeId) {
1325 {
1331 let deps = provider_dep_graph().read().expect("provider dep graph");
1332 if let Some(targets) = deps.get(&from) {
1333 if targets.contains(&to) {
1334 return;
1335 }
1336 }
1337 }
1338 let mut deps = provider_dep_graph().write().expect("provider dep graph");
1339 let targets = deps.entry(from).or_default();
1340 if !targets.contains(&to) {
1341 targets.push(to);
1342 }
1343}
1344
1345#[cfg(feature = "test-hooks")]
1349pub fn clear_provider_dependencies_for_tests() {
1350 provider_dep_graph()
1351 .write()
1352 .expect("provider dep graph")
1353 .clear();
1354}
1355
1356type ModuleBuildFn = Box<dyn FnOnce() -> (ProviderRegistry, Router) + Send>;
1357
1358static MODULE_BUILD_CACHE: OnceLock<RwLock<HashMap<TypeId, Arc<OnceLock<DynamicModule>>>>> =
1359 OnceLock::new();
1360
1361fn module_build_cache() -> &'static RwLock<HashMap<TypeId, Arc<OnceLock<DynamicModule>>>> {
1362 MODULE_BUILD_CACHE.get_or_init(|| RwLock::new(HashMap::new()))
1363}
1364
1365#[doc(hidden)]
1379pub fn __nestrs_memoize_module_build<M: Module + 'static>(
1380 build: ModuleBuildFn,
1381) -> (ProviderRegistry, Router) {
1382 let key = TypeId::of::<M>();
1383 let entry = Arc::clone(
1384 module_build_cache()
1385 .write()
1386 .expect("module build cache")
1387 .entry(key)
1388 .or_insert_with(|| Arc::new(OnceLock::new())),
1389 );
1390 let dm = entry.get_or_init(|| {
1394 let (registry, router) = build();
1395 DynamicModule::from_parts(registry, router, <M as Module>::exports())
1396 });
1397 (dm.registry.clone(), dm.router.clone())
1398}
1399
1400#[cfg(feature = "test-hooks")]
1405pub fn clear_module_cache_for_tests() {
1406 module_build_cache()
1407 .write()
1408 .expect("module build cache")
1409 .clear();
1410}
1411
1412#[cfg(test)]
1413mod request_scope_tests {
1414 use super::*;
1423 use std::sync::atomic::{AtomicUsize, Ordering};
1424
1425 struct Marker;
1426 struct OtherMarker;
1427
1428 #[tokio::test]
1429 async fn nested_with_request_scope_joins_the_outer_scope() {
1430 with_request_scope(async {
1431 request_scope_insert(
1432 TypeId::of::<Marker>(),
1433 Arc::new(Marker) as Arc<dyn Any + Send + Sync>,
1434 );
1435
1436 with_request_scope(async {
1439 assert!(
1440 request_scope_get(TypeId::of::<Marker>()).is_some(),
1441 "nested with_request_scope must not hide outer values"
1442 );
1443 request_scope_insert(
1445 TypeId::of::<OtherMarker>(),
1446 Arc::new(OtherMarker) as Arc<dyn Any + Send + Sync>,
1447 );
1448 })
1449 .await;
1450
1451 assert!(
1453 request_scope_get(TypeId::of::<OtherMarker>()).is_some(),
1454 "nested inserts must land in the active (outer) scope"
1455 );
1456 })
1457 .await;
1458 }
1459
1460 #[tokio::test]
1461 async fn with_request_scope_opens_a_fresh_scope_when_none_is_active() {
1462 assert!(request_scope_get(TypeId::of::<Marker>()).is_none());
1465 with_request_scope(async {
1467 request_scope_insert(
1468 TypeId::of::<Marker>(),
1469 Arc::new(Marker) as Arc<dyn Any + Send + Sync>,
1470 );
1471 assert!(request_scope_get(TypeId::of::<Marker>()).is_some());
1472 })
1473 .await;
1474 }
1475
1476 static REQUEST_PROVIDER_CONSTRUCTIONS: AtomicUsize = AtomicUsize::new(0);
1477
1478 struct CountedRequestService;
1479
1480 impl Injectable for CountedRequestService {
1481 fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
1482 REQUEST_PROVIDER_CONSTRUCTIONS.fetch_add(1, Ordering::SeqCst);
1483 Arc::new(Self)
1484 }
1485
1486 fn scope() -> ProviderScope {
1487 ProviderScope::Request
1488 }
1489 }
1490
1491 static SNAPSHOT_CONSTRUCTIONS: AtomicUsize = AtomicUsize::new(0);
1495
1496 struct SnapshottedService;
1497
1498 impl Injectable for SnapshottedService {
1499 fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
1500 SNAPSHOT_CONSTRUCTIONS.fetch_add(1, Ordering::SeqCst);
1501 Arc::new(Self)
1502 }
1503
1504 fn scope() -> ProviderScope {
1505 ProviderScope::Request
1506 }
1507 }
1508
1509 static OFF_SCOPE_CONSTRUCTIONS: AtomicUsize = AtomicUsize::new(0);
1510
1511 struct OffScopeService;
1512
1513 impl Injectable for OffScopeService {
1514 fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
1515 OFF_SCOPE_CONSTRUCTIONS.fetch_add(1, Ordering::SeqCst);
1516 Arc::new(Self)
1517 }
1518
1519 fn scope() -> ProviderScope {
1520 ProviderScope::Request
1521 }
1522 }
1523
1524 #[tokio::test]
1525 async fn nested_scope_does_not_rebuild_request_providers() {
1526 REQUEST_PROVIDER_CONSTRUCTIONS.store(0, Ordering::SeqCst);
1531 let mut registry = ProviderRegistry::new();
1532 registry.register::<CountedRequestService>();
1533
1534 with_request_scope(async {
1535 let outer: Arc<CountedRequestService> = registry.get();
1536
1537 let inner: Arc<CountedRequestService> =
1538 with_request_scope(async { registry.get::<CountedRequestService>() }).await;
1539
1540 assert!(
1541 Arc::ptr_eq(&outer, &inner),
1542 "nested with_request_scope must reuse the request-scoped instance"
1543 );
1544 assert_eq!(
1545 REQUEST_PROVIDER_CONSTRUCTIONS.load(Ordering::SeqCst),
1546 1,
1547 "one construction per request, not one per nested scope"
1548 );
1549 })
1550 .await;
1551 }
1552
1553 #[tokio::test]
1561 async fn try_get_returns_none_off_scope_instead_of_panicking() {
1562 let mut registry = ProviderRegistry::new();
1563 registry.register::<CountedRequestService>();
1564
1565 assert!(
1568 registry.try_get::<CountedRequestService>().is_none(),
1569 "off-scope Request-scoped resolution is absence, not a panic"
1570 );
1571 }
1572
1573 #[test]
1574 #[should_panic(expected = "spawn_with_request_scope")]
1575 fn get_off_scope_panics_with_the_fix_in_the_message() {
1576 let mut registry = ProviderRegistry::new();
1577 registry.register::<CountedRequestService>();
1578
1579 let _ = registry.get::<CountedRequestService>();
1580 }
1581
1582 #[tokio::test]
1583 async fn spawned_task_sees_the_request_snapshot() {
1584 SNAPSHOT_CONSTRUCTIONS.store(0, Ordering::SeqCst);
1585 let mut registry = ProviderRegistry::new();
1586 registry.register::<SnapshottedService>();
1587
1588 with_request_scope(async {
1589 let outer: Arc<SnapshottedService> = registry.get();
1590
1591 let registry = registry.clone();
1592 let handle = spawn_with_request_scope(async move {
1593 let child: Arc<SnapshottedService> = registry.get();
1594 Arc::ptr_eq(&outer, &child)
1595 });
1596
1597 assert!(
1598 handle.await.unwrap(),
1599 "spawned task must resolve the instance the request had at spawn time"
1600 );
1601 assert_eq!(
1602 SNAPSHOT_CONSTRUCTIONS.load(Ordering::SeqCst),
1603 1,
1604 "snapshot must carry the instance, not re-construct it"
1605 );
1606 })
1607 .await;
1608 }
1609
1610 #[tokio::test]
1611 async fn spawned_task_writes_stay_in_the_child() {
1612 with_request_scope(async {
1613 let handle = spawn_with_request_scope(async {
1614 request_scope_insert(
1615 TypeId::of::<OtherMarker>(),
1616 Arc::new(OtherMarker) as Arc<dyn Any + Send + Sync>,
1617 );
1618 assert!(
1619 request_scope_get(TypeId::of::<OtherMarker>()).is_some(),
1620 "the child sees its own inserts"
1621 );
1622 });
1623 handle.await.unwrap();
1624
1625 assert!(
1626 request_scope_get(TypeId::of::<OtherMarker>()).is_none(),
1627 "child-scope writes must not leak into the parent request scope"
1628 );
1629 })
1630 .await;
1631 }
1632
1633 #[tokio::test]
1634 async fn spawning_off_scope_gives_each_child_a_fresh_scope() {
1635 OFF_SCOPE_CONSTRUCTIONS.store(0, Ordering::SeqCst);
1636 let mut registry = ProviderRegistry::new();
1637 registry.register::<OffScopeService>();
1638
1639 let a = {
1642 let registry = registry.clone();
1643 spawn_with_request_scope(async move { registry.get::<OffScopeService>() })
1644 };
1645 let b = {
1646 let registry = registry.clone();
1647 spawn_with_request_scope(async move { registry.get::<OffScopeService>() })
1648 };
1649
1650 let a = a.await.unwrap();
1651 let b = b.await.unwrap();
1652 assert!(
1653 !Arc::ptr_eq(&a, &b),
1654 "off-scope spawns must not share request-scoped instances"
1655 );
1656 assert_eq!(
1657 OFF_SCOPE_CONSTRUCTIONS.load(Ordering::SeqCst),
1658 2,
1659 "one construction per spawned scope"
1660 );
1661 }
1662}
1663
1664#[cfg(test)]
1665mod provider_lifecycle_tests {
1666 use super::*;
1673 use std::sync::Mutex;
1674
1675 type Log = Arc<Mutex<Vec<String>>>;
1676
1677 fn assert_log(log: &Log, expected: &[&str]) {
1678 let got = log.lock().unwrap();
1679 let expected: Vec<String> = expected.iter().map(|s| s.to_string()).collect();
1680 assert_eq!(*got, expected, "hook firing order");
1681 }
1682
1683 struct Tagged<const TAG: char> {
1687 log: Log,
1688 }
1689
1690 impl<const TAG: char> Tagged<TAG> {
1691 fn record(&self, event: &str) {
1692 self.log.lock().unwrap().push(format!("{TAG}:{event}"));
1693 }
1694 }
1695
1696 #[async_trait]
1697 impl<const TAG: char> ProviderLifecycle for Tagged<TAG> {
1698 async fn on_module_init(&self) {
1699 self.record("init");
1700 }
1701 async fn on_module_destroy(&self) {
1702 self.record("destroy");
1703 }
1704 async fn on_application_bootstrap(&self) {
1705 self.record("bootstrap");
1706 }
1707 async fn on_before_application_shutdown(&self) {
1708 self.record("before_shutdown");
1709 }
1710 async fn on_application_shutdown(&self) {
1711 self.record("shutdown");
1712 }
1713 }
1714
1715 #[tokio::test]
1716 async fn use_value_lifecycle_hooks_fire_in_framework_order() {
1717 let log: Log = Arc::default();
1718 let mut registry = ProviderRegistry::new();
1719 registry.register_use_value_with_lifecycle(Arc::new(Tagged::<'V'> { log: log.clone() }));
1720
1721 registry.run_on_module_init().await;
1722 registry.run_on_application_bootstrap().await;
1723 registry.run_on_before_application_shutdown().await;
1724 registry.run_on_application_shutdown().await;
1725 registry.run_on_module_destroy().await;
1726
1727 assert_log(
1728 &log,
1729 &[
1730 "V:init",
1731 "V:bootstrap",
1732 "V:before_shutdown",
1733 "V:shutdown",
1734 "V:destroy",
1735 ],
1736 );
1737 }
1738
1739 #[tokio::test]
1740 async fn lifecycle_hooks_register_order_init_reverse_destroy() {
1741 let log: Log = Arc::default();
1742 let mut registry = ProviderRegistry::new();
1743 registry.register_use_value_with_lifecycle(Arc::new(Tagged::<'A'> { log: log.clone() }));
1744 registry.register_use_value_with_lifecycle(Arc::new(Tagged::<'B'> { log: log.clone() }));
1745
1746 registry.run_on_module_init().await;
1747 registry.run_on_module_destroy().await;
1748
1749 assert_log(&log, &["A:init", "B:init", "B:destroy", "A:destroy"]);
1752 }
1753
1754 #[tokio::test]
1755 async fn use_factory_lifecycle_hooks_fire_on_the_lazily_built_singleton() {
1756 let log: Log = Arc::default();
1757 let mut registry = ProviderRegistry::new();
1758 registry.register_use_factory_with_lifecycle(ProviderScope::Singleton, {
1759 let log = log.clone();
1760 move |_r| {
1761 log.lock().unwrap().push("F:construct".to_string());
1762 Arc::new(Tagged::<'F'> { log: log.clone() })
1763 }
1764 });
1765
1766 registry.run_on_module_init().await;
1770 assert_log(&log, &["F:construct", "F:init"]);
1771
1772 let _v: Arc<Tagged<'F'>> = registry.get();
1774 assert_log(&log, &["F:construct", "F:init"]);
1775 }
1776
1777 #[tokio::test]
1778 async fn plain_use_value_and_use_factory_stay_hook_less() {
1779 let log: Log = Arc::default();
1784 let mut registry = ProviderRegistry::new();
1785 registry.register_use_value(Arc::new(Tagged::<'V'> { log: log.clone() }));
1786 registry.register_use_factory(ProviderScope::Singleton, {
1787 let log = log.clone();
1788 move |_r| Arc::new(Tagged::<'G'> { log: log.clone() })
1789 });
1790
1791 registry.run_on_module_init().await;
1792 registry.run_on_application_bootstrap().await;
1793 registry.run_on_before_application_shutdown().await;
1794 registry.run_on_application_shutdown().await;
1795 registry.run_on_module_destroy().await;
1796
1797 assert_log(&log, &[]);
1798 }
1799}
1800
1801#[cfg(test)]
1802mod provider_dep_graph_tests {
1803 use super::*;
1812
1813 struct FromA;
1814 struct ToB;
1815
1816 #[test]
1817 fn record_provider_dependency_is_idempotent_on_repeated_edges() {
1818 let from = TypeId::of::<FromA>();
1819 let to = TypeId::of::<ToB>();
1820 record_provider_dependency(from, to);
1821 record_provider_dependency(from, to);
1824 record_provider_dependency(from, to);
1825 let deps = provider_dep_graph().read().expect("provider dep graph");
1826 let targets = deps.get(&from).expect("edge recorded");
1827 assert_eq!(targets.len(), 1, "repeated edges must not duplicate");
1828 assert_eq!(targets[0], to);
1829 }
1830
1831 #[test]
1832 fn record_provider_dependency_dedupes_racing_first_recordings() {
1833 let from = TypeId::of::<FromA>();
1837 let to = TypeId::of::<ToB>();
1838 std::thread::scope(|s| {
1839 for _ in 0..16 {
1840 s.spawn(|| record_provider_dependency(from, to));
1841 }
1842 });
1843 let deps = provider_dep_graph().read().expect("provider dep graph");
1844 let targets = deps.get(&from).expect("edge recorded");
1845 assert_eq!(
1846 targets.len(),
1847 1,
1848 "racing recorders must not duplicate an edge"
1849 );
1850 }
1851}
1852
1853#[cfg(test)]
1854mod override_provider_tests {
1855 use super::*;
1864
1865 struct SingletonSvc;
1866
1867 impl Injectable for SingletonSvc {
1868 fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
1869 unreachable!("overridden before construction")
1870 }
1871 }
1872
1873 struct RequestSvc;
1874
1875 impl Injectable for RequestSvc {
1876 fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
1877 unreachable!("overridden before construction")
1878 }
1879
1880 fn scope() -> ProviderScope {
1881 ProviderScope::Request
1882 }
1883 }
1884
1885 struct TransientSvc;
1886
1887 impl Injectable for TransientSvc {
1888 fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
1889 unreachable!("overridden before construction")
1890 }
1891
1892 fn scope() -> ProviderScope {
1893 ProviderScope::Transient
1894 }
1895 }
1896
1897 fn declared_scope<T: 'static>(registry: &ProviderRegistry) -> ProviderScope {
1898 let name = std::any::type_name::<T>();
1899 registry
1900 .provider_summaries()
1901 .into_iter()
1902 .find(|s| s.type_name == name)
1903 .map(|s| s.scope)
1904 .unwrap_or_else(|| panic!("{name} not registered"))
1905 }
1906
1907 #[test]
1908 fn singleton_override_returns_the_instance_and_keeps_singleton_scope() {
1909 let mut registry = ProviderRegistry::new();
1910 registry.register::<SingletonSvc>();
1911 let mock = Arc::new(SingletonSvc);
1912 registry.override_provider::<SingletonSvc>(mock.clone());
1913
1914 let resolved = registry.get::<SingletonSvc>();
1915 assert!(Arc::ptr_eq(&resolved, &mock), "override instance served");
1916 assert_eq!(
1917 declared_scope::<SingletonSvc>(®istry),
1918 ProviderScope::Singleton,
1919 "singleton overrides stay singleton"
1920 );
1921 }
1922
1923 #[tokio::test]
1924 async fn request_override_keeps_request_scope_and_resolves_to_the_instance() {
1925 let mut registry = ProviderRegistry::new();
1926 registry.register::<RequestSvc>();
1927 let mock = Arc::new(RequestSvc);
1928 registry.override_provider::<RequestSvc>(mock.clone());
1929
1930 let first = with_request_scope(async {
1933 let a = registry.get::<RequestSvc>();
1934 let b = registry.get::<RequestSvc>();
1935 assert!(Arc::ptr_eq(&a, &b), "one resolution per request");
1936 a
1937 })
1938 .await;
1939 assert!(Arc::ptr_eq(&first, &mock), "override served inside request");
1940
1941 let second = with_request_scope(async { registry.get::<RequestSvc>() }).await;
1946 assert!(
1947 Arc::ptr_eq(&second, &mock),
1948 "override instance shared across requests"
1949 );
1950
1951 assert_eq!(
1952 declared_scope::<RequestSvc>(®istry),
1953 ProviderScope::Request,
1954 "request overrides keep request scope — no silent Singleton coercion"
1955 );
1956 }
1957
1958 #[test]
1959 fn transient_override_keeps_transient_scope_and_resolves_each_time() {
1960 let mut registry = ProviderRegistry::new();
1961 registry.register::<TransientSvc>();
1962 let mock = Arc::new(TransientSvc);
1963 registry.override_provider::<TransientSvc>(mock.clone());
1964
1965 let a = registry.get::<TransientSvc>();
1969 let b = registry.get::<TransientSvc>();
1970 assert!(Arc::ptr_eq(&a, &mock), "first transient resolution served");
1971 assert!(Arc::ptr_eq(&b, &mock), "second transient resolution served");
1972 assert_eq!(
1973 declared_scope::<TransientSvc>(®istry),
1974 ProviderScope::Transient,
1975 "transient overrides keep transient scope"
1976 );
1977 }
1978}