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::{ExecutionContext, HostType, HttpExecutionArguments};
39pub use guard::{CanActivate, GuardError};
40pub use metadata::MetadataRegistry;
41pub use module_ref::ModuleRef;
42pub use pipe::{HttpPipeTransform, PipeTransform};
43pub use platform::{AxumHttpEngine, HttpServerEngine};
44pub use route_registry::{OpenApiResponseDesc, OpenApiRouteSpec, RouteInfo, RouteRegistry};
45pub use strategy::{AuthError, AuthStrategy};
46pub use trace::{current_trace_context, parse_traceparent, with_trace_context, TraceContext};
47
48#[cfg(feature = "sse")]
49pub use sse::{serialize_to_event, IntoSseEvent, SseEvent, SseKeepAlive, SseResponse};
50
51type CustomFactoryFn =
52 std::sync::Arc<dyn Fn(&ProviderRegistry) -> Arc<dyn Any + Send + Sync> + Send + Sync>;
53
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
62pub enum ProviderScope {
63 Singleton,
65 Transient,
67 Request,
69}
70
71#[derive(Clone, Copy, Debug, PartialEq, Eq)]
74pub struct ProviderSummary {
75 pub type_name: &'static str,
76 pub scope: ProviderScope,
77}
78
79#[derive(Clone)]
80enum ProviderFactory {
81 InjectableFn(fn(&ProviderRegistry) -> Arc<dyn Any + Send + Sync>),
82 Custom(CustomFactoryFn),
83}
84
85#[derive(Clone)]
86struct ProviderEntry {
87 type_name: &'static str,
88 scope: ProviderScope,
89 factory: ProviderFactory,
90 instance: Arc<OnceLock<Arc<dyn Any + Send + Sync>>>,
91 on_module_init: HookFn,
92 on_module_destroy: HookFn,
93 on_application_bootstrap: HookFn,
94 on_before_application_shutdown: HookFn,
97 on_application_shutdown: HookFn,
98}
99
100fn noop_hook<'a>(_registry: &'a ProviderRegistry) -> HookFuture<'a> {
101 Box::pin(async {})
102}
103
104fn create_entry_for_injectable<T: Injectable + Send + Sync + 'static>() -> ProviderEntry {
105 fn factory<T: Injectable + Send + Sync + 'static>(
106 registry: &ProviderRegistry,
107 ) -> Arc<dyn Any + Send + Sync> {
108 T::construct(registry)
109 }
110
111 ProviderEntry {
112 type_name: std::any::type_name::<T>(),
113 scope: T::scope(),
114 factory: ProviderFactory::InjectableFn(factory::<T>),
115 instance: Arc::new(OnceLock::new()),
116 on_module_init: hook_on_module_init::<T>,
117 on_module_destroy: hook_on_module_destroy::<T>,
118 on_application_bootstrap: hook_on_application_bootstrap::<T>,
119 on_before_application_shutdown: hook_on_before_application_shutdown::<T>,
120 on_application_shutdown: hook_on_application_shutdown::<T>,
121 }
122}
123
124pub struct ProviderRegistry {
125 entries: HashMap<TypeId, ProviderEntry>,
126 order: Vec<TypeId>,
129}
130
131#[derive(Clone, Copy, Debug)]
133pub struct HandlerKey(pub &'static str);
134
135impl ProviderRegistry {
136 pub fn new() -> Self {
137 Self {
138 entries: HashMap::new(),
139 order: Vec::new(),
140 }
141 }
142
143 fn insert_entry(&mut self, type_id: TypeId, entry: ProviderEntry) {
144 if !self.entries.contains_key(&type_id) {
145 self.order.push(type_id);
146 }
147 self.entries.insert(type_id, entry);
148 }
149
150 pub fn register<T>(&mut self)
151 where
152 T: Injectable + Send + Sync + 'static,
153 {
154 self.insert_entry(TypeId::of::<T>(), create_entry_for_injectable::<T>());
155 }
156
157 pub fn register_use_value<T: Send + Sync + 'static>(&mut self, value: Arc<T>) {
163 let preset: Arc<dyn Any + Send + Sync> = value;
164 let cell = Arc::new(OnceLock::new());
165 let _ = cell.set(preset.clone());
166 self.insert_entry(
167 TypeId::of::<T>(),
168 ProviderEntry {
169 type_name: std::any::type_name::<T>(),
170 scope: ProviderScope::Singleton,
171 factory: ProviderFactory::Custom(Arc::new(move |_| preset.clone())),
172 instance: cell,
173 on_module_init: noop_hook,
174 on_module_destroy: noop_hook,
175 on_application_bootstrap: noop_hook,
176 on_before_application_shutdown: noop_hook,
177 on_application_shutdown: noop_hook,
178 },
179 );
180 }
181
182 pub fn register_use_factory<T, F>(&mut self, scope: ProviderScope, factory: F)
195 where
196 T: Send + Sync + 'static,
197 F: Fn(&ProviderRegistry) -> Arc<T> + Send + Sync + 'static,
198 {
199 let factory: std::sync::Arc<F> = std::sync::Arc::new(factory);
200 let factory = factory.clone();
201 self.insert_entry(
202 TypeId::of::<T>(),
203 ProviderEntry {
204 type_name: std::any::type_name::<T>(),
205 scope,
206 factory: ProviderFactory::Custom(Arc::new(move |r| {
207 let v = factory(r);
208 v as Arc<dyn Any + Send + Sync>
209 })),
210 instance: Arc::new(OnceLock::new()),
211 on_module_init: noop_hook,
212 on_module_destroy: noop_hook,
213 on_application_bootstrap: noop_hook,
214 on_before_application_shutdown: noop_hook,
215 on_application_shutdown: noop_hook,
216 },
217 );
218 }
219
220 pub fn register_use_value_with_lifecycle<T>(&mut self, value: Arc<T>)
224 where
225 T: ProviderLifecycle + Send + Sync + 'static,
226 {
227 let preset: Arc<dyn Any + Send + Sync> = value;
228 let cell = Arc::new(OnceLock::new());
229 let _ = cell.set(preset.clone());
230 self.insert_entry(
231 TypeId::of::<T>(),
232 ProviderEntry {
233 type_name: std::any::type_name::<T>(),
234 scope: ProviderScope::Singleton,
235 factory: ProviderFactory::Custom(Arc::new(move |_| preset.clone())),
236 instance: cell,
237 on_module_init: lifecycle_on_module_init::<T>,
238 on_module_destroy: lifecycle_on_module_destroy::<T>,
239 on_application_bootstrap: lifecycle_on_application_bootstrap::<T>,
240 on_before_application_shutdown: lifecycle_on_before_application_shutdown::<T>,
241 on_application_shutdown: lifecycle_on_application_shutdown::<T>,
242 },
243 );
244 }
245
246 pub fn register_use_factory_with_lifecycle<T, F>(&mut self, scope: ProviderScope, factory: F)
251 where
252 T: ProviderLifecycle + Send + Sync + 'static,
253 F: Fn(&ProviderRegistry) -> Arc<T> + Send + Sync + 'static,
254 {
255 let factory: std::sync::Arc<F> = std::sync::Arc::new(factory);
256 let factory = factory.clone();
257 self.insert_entry(
258 TypeId::of::<T>(),
259 ProviderEntry {
260 type_name: std::any::type_name::<T>(),
261 scope,
262 factory: ProviderFactory::Custom(Arc::new(move |r| {
263 let v = factory(r);
264 v as Arc<dyn Any + Send + Sync>
265 })),
266 instance: Arc::new(OnceLock::new()),
267 on_module_init: lifecycle_on_module_init::<T>,
268 on_module_destroy: lifecycle_on_module_destroy::<T>,
269 on_application_bootstrap: lifecycle_on_application_bootstrap::<T>,
270 on_before_application_shutdown: lifecycle_on_before_application_shutdown::<T>,
271 on_application_shutdown: lifecycle_on_application_shutdown::<T>,
272 },
273 );
274 }
275
276 #[inline]
278 pub fn register_use_class<T>(&mut self)
279 where
280 T: Injectable + Send + Sync + 'static,
281 {
282 self.register::<T>();
283 }
284
285 pub fn override_provider<T>(&mut self, instance: Arc<T>)
295 where
296 T: Injectable + Send + Sync + 'static,
297 {
298 let preset: Arc<dyn Any + Send + Sync> = instance;
299 let instance_cell = Arc::new(OnceLock::new());
300 let _ = instance_cell.set(preset.clone());
301 let entry = ProviderEntry {
302 type_name: std::any::type_name::<T>(),
303 scope: T::scope(),
304 factory: ProviderFactory::Custom(Arc::new(move |_| preset.clone())),
308 instance: instance_cell,
309 on_module_init: hook_on_module_init::<T>,
310 on_module_destroy: hook_on_module_destroy::<T>,
311 on_application_bootstrap: hook_on_application_bootstrap::<T>,
312 on_before_application_shutdown: hook_on_before_application_shutdown::<T>,
313 on_application_shutdown: hook_on_application_shutdown::<T>,
314 };
315
316 self.insert_entry(TypeId::of::<T>(), entry);
317 }
318
319 fn produce_any(
325 &self,
326 type_id: TypeId,
327 entry: &ProviderEntry,
328 ) -> Option<Arc<dyn Any + Send + Sync>> {
329 match entry.scope {
330 ProviderScope::Singleton => {
331 let _guard = ConstructionGuard::push(type_id, entry.type_name);
332 Some(
333 entry
334 .instance
335 .get_or_init(|| match &entry.factory {
336 ProviderFactory::InjectableFn(f) => f(self),
337 ProviderFactory::Custom(f) => f(self),
338 })
339 .clone(),
340 )
341 }
342 ProviderScope::Transient => {
343 let _guard = ConstructionGuard::push(type_id, entry.type_name);
344 Some(match &entry.factory {
345 ProviderFactory::InjectableFn(f) => f(self),
346 ProviderFactory::Custom(f) => f(self),
347 })
348 }
349 ProviderScope::Request => {
350 let _guard = ConstructionGuard::push(type_id, entry.type_name);
351 REQUEST_SCOPE_CACHE
356 .try_with(|cell| {
357 if let Some(existing) = cell.borrow().get(&type_id).cloned() {
358 return existing;
359 }
360 let value = match &entry.factory {
361 ProviderFactory::InjectableFn(f) => f(self),
362 ProviderFactory::Custom(f) => f(self),
363 };
364 cell.borrow_mut().insert(type_id, value.clone());
365 value
366 })
367 .ok()
368 }
369 }
370 }
371
372 pub fn get<T>(&self) -> Arc<T>
384 where
385 T: Send + Sync + 'static,
386 {
387 self.try_get::<T>().unwrap_or_else(|| {
388 if let Some(entry) = self.entries.get(&TypeId::of::<T>()) {
390 if matches!(entry.scope, ProviderScope::Request) {
391 panic!(
392 "Request-scoped provider `{}` requested outside a request scope; \
393 enable request scope middleware (`use_request_scope`), spawn \
394 background work with `spawn_with_request_scope`, or use \
395 `try_get` to handle absence gracefully",
396 entry.type_name
397 );
398 }
399 }
400 panic!("Provider `{}` not registered", std::any::type_name::<T>())
401 })
402 }
403
404 pub fn try_get<T>(&self) -> Option<Arc<T>>
410 where
411 T: Send + Sync + 'static,
412 {
413 let type_id = TypeId::of::<T>();
414 let entry = self.entries.get(&type_id)?;
415
416 if let Some(parent) =
417 CONSTRUCTION_STACK.with(|stack| stack.borrow().last().map(|(_, id)| *id))
418 {
419 record_provider_dependency(parent, type_id);
420 }
421
422 let any = self.produce_any(type_id, entry)?;
423
424 any.downcast::<T>().ok()
425 }
426
427 pub fn registered_type_ids(&self) -> Vec<TypeId> {
429 self.order.clone()
430 }
431
432 pub fn registered_type_names(&self) -> Vec<&'static str> {
434 self.order
435 .iter()
436 .filter_map(|id| self.entries.get(id).map(|e| e.type_name))
437 .collect()
438 }
439
440 pub fn provider_summaries(&self) -> Vec<ProviderSummary> {
445 self.order
446 .iter()
447 .filter_map(|id| {
448 self.entries.get(id).map(|e| ProviderSummary {
449 type_name: e.type_name,
450 scope: e.scope,
451 })
452 })
453 .collect()
454 }
455
456 pub fn absorb(&mut self, other: ProviderRegistry) {
457 let ProviderRegistry { entries, order } = other;
458 let mut leftover = entries;
459 for type_id in order {
460 if let Some(entry) = leftover.remove(&type_id) {
461 self.insert_entry(type_id, entry);
462 }
463 }
464 for (type_id, entry) in leftover {
465 self.insert_entry(type_id, entry);
466 }
467 }
468
469 pub fn absorb_exported(&mut self, mut other: ProviderRegistry, exported: &[TypeId]) {
470 if exported.is_empty() {
471 return;
472 }
473 let allow = exported.iter().copied().collect::<HashSet<_>>();
474 for type_id in std::mem::take(&mut other.order) {
476 if allow.contains(&type_id) {
477 if let Some(entry) = other.entries.remove(&type_id) {
478 self.insert_entry(type_id, entry);
479 }
480 }
481 }
482 }
483
484 pub fn absorb_exported_from(&mut self, other: &ProviderRegistry, exported: &[TypeId]) {
487 if exported.is_empty() {
488 return;
489 }
490 let allow = exported.iter().copied().collect::<HashSet<_>>();
491 for type_id in &other.order {
492 if allow.contains(type_id) {
493 if let Some(entry) = other.entries.get(type_id) {
494 self.insert_entry(*type_id, entry.clone());
495 }
496 }
497 }
498 }
499
500 pub fn eager_init_singletons(&self) {
503 for type_id in &self.order {
504 let Some(entry) = self.entries.get(type_id) else {
505 continue;
506 };
507 if entry.scope == ProviderScope::Singleton {
508 let _guard = ConstructionGuard::push(*type_id, entry.type_name);
509 let _ = entry.instance.get_or_init(|| match &entry.factory {
510 ProviderFactory::InjectableFn(f) => f(self),
511 ProviderFactory::Custom(f) => f(self),
512 });
513 }
514 }
515 }
516
517 fn ordered_singletons(&self) -> Vec<TypeId> {
523 let singletons: HashSet<TypeId> = self
524 .order
525 .iter()
526 .filter(|id| {
527 self.entries
528 .get(id)
529 .is_some_and(|e| e.scope == ProviderScope::Singleton)
530 })
531 .copied()
532 .collect();
533
534 let deps = provider_dep_graph().read().expect("provider dep graph");
539 let mut incoming: HashMap<TypeId, usize> =
540 singletons.iter().map(|id| (*id, 0usize)).collect();
541 let mut adjacency: HashMap<TypeId, Vec<TypeId>> = HashMap::new();
542 for (from, targets) in deps.iter() {
543 if !singletons.contains(from) {
544 continue;
545 }
546 for to in targets {
547 if singletons.contains(to) {
548 adjacency.entry(*to).or_default().push(*from);
549 *incoming.entry(*from).or_insert(0) += 1;
550 }
551 }
552 }
553 drop(deps);
554
555 use std::cmp::Reverse;
557 let position: HashMap<&TypeId, usize> = self
558 .order
559 .iter()
560 .enumerate()
561 .map(|(i, id)| (id, i))
562 .collect();
563 let mut ready: std::collections::BinaryHeap<Reverse<usize>> = singletons
564 .iter()
565 .filter(|id| incoming[id] == 0)
566 .map(|id| Reverse(position[id]))
567 .collect();
568
569 let mut sorted = Vec::with_capacity(singletons.len());
570 let mut visited = HashSet::new();
571 while let Some(Reverse(pos)) = ready.pop() {
572 let id = self.order[pos];
573 visited.insert(id);
574 sorted.push(id);
575 if let Some(dependents) = adjacency.get(&id) {
576 for to in dependents {
577 let e = incoming.get_mut(to).expect("edge target tracked");
578 *e -= 1;
579 if *e == 0 && !visited.contains(to) {
580 ready.push(Reverse(position[to]));
581 }
582 }
583 }
584 }
585
586 for id in &self.order {
588 if singletons.contains(id) && !visited.contains(id) {
589 sorted.push(*id);
590 }
591 }
592 sorted
593 }
594
595 pub async fn run_on_module_init(&self) {
596 for type_id in self.ordered_singletons() {
597 if let Some(entry) = self.entries.get(&type_id) {
598 (entry.on_module_init)(self).await;
599 }
600 }
601 }
602
603 pub async fn run_on_module_destroy(&self) {
605 for type_id in self.ordered_singletons().into_iter().rev() {
606 if let Some(entry) = self.entries.get(&type_id) {
607 (entry.on_module_destroy)(self).await;
608 }
609 }
610 }
611
612 pub async fn run_on_application_bootstrap(&self) {
613 for type_id in self.ordered_singletons() {
614 if let Some(entry) = self.entries.get(&type_id) {
615 (entry.on_application_bootstrap)(self).await;
616 }
617 }
618 }
619
620 pub async fn run_on_before_application_shutdown(&self) {
624 for type_id in self.ordered_singletons().into_iter().rev() {
625 if let Some(entry) = self.entries.get(&type_id) {
626 (entry.on_before_application_shutdown)(self).await;
627 }
628 }
629 }
630
631 pub async fn run_on_application_shutdown(&self) {
633 for type_id in self.ordered_singletons().into_iter().rev() {
634 if let Some(entry) = self.entries.get(&type_id) {
635 (entry.on_application_shutdown)(self).await;
636 }
637 }
638 }
639}
640
641impl Clone for ProviderRegistry {
642 fn clone(&self) -> Self {
643 Self {
644 entries: self.entries.clone(),
645 order: self.order.clone(),
646 }
647 }
648}
649
650impl Default for ProviderRegistry {
651 fn default() -> Self {
652 Self::new()
653 }
654}
655
656type HookFuture<'a> = Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
657type HookFn = for<'a> fn(&'a ProviderRegistry) -> HookFuture<'a>;
658
659fn hook_on_module_init<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
660where
661 T: Injectable + Send + Sync + 'static,
662{
663 Box::pin(async move {
664 let v = registry.get::<T>();
665 v.on_module_init().await;
666 })
667}
668
669fn hook_on_module_destroy<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
670where
671 T: Injectable + Send + Sync + 'static,
672{
673 Box::pin(async move {
674 let v = registry.get::<T>();
675 v.on_module_destroy().await;
676 })
677}
678
679fn hook_on_application_bootstrap<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
680where
681 T: Injectable + Send + Sync + 'static,
682{
683 Box::pin(async move {
684 let v = registry.get::<T>();
685 v.on_application_bootstrap().await;
686 })
687}
688
689fn hook_on_before_application_shutdown<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
690where
691 T: Injectable + Send + Sync + 'static,
692{
693 Box::pin(async move {
694 let v = registry.get::<T>();
695 v.on_before_application_shutdown().await;
696 })
697}
698
699fn hook_on_application_shutdown<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
700where
701 T: Injectable + Send + Sync + 'static,
702{
703 Box::pin(async move {
704 let v = registry.get::<T>();
705 v.on_application_shutdown().await;
706 })
707}
708
709fn lifecycle_on_module_init<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
712where
713 T: ProviderLifecycle + Send + Sync + 'static,
714{
715 Box::pin(async move {
716 let v = registry.get::<T>();
717 v.on_module_init().await;
718 })
719}
720
721fn lifecycle_on_module_destroy<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
722where
723 T: ProviderLifecycle + Send + Sync + 'static,
724{
725 Box::pin(async move {
726 let v = registry.get::<T>();
727 v.on_module_destroy().await;
728 })
729}
730
731fn lifecycle_on_application_bootstrap<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
732where
733 T: ProviderLifecycle + Send + Sync + 'static,
734{
735 Box::pin(async move {
736 let v = registry.get::<T>();
737 v.on_application_bootstrap().await;
738 })
739}
740
741fn lifecycle_on_before_application_shutdown<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
742where
743 T: ProviderLifecycle + Send + Sync + 'static,
744{
745 Box::pin(async move {
746 let v = registry.get::<T>();
747 v.on_before_application_shutdown().await;
748 })
749}
750
751fn lifecycle_on_application_shutdown<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
752where
753 T: ProviderLifecycle + Send + Sync + 'static,
754{
755 Box::pin(async move {
756 let v = registry.get::<T>();
757 v.on_application_shutdown().await;
758 })
759}
760
761#[async_trait]
771pub trait Injectable: Send + Sync + 'static {
772 fn construct(registry: &ProviderRegistry) -> Arc<Self>;
773
774 fn scope() -> ProviderScope {
776 ProviderScope::Singleton
777 }
778
779 async fn on_module_init(&self) {}
780 async fn on_module_destroy(&self) {}
781 async fn on_application_bootstrap(&self) {}
782 async fn on_before_application_shutdown(&self) {}
786 async fn on_application_shutdown(&self) {}
787}
788
789#[async_trait]
806pub trait ProviderLifecycle: Send + Sync + 'static {
807 async fn on_module_init(&self) {}
808 async fn on_module_destroy(&self) {}
809 async fn on_application_bootstrap(&self) {}
810 async fn on_before_application_shutdown(&self) {}
814 async fn on_application_shutdown(&self) {}
815}
816
817pub trait Controller {
818 fn register(router: Router, registry: &ProviderRegistry) -> Router;
819}
820
821pub trait Module {
822 fn build() -> (ProviderRegistry, Router);
823
824 fn exports() -> Vec<TypeId> {
825 Vec::new()
826 }
827}
828
829pub trait ModuleGraph {
835 fn register_providers(registry: &mut ProviderRegistry);
836 fn register_controllers(router: Router, registry: &ProviderRegistry) -> Router;
837}
838
839pub struct DynamicModule {
847 pub registry: ProviderRegistry,
849 pub router: Router,
850 pub exports: Vec<TypeId>,
852}
853
854impl DynamicModule {
855 pub fn from_module<M: Module>() -> Self {
857 let (registry, router) = M::build();
858 let exports = <M as Module>::exports();
859 Self {
860 registry,
861 router,
862 exports,
863 }
864 }
865
866 pub fn from_router(router: Router) -> Self {
868 Self {
869 registry: ProviderRegistry::new(),
870 router,
871 exports: Vec::new(),
872 }
873 }
874
875 pub fn from_parts(registry: ProviderRegistry, router: Router, exports: Vec<TypeId>) -> Self {
877 Self {
878 registry,
879 router,
880 exports,
881 }
882 }
883
884 pub fn lazy<M: Module + 'static>() -> Self {
887 static CELL: std::sync::OnceLock<DynamicModule> = std::sync::OnceLock::new();
888 CELL.get_or_init(DynamicModule::from_module::<M>).clone()
889 }
890}
891
892impl Clone for DynamicModule {
893 fn clone(&self) -> Self {
894 Self {
895 registry: self.registry.clone(),
896 router: self.router.clone(),
897 exports: self.exports.clone(),
898 }
899 }
900}
901
902pub struct ModuleOptions<O, M> {
907 inner: O,
908 _marker: std::marker::PhantomData<fn() -> M>,
909}
910
911impl<O, M> ModuleOptions<O, M> {
912 pub fn new(inner: O) -> Self {
913 Self {
914 inner,
915 _marker: std::marker::PhantomData,
916 }
917 }
918
919 pub fn get(&self) -> &O {
920 &self.inner
921 }
922
923 pub fn into_inner(self) -> O {
924 self.inner
925 }
926}
927
928impl<O, M> std::ops::Deref for ModuleOptions<O, M> {
929 type Target = O;
930
931 fn deref(&self) -> &Self::Target {
932 &self.inner
933 }
934}
935
936#[async_trait]
937impl<O, M> Injectable for ModuleOptions<O, M>
938where
939 O: Send + Sync + 'static,
940 M: 'static,
941{
942 fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
943 panic!(
944 "ModuleOptions requested but no value was provided. Use ConfigurableModuleBuilder / DynamicModuleBuilder to supply module options."
945 );
946 }
947}
948
949type RegistryOverrideFn = Box<dyn FnOnce(&mut ProviderRegistry) + Send>;
950
951pub struct DynamicModuleBuilder<M>
954where
955 M: Module + ModuleGraph,
956{
957 overrides: Vec<RegistryOverrideFn>,
958 _marker: std::marker::PhantomData<M>,
959}
960
961impl<M> DynamicModuleBuilder<M>
962where
963 M: Module + ModuleGraph,
964{
965 pub fn new() -> Self {
966 Self {
967 overrides: Vec::new(),
968 _marker: std::marker::PhantomData,
969 }
970 }
971
972 pub fn override_provider<T>(mut self, instance: Arc<T>) -> Self
973 where
974 T: Injectable + Send + Sync + 'static,
975 {
976 self.overrides
977 .push(Box::new(move |r| r.override_provider::<T>(instance)));
978 self
979 }
980
981 pub fn build(self) -> DynamicModule {
982 let mut registry = ProviderRegistry::new();
983 M::register_providers(&mut registry);
984 for apply in self.overrides {
985 apply(&mut registry);
986 }
987 let router = M::register_controllers(Router::new(), ®istry);
988 DynamicModule::from_parts(registry, router, M::exports())
989 }
990}
991
992impl<M> Default for DynamicModuleBuilder<M>
993where
994 M: Module + ModuleGraph,
995{
996 fn default() -> Self {
997 Self::new()
998 }
999}
1000
1001pub struct ConfigurableModuleBuilder<O> {
1003 _marker: std::marker::PhantomData<O>,
1004}
1005
1006impl<O> ConfigurableModuleBuilder<O>
1007where
1008 O: Send + Sync + 'static,
1009{
1010 pub fn for_root<M>(options: O) -> DynamicModule
1011 where
1012 M: Module + ModuleGraph + 'static,
1013 {
1014 DynamicModuleBuilder::<M>::new()
1015 .override_provider::<ModuleOptions<O, M>>(Arc::new(ModuleOptions::new(options)))
1016 .build()
1017 }
1018
1019 pub async fn for_root_async<M, F, Fut>(factory: F) -> DynamicModule
1020 where
1021 M: Module + ModuleGraph + 'static,
1022 F: FnOnce() -> Fut,
1023 Fut: Future<Output = O>,
1024 {
1025 let options = factory().await;
1026 Self::for_root::<M>(options)
1027 }
1028}
1029
1030thread_local! {
1031 static MODULE_BUILD_STACK: std::cell::RefCell<Vec<(&'static str, TypeId)>> =
1032 const { std::cell::RefCell::new(Vec::new()) };
1033}
1034
1035#[doc(hidden)]
1037pub struct __NestrsModuleBuildGuard {
1038 type_id: TypeId,
1039}
1040
1041impl __NestrsModuleBuildGuard {
1042 pub fn push(type_id: TypeId, type_name: &'static str) -> Self {
1043 let is_cycle = MODULE_BUILD_STACK.with(|stack| {
1044 let mut guard = stack.borrow_mut();
1045 let cycle = guard.iter().any(|(_, id)| *id == type_id);
1046 if !cycle {
1047 guard.push((type_name, type_id));
1048 }
1049 cycle
1050 });
1051
1052 if is_cycle {
1053 __nestrs_panic_circular_module_dependency(type_name);
1054 }
1055
1056 Self { type_id }
1057 }
1058}
1059
1060impl Drop for __NestrsModuleBuildGuard {
1061 fn drop(&mut self) {
1062 MODULE_BUILD_STACK.with(|stack| {
1063 let mut guard = stack.borrow_mut();
1064 if let Some((_, id)) = guard.last() {
1065 if *id == self.type_id {
1066 guard.pop();
1067 }
1068 }
1069 });
1070 }
1071}
1072
1073#[doc(hidden)]
1074pub fn __nestrs_module_stack_contains(type_id: TypeId) -> bool {
1075 MODULE_BUILD_STACK.with(|stack| stack.borrow().iter().any(|(_, id)| *id == type_id))
1076}
1077
1078#[doc(hidden)]
1079pub fn __nestrs_panic_circular_module_dependency(import_type_name: &'static str) -> ! {
1080 let chain = MODULE_BUILD_STACK.with(|stack| {
1081 stack
1082 .borrow()
1083 .iter()
1084 .map(|(name, _)| *name)
1085 .chain(std::iter::once(import_type_name))
1086 .collect::<Vec<_>>()
1087 .join(" -> ")
1088 });
1089
1090 panic!(
1091 "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`).",
1092 );
1093}
1094
1095tokio::task_local! {
1096 static REQUEST_SCOPE_CACHE: std::cell::RefCell<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>;
1097}
1098
1099pub async fn with_request_scope<Fut, T>(future: Fut) -> T
1114where
1115 Fut: std::future::Future<Output = T>,
1116{
1117 if REQUEST_SCOPE_CACHE.try_with(|_| ()).is_ok() {
1120 return future.await;
1121 }
1122 REQUEST_SCOPE_CACHE
1123 .scope(std::cell::RefCell::new(HashMap::new()), future)
1124 .await
1125}
1126
1127pub fn spawn_with_request_scope<F>(future: F) -> tokio::task::JoinHandle<F::Output>
1154where
1155 F: std::future::Future + Send + 'static,
1156 F::Output: Send + 'static,
1157{
1158 let snapshot = REQUEST_SCOPE_CACHE
1159 .try_with(|cell| cell.borrow().clone())
1160 .ok();
1161 tokio::spawn(async move {
1162 match snapshot {
1163 Some(map) => {
1164 REQUEST_SCOPE_CACHE
1165 .scope(std::cell::RefCell::new(map), future)
1166 .await
1167 }
1168 None => with_request_scope(future).await,
1169 }
1170 })
1171}
1172
1173pub fn request_scope_get(type_id: TypeId) -> Option<Arc<dyn Any + Send + Sync>> {
1177 REQUEST_SCOPE_CACHE
1178 .try_with(|c| c.borrow().get(&type_id).cloned())
1179 .ok()
1180 .flatten()
1181}
1182
1183pub fn request_scope_insert(type_id: TypeId, value: Arc<dyn Any + Send + Sync>) {
1186 let _ = REQUEST_SCOPE_CACHE.try_with(|c| {
1187 c.borrow_mut().insert(type_id, value);
1188 });
1189}
1190
1191tokio::task_local! {
1203 static ABILITY_SLOT: std::cell::RefCell<Option<Arc<dyn Any + Send + Sync>>>;
1204}
1205
1206pub async fn with_ability_erased<F, T>(ability: Arc<dyn Any + Send + Sync>, future: F) -> T
1213where
1214 F: std::future::Future<Output = T>,
1215{
1216 ABILITY_SLOT
1217 .scope(std::cell::RefCell::new(Some(ability)), future)
1218 .await
1219}
1220
1221pub fn current_ability_erased() -> Option<Arc<dyn Any + Send + Sync>> {
1225 ABILITY_SLOT.try_with(|c| c.borrow().clone()).ok().flatten()
1226}
1227
1228tokio::task_local! {
1240 static PRINCIPAL_SLOT: std::cell::RefCell<Option<Arc<dyn Any + Send + Sync>>>;
1241}
1242
1243pub async fn with_principal_erased<F, T>(principal: Arc<dyn Any + Send + Sync>, future: F) -> T
1251where
1252 F: std::future::Future<Output = T>,
1253{
1254 PRINCIPAL_SLOT
1255 .scope(std::cell::RefCell::new(Some(principal)), future)
1256 .await
1257}
1258
1259pub fn current_principal_erased() -> Option<Arc<dyn Any + Send + Sync>> {
1263 PRINCIPAL_SLOT
1264 .try_with(|c| c.borrow().clone())
1265 .ok()
1266 .flatten()
1267}
1268
1269thread_local! {
1270 static CONSTRUCTION_STACK: std::cell::RefCell<Vec<(&'static str, TypeId)>> =
1271 const { std::cell::RefCell::new(Vec::new()) };
1272}
1273
1274struct ConstructionGuard {
1275 type_id: TypeId,
1276}
1277
1278impl ConstructionGuard {
1279 fn push(type_id: TypeId, type_name: &'static str) -> Self {
1280 CONSTRUCTION_STACK.with(|stack| {
1281 let mut guard = stack.borrow_mut();
1282 if guard.iter().any(|(_, id)| *id == type_id) {
1283 let chain = guard
1284 .iter()
1285 .map(|(name, _)| *name)
1286 .chain(std::iter::once(type_name))
1287 .collect::<Vec<_>>()
1288 .join(" -> ");
1289 panic!(
1290 "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`)."
1291 );
1292 }
1293 guard.push((type_name, type_id));
1294 });
1295 Self { type_id }
1296 }
1297}
1298
1299impl Drop for ConstructionGuard {
1300 fn drop(&mut self) {
1301 CONSTRUCTION_STACK.with(|stack| {
1302 let mut guard = stack.borrow_mut();
1303 if let Some((_, id)) = guard.last() {
1304 if *id == self.type_id {
1305 guard.pop();
1306 }
1307 }
1308 });
1309 }
1310}
1311
1312fn provider_dep_graph() -> &'static RwLock<HashMap<TypeId, Vec<TypeId>>> {
1315 static DEPS: OnceLock<RwLock<HashMap<TypeId, Vec<TypeId>>>> = OnceLock::new();
1316 DEPS.get_or_init(|| RwLock::new(HashMap::new()))
1317}
1318
1319fn record_provider_dependency(from: TypeId, to: TypeId) {
1320 {
1326 let deps = provider_dep_graph().read().expect("provider dep graph");
1327 if let Some(targets) = deps.get(&from) {
1328 if targets.contains(&to) {
1329 return;
1330 }
1331 }
1332 }
1333 let mut deps = provider_dep_graph().write().expect("provider dep graph");
1334 let targets = deps.entry(from).or_default();
1335 if !targets.contains(&to) {
1336 targets.push(to);
1337 }
1338}
1339
1340#[cfg(feature = "test-hooks")]
1344pub fn clear_provider_dependencies_for_tests() {
1345 provider_dep_graph()
1346 .write()
1347 .expect("provider dep graph")
1348 .clear();
1349}
1350
1351type ModuleBuildFn = Box<dyn FnOnce() -> (ProviderRegistry, Router) + Send>;
1352
1353static MODULE_BUILD_CACHE: OnceLock<RwLock<HashMap<TypeId, Arc<OnceLock<DynamicModule>>>>> =
1354 OnceLock::new();
1355
1356fn module_build_cache() -> &'static RwLock<HashMap<TypeId, Arc<OnceLock<DynamicModule>>>> {
1357 MODULE_BUILD_CACHE.get_or_init(|| RwLock::new(HashMap::new()))
1358}
1359
1360#[doc(hidden)]
1374pub fn __nestrs_memoize_module_build<M: Module + 'static>(
1375 build: ModuleBuildFn,
1376) -> (ProviderRegistry, Router) {
1377 let key = TypeId::of::<M>();
1378 let entry = Arc::clone(
1379 module_build_cache()
1380 .write()
1381 .expect("module build cache")
1382 .entry(key)
1383 .or_insert_with(|| Arc::new(OnceLock::new())),
1384 );
1385 let dm = entry.get_or_init(|| {
1389 let (registry, router) = build();
1390 DynamicModule::from_parts(registry, router, <M as Module>::exports())
1391 });
1392 (dm.registry.clone(), dm.router.clone())
1393}
1394
1395#[cfg(feature = "test-hooks")]
1400pub fn clear_module_cache_for_tests() {
1401 module_build_cache()
1402 .write()
1403 .expect("module build cache")
1404 .clear();
1405}
1406
1407#[cfg(test)]
1408mod request_scope_tests {
1409 use super::*;
1418 use std::sync::atomic::{AtomicUsize, Ordering};
1419
1420 struct Marker;
1421 struct OtherMarker;
1422
1423 #[tokio::test]
1424 async fn nested_with_request_scope_joins_the_outer_scope() {
1425 with_request_scope(async {
1426 request_scope_insert(
1427 TypeId::of::<Marker>(),
1428 Arc::new(Marker) as Arc<dyn Any + Send + Sync>,
1429 );
1430
1431 with_request_scope(async {
1434 assert!(
1435 request_scope_get(TypeId::of::<Marker>()).is_some(),
1436 "nested with_request_scope must not hide outer values"
1437 );
1438 request_scope_insert(
1440 TypeId::of::<OtherMarker>(),
1441 Arc::new(OtherMarker) as Arc<dyn Any + Send + Sync>,
1442 );
1443 })
1444 .await;
1445
1446 assert!(
1448 request_scope_get(TypeId::of::<OtherMarker>()).is_some(),
1449 "nested inserts must land in the active (outer) scope"
1450 );
1451 })
1452 .await;
1453 }
1454
1455 #[tokio::test]
1456 async fn with_request_scope_opens_a_fresh_scope_when_none_is_active() {
1457 assert!(request_scope_get(TypeId::of::<Marker>()).is_none());
1460 with_request_scope(async {
1462 request_scope_insert(
1463 TypeId::of::<Marker>(),
1464 Arc::new(Marker) as Arc<dyn Any + Send + Sync>,
1465 );
1466 assert!(request_scope_get(TypeId::of::<Marker>()).is_some());
1467 })
1468 .await;
1469 }
1470
1471 static REQUEST_PROVIDER_CONSTRUCTIONS: AtomicUsize = AtomicUsize::new(0);
1472
1473 struct CountedRequestService;
1474
1475 impl Injectable for CountedRequestService {
1476 fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
1477 REQUEST_PROVIDER_CONSTRUCTIONS.fetch_add(1, Ordering::SeqCst);
1478 Arc::new(Self)
1479 }
1480
1481 fn scope() -> ProviderScope {
1482 ProviderScope::Request
1483 }
1484 }
1485
1486 static SNAPSHOT_CONSTRUCTIONS: AtomicUsize = AtomicUsize::new(0);
1490
1491 struct SnapshottedService;
1492
1493 impl Injectable for SnapshottedService {
1494 fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
1495 SNAPSHOT_CONSTRUCTIONS.fetch_add(1, Ordering::SeqCst);
1496 Arc::new(Self)
1497 }
1498
1499 fn scope() -> ProviderScope {
1500 ProviderScope::Request
1501 }
1502 }
1503
1504 static OFF_SCOPE_CONSTRUCTIONS: AtomicUsize = AtomicUsize::new(0);
1505
1506 struct OffScopeService;
1507
1508 impl Injectable for OffScopeService {
1509 fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
1510 OFF_SCOPE_CONSTRUCTIONS.fetch_add(1, Ordering::SeqCst);
1511 Arc::new(Self)
1512 }
1513
1514 fn scope() -> ProviderScope {
1515 ProviderScope::Request
1516 }
1517 }
1518
1519 #[tokio::test]
1520 async fn nested_scope_does_not_rebuild_request_providers() {
1521 REQUEST_PROVIDER_CONSTRUCTIONS.store(0, Ordering::SeqCst);
1526 let mut registry = ProviderRegistry::new();
1527 registry.register::<CountedRequestService>();
1528
1529 with_request_scope(async {
1530 let outer: Arc<CountedRequestService> = registry.get();
1531
1532 let inner: Arc<CountedRequestService> =
1533 with_request_scope(async { registry.get::<CountedRequestService>() }).await;
1534
1535 assert!(
1536 Arc::ptr_eq(&outer, &inner),
1537 "nested with_request_scope must reuse the request-scoped instance"
1538 );
1539 assert_eq!(
1540 REQUEST_PROVIDER_CONSTRUCTIONS.load(Ordering::SeqCst),
1541 1,
1542 "one construction per request, not one per nested scope"
1543 );
1544 })
1545 .await;
1546 }
1547
1548 #[tokio::test]
1556 async fn try_get_returns_none_off_scope_instead_of_panicking() {
1557 let mut registry = ProviderRegistry::new();
1558 registry.register::<CountedRequestService>();
1559
1560 assert!(
1563 registry.try_get::<CountedRequestService>().is_none(),
1564 "off-scope Request-scoped resolution is absence, not a panic"
1565 );
1566 }
1567
1568 #[test]
1569 #[should_panic(expected = "spawn_with_request_scope")]
1570 fn get_off_scope_panics_with_the_fix_in_the_message() {
1571 let mut registry = ProviderRegistry::new();
1572 registry.register::<CountedRequestService>();
1573
1574 let _ = registry.get::<CountedRequestService>();
1575 }
1576
1577 #[tokio::test]
1578 async fn spawned_task_sees_the_request_snapshot() {
1579 SNAPSHOT_CONSTRUCTIONS.store(0, Ordering::SeqCst);
1580 let mut registry = ProviderRegistry::new();
1581 registry.register::<SnapshottedService>();
1582
1583 with_request_scope(async {
1584 let outer: Arc<SnapshottedService> = registry.get();
1585
1586 let registry = registry.clone();
1587 let handle = spawn_with_request_scope(async move {
1588 let child: Arc<SnapshottedService> = registry.get();
1589 Arc::ptr_eq(&outer, &child)
1590 });
1591
1592 assert!(
1593 handle.await.unwrap(),
1594 "spawned task must resolve the instance the request had at spawn time"
1595 );
1596 assert_eq!(
1597 SNAPSHOT_CONSTRUCTIONS.load(Ordering::SeqCst),
1598 1,
1599 "snapshot must carry the instance, not re-construct it"
1600 );
1601 })
1602 .await;
1603 }
1604
1605 #[tokio::test]
1606 async fn spawned_task_writes_stay_in_the_child() {
1607 with_request_scope(async {
1608 let handle = spawn_with_request_scope(async {
1609 request_scope_insert(
1610 TypeId::of::<OtherMarker>(),
1611 Arc::new(OtherMarker) as Arc<dyn Any + Send + Sync>,
1612 );
1613 assert!(
1614 request_scope_get(TypeId::of::<OtherMarker>()).is_some(),
1615 "the child sees its own inserts"
1616 );
1617 });
1618 handle.await.unwrap();
1619
1620 assert!(
1621 request_scope_get(TypeId::of::<OtherMarker>()).is_none(),
1622 "child-scope writes must not leak into the parent request scope"
1623 );
1624 })
1625 .await;
1626 }
1627
1628 #[tokio::test]
1629 async fn spawning_off_scope_gives_each_child_a_fresh_scope() {
1630 OFF_SCOPE_CONSTRUCTIONS.store(0, Ordering::SeqCst);
1631 let mut registry = ProviderRegistry::new();
1632 registry.register::<OffScopeService>();
1633
1634 let a = {
1637 let registry = registry.clone();
1638 spawn_with_request_scope(async move { registry.get::<OffScopeService>() })
1639 };
1640 let b = {
1641 let registry = registry.clone();
1642 spawn_with_request_scope(async move { registry.get::<OffScopeService>() })
1643 };
1644
1645 let a = a.await.unwrap();
1646 let b = b.await.unwrap();
1647 assert!(
1648 !Arc::ptr_eq(&a, &b),
1649 "off-scope spawns must not share request-scoped instances"
1650 );
1651 assert_eq!(
1652 OFF_SCOPE_CONSTRUCTIONS.load(Ordering::SeqCst),
1653 2,
1654 "one construction per spawned scope"
1655 );
1656 }
1657}
1658
1659#[cfg(test)]
1660mod provider_lifecycle_tests {
1661 use super::*;
1668 use std::sync::Mutex;
1669
1670 type Log = Arc<Mutex<Vec<String>>>;
1671
1672 fn assert_log(log: &Log, expected: &[&str]) {
1673 let got = log.lock().unwrap();
1674 let expected: Vec<String> = expected.iter().map(|s| s.to_string()).collect();
1675 assert_eq!(*got, expected, "hook firing order");
1676 }
1677
1678 struct Tagged<const TAG: char> {
1682 log: Log,
1683 }
1684
1685 impl<const TAG: char> Tagged<TAG> {
1686 fn record(&self, event: &str) {
1687 self.log.lock().unwrap().push(format!("{TAG}:{event}"));
1688 }
1689 }
1690
1691 #[async_trait]
1692 impl<const TAG: char> ProviderLifecycle for Tagged<TAG> {
1693 async fn on_module_init(&self) {
1694 self.record("init");
1695 }
1696 async fn on_module_destroy(&self) {
1697 self.record("destroy");
1698 }
1699 async fn on_application_bootstrap(&self) {
1700 self.record("bootstrap");
1701 }
1702 async fn on_before_application_shutdown(&self) {
1703 self.record("before_shutdown");
1704 }
1705 async fn on_application_shutdown(&self) {
1706 self.record("shutdown");
1707 }
1708 }
1709
1710 #[tokio::test]
1711 async fn use_value_lifecycle_hooks_fire_in_framework_order() {
1712 let log: Log = Arc::default();
1713 let mut registry = ProviderRegistry::new();
1714 registry.register_use_value_with_lifecycle(Arc::new(Tagged::<'V'> { log: log.clone() }));
1715
1716 registry.run_on_module_init().await;
1717 registry.run_on_application_bootstrap().await;
1718 registry.run_on_before_application_shutdown().await;
1719 registry.run_on_application_shutdown().await;
1720 registry.run_on_module_destroy().await;
1721
1722 assert_log(
1723 &log,
1724 &[
1725 "V:init",
1726 "V:bootstrap",
1727 "V:before_shutdown",
1728 "V:shutdown",
1729 "V:destroy",
1730 ],
1731 );
1732 }
1733
1734 #[tokio::test]
1735 async fn lifecycle_hooks_register_order_init_reverse_destroy() {
1736 let log: Log = Arc::default();
1737 let mut registry = ProviderRegistry::new();
1738 registry.register_use_value_with_lifecycle(Arc::new(Tagged::<'A'> { log: log.clone() }));
1739 registry.register_use_value_with_lifecycle(Arc::new(Tagged::<'B'> { log: log.clone() }));
1740
1741 registry.run_on_module_init().await;
1742 registry.run_on_module_destroy().await;
1743
1744 assert_log(&log, &["A:init", "B:init", "B:destroy", "A:destroy"]);
1747 }
1748
1749 #[tokio::test]
1750 async fn use_factory_lifecycle_hooks_fire_on_the_lazily_built_singleton() {
1751 let log: Log = Arc::default();
1752 let mut registry = ProviderRegistry::new();
1753 registry.register_use_factory_with_lifecycle(ProviderScope::Singleton, {
1754 let log = log.clone();
1755 move |_r| {
1756 log.lock().unwrap().push("F:construct".to_string());
1757 Arc::new(Tagged::<'F'> { log: log.clone() })
1758 }
1759 });
1760
1761 registry.run_on_module_init().await;
1765 assert_log(&log, &["F:construct", "F:init"]);
1766
1767 let _v: Arc<Tagged<'F'>> = registry.get();
1769 assert_log(&log, &["F:construct", "F:init"]);
1770 }
1771
1772 #[tokio::test]
1773 async fn plain_use_value_and_use_factory_stay_hook_less() {
1774 let log: Log = Arc::default();
1779 let mut registry = ProviderRegistry::new();
1780 registry.register_use_value(Arc::new(Tagged::<'V'> { log: log.clone() }));
1781 registry.register_use_factory(ProviderScope::Singleton, {
1782 let log = log.clone();
1783 move |_r| Arc::new(Tagged::<'G'> { log: log.clone() })
1784 });
1785
1786 registry.run_on_module_init().await;
1787 registry.run_on_application_bootstrap().await;
1788 registry.run_on_before_application_shutdown().await;
1789 registry.run_on_application_shutdown().await;
1790 registry.run_on_module_destroy().await;
1791
1792 assert_log(&log, &[]);
1793 }
1794}
1795
1796#[cfg(test)]
1797mod provider_dep_graph_tests {
1798 use super::*;
1807
1808 struct FromA;
1809 struct ToB;
1810
1811 #[test]
1812 fn record_provider_dependency_is_idempotent_on_repeated_edges() {
1813 let from = TypeId::of::<FromA>();
1814 let to = TypeId::of::<ToB>();
1815 record_provider_dependency(from, to);
1816 record_provider_dependency(from, to);
1819 record_provider_dependency(from, to);
1820 let deps = provider_dep_graph().read().expect("provider dep graph");
1821 let targets = deps.get(&from).expect("edge recorded");
1822 assert_eq!(targets.len(), 1, "repeated edges must not duplicate");
1823 assert_eq!(targets[0], to);
1824 }
1825
1826 #[test]
1827 fn record_provider_dependency_dedupes_racing_first_recordings() {
1828 let from = TypeId::of::<FromA>();
1832 let to = TypeId::of::<ToB>();
1833 std::thread::scope(|s| {
1834 for _ in 0..16 {
1835 s.spawn(|| record_provider_dependency(from, to));
1836 }
1837 });
1838 let deps = provider_dep_graph().read().expect("provider dep graph");
1839 let targets = deps.get(&from).expect("edge recorded");
1840 assert_eq!(
1841 targets.len(),
1842 1,
1843 "racing recorders must not duplicate an edge"
1844 );
1845 }
1846}
1847
1848#[cfg(test)]
1849mod override_provider_tests {
1850 use super::*;
1859
1860 struct SingletonSvc;
1861
1862 impl Injectable for SingletonSvc {
1863 fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
1864 unreachable!("overridden before construction")
1865 }
1866 }
1867
1868 struct RequestSvc;
1869
1870 impl Injectable for RequestSvc {
1871 fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
1872 unreachable!("overridden before construction")
1873 }
1874
1875 fn scope() -> ProviderScope {
1876 ProviderScope::Request
1877 }
1878 }
1879
1880 struct TransientSvc;
1881
1882 impl Injectable for TransientSvc {
1883 fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
1884 unreachable!("overridden before construction")
1885 }
1886
1887 fn scope() -> ProviderScope {
1888 ProviderScope::Transient
1889 }
1890 }
1891
1892 fn declared_scope<T: 'static>(registry: &ProviderRegistry) -> ProviderScope {
1893 let name = std::any::type_name::<T>();
1894 registry
1895 .provider_summaries()
1896 .into_iter()
1897 .find(|s| s.type_name == name)
1898 .map(|s| s.scope)
1899 .unwrap_or_else(|| panic!("{name} not registered"))
1900 }
1901
1902 #[test]
1903 fn singleton_override_returns_the_instance_and_keeps_singleton_scope() {
1904 let mut registry = ProviderRegistry::new();
1905 registry.register::<SingletonSvc>();
1906 let mock = Arc::new(SingletonSvc);
1907 registry.override_provider::<SingletonSvc>(mock.clone());
1908
1909 let resolved = registry.get::<SingletonSvc>();
1910 assert!(Arc::ptr_eq(&resolved, &mock), "override instance served");
1911 assert_eq!(
1912 declared_scope::<SingletonSvc>(®istry),
1913 ProviderScope::Singleton,
1914 "singleton overrides stay singleton"
1915 );
1916 }
1917
1918 #[tokio::test]
1919 async fn request_override_keeps_request_scope_and_resolves_to_the_instance() {
1920 let mut registry = ProviderRegistry::new();
1921 registry.register::<RequestSvc>();
1922 let mock = Arc::new(RequestSvc);
1923 registry.override_provider::<RequestSvc>(mock.clone());
1924
1925 let first = with_request_scope(async {
1928 let a = registry.get::<RequestSvc>();
1929 let b = registry.get::<RequestSvc>();
1930 assert!(Arc::ptr_eq(&a, &b), "one resolution per request");
1931 a
1932 })
1933 .await;
1934 assert!(Arc::ptr_eq(&first, &mock), "override served inside request");
1935
1936 let second = with_request_scope(async { registry.get::<RequestSvc>() }).await;
1941 assert!(
1942 Arc::ptr_eq(&second, &mock),
1943 "override instance shared across requests"
1944 );
1945
1946 assert_eq!(
1947 declared_scope::<RequestSvc>(®istry),
1948 ProviderScope::Request,
1949 "request overrides keep request scope — no silent Singleton coercion"
1950 );
1951 }
1952
1953 #[test]
1954 fn transient_override_keeps_transient_scope_and_resolves_each_time() {
1955 let mut registry = ProviderRegistry::new();
1956 registry.register::<TransientSvc>();
1957 let mock = Arc::new(TransientSvc);
1958 registry.override_provider::<TransientSvc>(mock.clone());
1959
1960 let a = registry.get::<TransientSvc>();
1964 let b = registry.get::<TransientSvc>();
1965 assert!(Arc::ptr_eq(&a, &mock), "first transient resolution served");
1966 assert!(Arc::ptr_eq(&b, &mock), "second transient resolution served");
1967 assert_eq!(
1968 declared_scope::<TransientSvc>(®istry),
1969 ProviderScope::Transient,
1970 "transient overrides keep transient scope"
1971 );
1972 }
1973}