use std::any::{Any, TypeId};
use std::collections::{HashMap, HashSet};
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::OnceLock;
use std::sync::RwLock;
use async_trait::async_trait;
use axum::Router;
mod admin_snapshot;
mod database;
mod discovery;
mod execution_context;
mod guard;
mod metadata;
mod module_ref;
mod pipe;
mod platform;
mod route_registry;
mod strategy;
mod trace;
pub use admin_snapshot::AdminSnapshot;
pub use database::DatabasePing;
pub use discovery::DiscoveryService;
pub use execution_context::{ExecutionContext, HostType, HttpExecutionArguments};
pub use guard::{CanActivate, GuardError};
pub use metadata::MetadataRegistry;
pub use module_ref::ModuleRef;
pub use pipe::{HttpPipeTransform, PipeTransform};
pub use platform::{AxumHttpEngine, HttpServerEngine};
pub use route_registry::{OpenApiResponseDesc, OpenApiRouteSpec, RouteInfo, RouteRegistry};
pub use strategy::{AuthError, AuthStrategy};
pub use trace::{current_trace_context, parse_traceparent, with_trace_context, TraceContext};
type CustomFactoryFn =
std::sync::Arc<dyn Fn(&ProviderRegistry) -> Arc<dyn Any + Send + Sync> + Send + Sync>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ProviderScope {
Singleton,
Transient,
Request,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ProviderSummary {
pub type_name: &'static str,
pub scope: ProviderScope,
}
#[derive(Clone)]
enum ProviderFactory {
InjectableFn(fn(&ProviderRegistry) -> Arc<dyn Any + Send + Sync>),
Custom(CustomFactoryFn),
}
#[derive(Clone)]
struct ProviderEntry {
type_name: &'static str,
scope: ProviderScope,
factory: ProviderFactory,
instance: Arc<OnceLock<Arc<dyn Any + Send + Sync>>>,
on_module_init: HookFn,
on_module_destroy: HookFn,
on_application_bootstrap: HookFn,
on_before_application_shutdown: HookFn,
on_application_shutdown: HookFn,
}
fn noop_hook<'a>(_registry: &'a ProviderRegistry) -> HookFuture<'a> {
Box::pin(async {})
}
fn create_entry_for_injectable<T: Injectable + Send + Sync + 'static>() -> ProviderEntry {
fn factory<T: Injectable + Send + Sync + 'static>(
registry: &ProviderRegistry,
) -> Arc<dyn Any + Send + Sync> {
T::construct(registry)
}
ProviderEntry {
type_name: std::any::type_name::<T>(),
scope: T::scope(),
factory: ProviderFactory::InjectableFn(factory::<T>),
instance: Arc::new(OnceLock::new()),
on_module_init: hook_on_module_init::<T>,
on_module_destroy: hook_on_module_destroy::<T>,
on_application_bootstrap: hook_on_application_bootstrap::<T>,
on_before_application_shutdown: hook_on_before_application_shutdown::<T>,
on_application_shutdown: hook_on_application_shutdown::<T>,
}
}
pub struct ProviderRegistry {
entries: HashMap<TypeId, ProviderEntry>,
order: Vec<TypeId>,
}
#[derive(Clone, Copy, Debug)]
pub struct HandlerKey(pub &'static str);
impl ProviderRegistry {
pub fn new() -> Self {
Self {
entries: HashMap::new(),
order: Vec::new(),
}
}
fn insert_entry(&mut self, type_id: TypeId, entry: ProviderEntry) {
if !self.entries.contains_key(&type_id) {
self.order.push(type_id);
}
self.entries.insert(type_id, entry);
}
pub fn register<T>(&mut self)
where
T: Injectable + Send + Sync + 'static,
{
self.insert_entry(TypeId::of::<T>(), create_entry_for_injectable::<T>());
}
pub fn register_use_value<T: Send + Sync + 'static>(&mut self, value: Arc<T>) {
let preset: Arc<dyn Any + Send + Sync> = value;
let cell = Arc::new(OnceLock::new());
let _ = cell.set(preset.clone());
self.insert_entry(
TypeId::of::<T>(),
ProviderEntry {
type_name: std::any::type_name::<T>(),
scope: ProviderScope::Singleton,
factory: ProviderFactory::Custom(Arc::new(move |_| preset.clone())),
instance: cell,
on_module_init: noop_hook,
on_module_destroy: noop_hook,
on_application_bootstrap: noop_hook,
on_before_application_shutdown: noop_hook,
on_application_shutdown: noop_hook,
},
);
}
pub fn register_use_factory<T, F>(&mut self, scope: ProviderScope, factory: F)
where
T: Send + Sync + 'static,
F: Fn(&ProviderRegistry) -> Arc<T> + Send + Sync + 'static,
{
let factory: std::sync::Arc<F> = std::sync::Arc::new(factory);
let factory = factory.clone();
self.insert_entry(
TypeId::of::<T>(),
ProviderEntry {
type_name: std::any::type_name::<T>(),
scope,
factory: ProviderFactory::Custom(Arc::new(move |r| {
let v = factory(r);
v as Arc<dyn Any + Send + Sync>
})),
instance: Arc::new(OnceLock::new()),
on_module_init: noop_hook,
on_module_destroy: noop_hook,
on_application_bootstrap: noop_hook,
on_before_application_shutdown: noop_hook,
on_application_shutdown: noop_hook,
},
);
}
pub fn register_use_value_with_lifecycle<T>(&mut self, value: Arc<T>)
where
T: ProviderLifecycle + Send + Sync + 'static,
{
let preset: Arc<dyn Any + Send + Sync> = value;
let cell = Arc::new(OnceLock::new());
let _ = cell.set(preset.clone());
self.insert_entry(
TypeId::of::<T>(),
ProviderEntry {
type_name: std::any::type_name::<T>(),
scope: ProviderScope::Singleton,
factory: ProviderFactory::Custom(Arc::new(move |_| preset.clone())),
instance: cell,
on_module_init: lifecycle_on_module_init::<T>,
on_module_destroy: lifecycle_on_module_destroy::<T>,
on_application_bootstrap: lifecycle_on_application_bootstrap::<T>,
on_before_application_shutdown: lifecycle_on_before_application_shutdown::<T>,
on_application_shutdown: lifecycle_on_application_shutdown::<T>,
},
);
}
pub fn register_use_factory_with_lifecycle<T, F>(&mut self, scope: ProviderScope, factory: F)
where
T: ProviderLifecycle + Send + Sync + 'static,
F: Fn(&ProviderRegistry) -> Arc<T> + Send + Sync + 'static,
{
let factory: std::sync::Arc<F> = std::sync::Arc::new(factory);
let factory = factory.clone();
self.insert_entry(
TypeId::of::<T>(),
ProviderEntry {
type_name: std::any::type_name::<T>(),
scope,
factory: ProviderFactory::Custom(Arc::new(move |r| {
let v = factory(r);
v as Arc<dyn Any + Send + Sync>
})),
instance: Arc::new(OnceLock::new()),
on_module_init: lifecycle_on_module_init::<T>,
on_module_destroy: lifecycle_on_module_destroy::<T>,
on_application_bootstrap: lifecycle_on_application_bootstrap::<T>,
on_before_application_shutdown: lifecycle_on_before_application_shutdown::<T>,
on_application_shutdown: lifecycle_on_application_shutdown::<T>,
},
);
}
#[inline]
pub fn register_use_class<T>(&mut self)
where
T: Injectable + Send + Sync + 'static,
{
self.register::<T>();
}
pub fn override_provider<T>(&mut self, instance: Arc<T>)
where
T: Injectable + Send + Sync + 'static,
{
let preset: Arc<dyn Any + Send + Sync> = instance;
let instance_cell = Arc::new(OnceLock::new());
let _ = instance_cell.set(preset.clone());
let entry = ProviderEntry {
type_name: std::any::type_name::<T>(),
scope: T::scope(),
factory: ProviderFactory::Custom(Arc::new(move |_| preset.clone())),
instance: instance_cell,
on_module_init: hook_on_module_init::<T>,
on_module_destroy: hook_on_module_destroy::<T>,
on_application_bootstrap: hook_on_application_bootstrap::<T>,
on_before_application_shutdown: hook_on_before_application_shutdown::<T>,
on_application_shutdown: hook_on_application_shutdown::<T>,
};
self.insert_entry(TypeId::of::<T>(), entry);
}
fn produce_any(
&self,
type_id: TypeId,
entry: &ProviderEntry,
) -> Option<Arc<dyn Any + Send + Sync>> {
match entry.scope {
ProviderScope::Singleton => {
let _guard = ConstructionGuard::push(type_id, entry.type_name);
Some(
entry
.instance
.get_or_init(|| match &entry.factory {
ProviderFactory::InjectableFn(f) => f(self),
ProviderFactory::Custom(f) => f(self),
})
.clone(),
)
}
ProviderScope::Transient => {
let _guard = ConstructionGuard::push(type_id, entry.type_name);
Some(match &entry.factory {
ProviderFactory::InjectableFn(f) => f(self),
ProviderFactory::Custom(f) => f(self),
})
}
ProviderScope::Request => {
let _guard = ConstructionGuard::push(type_id, entry.type_name);
REQUEST_SCOPE_CACHE
.try_with(|cell| {
if let Some(existing) = cell.borrow().get(&type_id).cloned() {
return existing;
}
let value = match &entry.factory {
ProviderFactory::InjectableFn(f) => f(self),
ProviderFactory::Custom(f) => f(self),
};
cell.borrow_mut().insert(type_id, value.clone());
value
})
.ok()
}
}
}
pub fn get<T>(&self) -> Arc<T>
where
T: Send + Sync + 'static,
{
self.try_get::<T>().unwrap_or_else(|| {
if let Some(entry) = self.entries.get(&TypeId::of::<T>()) {
if matches!(entry.scope, ProviderScope::Request) {
panic!(
"Request-scoped provider `{}` requested outside a request scope; \
enable request scope middleware (`use_request_scope`), spawn \
background work with `spawn_with_request_scope`, or use \
`try_get` to handle absence gracefully",
entry.type_name
);
}
}
panic!("Provider `{}` not registered", std::any::type_name::<T>())
})
}
pub fn try_get<T>(&self) -> Option<Arc<T>>
where
T: Send + Sync + 'static,
{
let type_id = TypeId::of::<T>();
let entry = self.entries.get(&type_id)?;
if let Some(parent) =
CONSTRUCTION_STACK.with(|stack| stack.borrow().last().map(|(_, id)| *id))
{
record_provider_dependency(parent, type_id);
}
let any = self.produce_any(type_id, entry)?;
any.downcast::<T>().ok()
}
pub fn registered_type_ids(&self) -> Vec<TypeId> {
self.order.clone()
}
pub fn registered_type_names(&self) -> Vec<&'static str> {
self.order
.iter()
.filter_map(|id| self.entries.get(id).map(|e| e.type_name))
.collect()
}
pub fn provider_summaries(&self) -> Vec<ProviderSummary> {
self.order
.iter()
.filter_map(|id| {
self.entries.get(id).map(|e| ProviderSummary {
type_name: e.type_name,
scope: e.scope,
})
})
.collect()
}
pub fn absorb(&mut self, other: ProviderRegistry) {
let ProviderRegistry { entries, order } = other;
let mut leftover = entries;
for type_id in order {
if let Some(entry) = leftover.remove(&type_id) {
self.insert_entry(type_id, entry);
}
}
for (type_id, entry) in leftover {
self.insert_entry(type_id, entry);
}
}
pub fn absorb_exported(&mut self, mut other: ProviderRegistry, exported: &[TypeId]) {
if exported.is_empty() {
return;
}
let allow = exported.iter().copied().collect::<HashSet<_>>();
for type_id in std::mem::take(&mut other.order) {
if allow.contains(&type_id) {
if let Some(entry) = other.entries.remove(&type_id) {
self.insert_entry(type_id, entry);
}
}
}
}
pub fn absorb_exported_from(&mut self, other: &ProviderRegistry, exported: &[TypeId]) {
if exported.is_empty() {
return;
}
let allow = exported.iter().copied().collect::<HashSet<_>>();
for type_id in &other.order {
if allow.contains(type_id) {
if let Some(entry) = other.entries.get(type_id) {
self.insert_entry(*type_id, entry.clone());
}
}
}
}
pub fn eager_init_singletons(&self) {
for type_id in &self.order {
let Some(entry) = self.entries.get(type_id) else {
continue;
};
if entry.scope == ProviderScope::Singleton {
let _guard = ConstructionGuard::push(*type_id, entry.type_name);
let _ = entry.instance.get_or_init(|| match &entry.factory {
ProviderFactory::InjectableFn(f) => f(self),
ProviderFactory::Custom(f) => f(self),
});
}
}
}
fn ordered_singletons(&self) -> Vec<TypeId> {
let singletons: HashSet<TypeId> = self
.order
.iter()
.filter(|id| {
self.entries
.get(id)
.is_some_and(|e| e.scope == ProviderScope::Singleton)
})
.copied()
.collect();
let deps = provider_dep_graph().read().expect("provider dep graph");
let mut incoming: HashMap<TypeId, usize> =
singletons.iter().map(|id| (*id, 0usize)).collect();
let mut adjacency: HashMap<TypeId, Vec<TypeId>> = HashMap::new();
for (from, targets) in deps.iter() {
if !singletons.contains(from) {
continue;
}
for to in targets {
if singletons.contains(to) {
adjacency.entry(*to).or_default().push(*from);
*incoming.entry(*from).or_insert(0) += 1;
}
}
}
drop(deps);
use std::cmp::Reverse;
let position: HashMap<&TypeId, usize> = self
.order
.iter()
.enumerate()
.map(|(i, id)| (id, i))
.collect();
let mut ready: std::collections::BinaryHeap<Reverse<usize>> = singletons
.iter()
.filter(|id| incoming[id] == 0)
.map(|id| Reverse(position[id]))
.collect();
let mut sorted = Vec::with_capacity(singletons.len());
let mut visited = HashSet::new();
while let Some(Reverse(pos)) = ready.pop() {
let id = self.order[pos];
visited.insert(id);
sorted.push(id);
if let Some(dependents) = adjacency.get(&id) {
for to in dependents {
let e = incoming.get_mut(to).expect("edge target tracked");
*e -= 1;
if *e == 0 && !visited.contains(to) {
ready.push(Reverse(position[to]));
}
}
}
}
for id in &self.order {
if singletons.contains(id) && !visited.contains(id) {
sorted.push(*id);
}
}
sorted
}
pub async fn run_on_module_init(&self) {
for type_id in self.ordered_singletons() {
if let Some(entry) = self.entries.get(&type_id) {
(entry.on_module_init)(self).await;
}
}
}
pub async fn run_on_module_destroy(&self) {
for type_id in self.ordered_singletons().into_iter().rev() {
if let Some(entry) = self.entries.get(&type_id) {
(entry.on_module_destroy)(self).await;
}
}
}
pub async fn run_on_application_bootstrap(&self) {
for type_id in self.ordered_singletons() {
if let Some(entry) = self.entries.get(&type_id) {
(entry.on_application_bootstrap)(self).await;
}
}
}
pub async fn run_on_before_application_shutdown(&self) {
for type_id in self.ordered_singletons().into_iter().rev() {
if let Some(entry) = self.entries.get(&type_id) {
(entry.on_before_application_shutdown)(self).await;
}
}
}
pub async fn run_on_application_shutdown(&self) {
for type_id in self.ordered_singletons().into_iter().rev() {
if let Some(entry) = self.entries.get(&type_id) {
(entry.on_application_shutdown)(self).await;
}
}
}
}
impl Clone for ProviderRegistry {
fn clone(&self) -> Self {
Self {
entries: self.entries.clone(),
order: self.order.clone(),
}
}
}
impl Default for ProviderRegistry {
fn default() -> Self {
Self::new()
}
}
type HookFuture<'a> = Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
type HookFn = for<'a> fn(&'a ProviderRegistry) -> HookFuture<'a>;
fn hook_on_module_init<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
where
T: Injectable + Send + Sync + 'static,
{
Box::pin(async move {
let v = registry.get::<T>();
v.on_module_init().await;
})
}
fn hook_on_module_destroy<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
where
T: Injectable + Send + Sync + 'static,
{
Box::pin(async move {
let v = registry.get::<T>();
v.on_module_destroy().await;
})
}
fn hook_on_application_bootstrap<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
where
T: Injectable + Send + Sync + 'static,
{
Box::pin(async move {
let v = registry.get::<T>();
v.on_application_bootstrap().await;
})
}
fn hook_on_before_application_shutdown<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
where
T: Injectable + Send + Sync + 'static,
{
Box::pin(async move {
let v = registry.get::<T>();
v.on_before_application_shutdown().await;
})
}
fn hook_on_application_shutdown<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
where
T: Injectable + Send + Sync + 'static,
{
Box::pin(async move {
let v = registry.get::<T>();
v.on_application_shutdown().await;
})
}
fn lifecycle_on_module_init<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
where
T: ProviderLifecycle + Send + Sync + 'static,
{
Box::pin(async move {
let v = registry.get::<T>();
v.on_module_init().await;
})
}
fn lifecycle_on_module_destroy<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
where
T: ProviderLifecycle + Send + Sync + 'static,
{
Box::pin(async move {
let v = registry.get::<T>();
v.on_module_destroy().await;
})
}
fn lifecycle_on_application_bootstrap<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
where
T: ProviderLifecycle + Send + Sync + 'static,
{
Box::pin(async move {
let v = registry.get::<T>();
v.on_application_bootstrap().await;
})
}
fn lifecycle_on_before_application_shutdown<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
where
T: ProviderLifecycle + Send + Sync + 'static,
{
Box::pin(async move {
let v = registry.get::<T>();
v.on_before_application_shutdown().await;
})
}
fn lifecycle_on_application_shutdown<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
where
T: ProviderLifecycle + Send + Sync + 'static,
{
Box::pin(async move {
let v = registry.get::<T>();
v.on_application_shutdown().await;
})
}
#[async_trait]
pub trait Injectable: Send + Sync + 'static {
fn construct(registry: &ProviderRegistry) -> Arc<Self>;
fn scope() -> ProviderScope {
ProviderScope::Singleton
}
async fn on_module_init(&self) {}
async fn on_module_destroy(&self) {}
async fn on_application_bootstrap(&self) {}
async fn on_before_application_shutdown(&self) {}
async fn on_application_shutdown(&self) {}
}
#[async_trait]
pub trait ProviderLifecycle: Send + Sync + 'static {
async fn on_module_init(&self) {}
async fn on_module_destroy(&self) {}
async fn on_application_bootstrap(&self) {}
async fn on_before_application_shutdown(&self) {}
async fn on_application_shutdown(&self) {}
}
pub trait Controller {
fn register(router: Router, registry: &ProviderRegistry) -> Router;
}
pub trait Module {
fn build() -> (ProviderRegistry, Router);
fn exports() -> Vec<TypeId> {
Vec::new()
}
}
pub trait ModuleGraph {
fn register_providers(registry: &mut ProviderRegistry);
fn register_controllers(router: Router, registry: &ProviderRegistry) -> Router;
}
pub struct DynamicModule {
pub registry: ProviderRegistry,
pub router: Router,
pub exports: Vec<TypeId>,
}
impl DynamicModule {
pub fn from_module<M: Module>() -> Self {
let (registry, router) = M::build();
let exports = <M as Module>::exports();
Self {
registry,
router,
exports,
}
}
pub fn from_router(router: Router) -> Self {
Self {
registry: ProviderRegistry::new(),
router,
exports: Vec::new(),
}
}
pub fn from_parts(registry: ProviderRegistry, router: Router, exports: Vec<TypeId>) -> Self {
Self {
registry,
router,
exports,
}
}
pub fn lazy<M: Module + 'static>() -> Self {
static CELL: std::sync::OnceLock<DynamicModule> = std::sync::OnceLock::new();
CELL.get_or_init(DynamicModule::from_module::<M>).clone()
}
}
impl Clone for DynamicModule {
fn clone(&self) -> Self {
Self {
registry: self.registry.clone(),
router: self.router.clone(),
exports: self.exports.clone(),
}
}
}
pub struct ModuleOptions<O, M> {
inner: O,
_marker: std::marker::PhantomData<fn() -> M>,
}
impl<O, M> ModuleOptions<O, M> {
pub fn new(inner: O) -> Self {
Self {
inner,
_marker: std::marker::PhantomData,
}
}
pub fn get(&self) -> &O {
&self.inner
}
pub fn into_inner(self) -> O {
self.inner
}
}
impl<O, M> std::ops::Deref for ModuleOptions<O, M> {
type Target = O;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[async_trait]
impl<O, M> Injectable for ModuleOptions<O, M>
where
O: Send + Sync + 'static,
M: 'static,
{
fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
panic!(
"ModuleOptions requested but no value was provided. Use ConfigurableModuleBuilder / DynamicModuleBuilder to supply module options."
);
}
}
type RegistryOverrideFn = Box<dyn FnOnce(&mut ProviderRegistry) + Send>;
pub struct DynamicModuleBuilder<M>
where
M: Module + ModuleGraph,
{
overrides: Vec<RegistryOverrideFn>,
_marker: std::marker::PhantomData<M>,
}
impl<M> DynamicModuleBuilder<M>
where
M: Module + ModuleGraph,
{
pub fn new() -> Self {
Self {
overrides: Vec::new(),
_marker: std::marker::PhantomData,
}
}
pub fn override_provider<T>(mut self, instance: Arc<T>) -> Self
where
T: Injectable + Send + Sync + 'static,
{
self.overrides
.push(Box::new(move |r| r.override_provider::<T>(instance)));
self
}
pub fn build(self) -> DynamicModule {
let mut registry = ProviderRegistry::new();
M::register_providers(&mut registry);
for apply in self.overrides {
apply(&mut registry);
}
let router = M::register_controllers(Router::new(), ®istry);
DynamicModule::from_parts(registry, router, M::exports())
}
}
impl<M> Default for DynamicModuleBuilder<M>
where
M: Module + ModuleGraph,
{
fn default() -> Self {
Self::new()
}
}
pub struct ConfigurableModuleBuilder<O> {
_marker: std::marker::PhantomData<O>,
}
impl<O> ConfigurableModuleBuilder<O>
where
O: Send + Sync + 'static,
{
pub fn for_root<M>(options: O) -> DynamicModule
where
M: Module + ModuleGraph + 'static,
{
DynamicModuleBuilder::<M>::new()
.override_provider::<ModuleOptions<O, M>>(Arc::new(ModuleOptions::new(options)))
.build()
}
pub async fn for_root_async<M, F, Fut>(factory: F) -> DynamicModule
where
M: Module + ModuleGraph + 'static,
F: FnOnce() -> Fut,
Fut: Future<Output = O>,
{
let options = factory().await;
Self::for_root::<M>(options)
}
}
thread_local! {
static MODULE_BUILD_STACK: std::cell::RefCell<Vec<(&'static str, TypeId)>> =
const { std::cell::RefCell::new(Vec::new()) };
}
#[doc(hidden)]
pub struct __NestrsModuleBuildGuard {
type_id: TypeId,
}
impl __NestrsModuleBuildGuard {
pub fn push(type_id: TypeId, type_name: &'static str) -> Self {
let is_cycle = MODULE_BUILD_STACK.with(|stack| {
let mut guard = stack.borrow_mut();
let cycle = guard.iter().any(|(_, id)| *id == type_id);
if !cycle {
guard.push((type_name, type_id));
}
cycle
});
if is_cycle {
__nestrs_panic_circular_module_dependency(type_name);
}
Self { type_id }
}
}
impl Drop for __NestrsModuleBuildGuard {
fn drop(&mut self) {
MODULE_BUILD_STACK.with(|stack| {
let mut guard = stack.borrow_mut();
if let Some((_, id)) = guard.last() {
if *id == self.type_id {
guard.pop();
}
}
});
}
}
#[doc(hidden)]
pub fn __nestrs_module_stack_contains(type_id: TypeId) -> bool {
MODULE_BUILD_STACK.with(|stack| stack.borrow().iter().any(|(_, id)| *id == type_id))
}
#[doc(hidden)]
pub fn __nestrs_panic_circular_module_dependency(import_type_name: &'static str) -> ! {
let chain = MODULE_BUILD_STACK.with(|stack| {
stack
.borrow()
.iter()
.map(|(name, _)| *name)
.chain(std::iter::once(import_type_name))
.collect::<Vec<_>>()
.join(" -> ")
});
panic!(
"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`).",
);
}
tokio::task_local! {
static REQUEST_SCOPE_CACHE: std::cell::RefCell<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>;
}
pub async fn with_request_scope<Fut, T>(future: Fut) -> T
where
Fut: std::future::Future<Output = T>,
{
if REQUEST_SCOPE_CACHE.try_with(|_| ()).is_ok() {
return future.await;
}
REQUEST_SCOPE_CACHE
.scope(std::cell::RefCell::new(HashMap::new()), future)
.await
}
pub fn spawn_with_request_scope<F>(future: F) -> tokio::task::JoinHandle<F::Output>
where
F: std::future::Future + Send + 'static,
F::Output: Send + 'static,
{
let snapshot = REQUEST_SCOPE_CACHE
.try_with(|cell| cell.borrow().clone())
.ok();
tokio::spawn(async move {
match snapshot {
Some(map) => {
REQUEST_SCOPE_CACHE
.scope(std::cell::RefCell::new(map), future)
.await
}
None => with_request_scope(future).await,
}
})
}
pub fn request_scope_get(type_id: TypeId) -> Option<Arc<dyn Any + Send + Sync>> {
REQUEST_SCOPE_CACHE
.try_with(|c| c.borrow().get(&type_id).cloned())
.ok()
.flatten()
}
pub fn request_scope_insert(type_id: TypeId, value: Arc<dyn Any + Send + Sync>) {
let _ = REQUEST_SCOPE_CACHE.try_with(|c| {
c.borrow_mut().insert(type_id, value);
});
}
tokio::task_local! {
static ABILITY_SLOT: std::cell::RefCell<Option<Arc<dyn Any + Send + Sync>>>;
}
pub async fn with_ability_erased<F, T>(ability: Arc<dyn Any + Send + Sync>, future: F) -> T
where
F: std::future::Future<Output = T>,
{
ABILITY_SLOT
.scope(std::cell::RefCell::new(Some(ability)), future)
.await
}
pub fn current_ability_erased() -> Option<Arc<dyn Any + Send + Sync>> {
ABILITY_SLOT.try_with(|c| c.borrow().clone()).ok().flatten()
}
tokio::task_local! {
static PRINCIPAL_SLOT: std::cell::RefCell<Option<Arc<dyn Any + Send + Sync>>>;
}
pub async fn with_principal_erased<F, T>(principal: Arc<dyn Any + Send + Sync>, future: F) -> T
where
F: std::future::Future<Output = T>,
{
PRINCIPAL_SLOT
.scope(std::cell::RefCell::new(Some(principal)), future)
.await
}
pub fn current_principal_erased() -> Option<Arc<dyn Any + Send + Sync>> {
PRINCIPAL_SLOT
.try_with(|c| c.borrow().clone())
.ok()
.flatten()
}
thread_local! {
static CONSTRUCTION_STACK: std::cell::RefCell<Vec<(&'static str, TypeId)>> =
const { std::cell::RefCell::new(Vec::new()) };
}
struct ConstructionGuard {
type_id: TypeId,
}
impl ConstructionGuard {
fn push(type_id: TypeId, type_name: &'static str) -> Self {
CONSTRUCTION_STACK.with(|stack| {
let mut guard = stack.borrow_mut();
if guard.iter().any(|(_, id)| *id == type_id) {
let chain = guard
.iter()
.map(|(name, _)| *name)
.chain(std::iter::once(type_name))
.collect::<Vec<_>>()
.join(" -> ");
panic!(
"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`)."
);
}
guard.push((type_name, type_id));
});
Self { type_id }
}
}
impl Drop for ConstructionGuard {
fn drop(&mut self) {
CONSTRUCTION_STACK.with(|stack| {
let mut guard = stack.borrow_mut();
if let Some((_, id)) = guard.last() {
if *id == self.type_id {
guard.pop();
}
}
});
}
}
fn provider_dep_graph() -> &'static RwLock<HashMap<TypeId, Vec<TypeId>>> {
static DEPS: OnceLock<RwLock<HashMap<TypeId, Vec<TypeId>>>> = OnceLock::new();
DEPS.get_or_init(|| RwLock::new(HashMap::new()))
}
fn record_provider_dependency(from: TypeId, to: TypeId) {
{
let deps = provider_dep_graph().read().expect("provider dep graph");
if let Some(targets) = deps.get(&from) {
if targets.contains(&to) {
return;
}
}
}
let mut deps = provider_dep_graph().write().expect("provider dep graph");
let targets = deps.entry(from).or_default();
if !targets.contains(&to) {
targets.push(to);
}
}
#[cfg(feature = "test-hooks")]
pub fn clear_provider_dependencies_for_tests() {
provider_dep_graph()
.write()
.expect("provider dep graph")
.clear();
}
type ModuleBuildFn = Box<dyn FnOnce() -> (ProviderRegistry, Router) + Send>;
static MODULE_BUILD_CACHE: OnceLock<RwLock<HashMap<TypeId, Arc<OnceLock<DynamicModule>>>>> =
OnceLock::new();
fn module_build_cache() -> &'static RwLock<HashMap<TypeId, Arc<OnceLock<DynamicModule>>>> {
MODULE_BUILD_CACHE.get_or_init(|| RwLock::new(HashMap::new()))
}
#[doc(hidden)]
pub fn __nestrs_memoize_module_build<M: Module + 'static>(
build: ModuleBuildFn,
) -> (ProviderRegistry, Router) {
let key = TypeId::of::<M>();
let entry = Arc::clone(
module_build_cache()
.write()
.expect("module build cache")
.entry(key)
.or_insert_with(|| Arc::new(OnceLock::new())),
);
let dm = entry.get_or_init(|| {
let (registry, router) = build();
DynamicModule::from_parts(registry, router, <M as Module>::exports())
});
(dm.registry.clone(), dm.router.clone())
}
#[cfg(feature = "test-hooks")]
pub fn clear_module_cache_for_tests() {
module_build_cache()
.write()
.expect("module build cache")
.clear();
}
#[cfg(test)]
mod request_scope_tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
struct Marker;
struct OtherMarker;
#[tokio::test]
async fn nested_with_request_scope_joins_the_outer_scope() {
with_request_scope(async {
request_scope_insert(
TypeId::of::<Marker>(),
Arc::new(Marker) as Arc<dyn Any + Send + Sync>,
);
with_request_scope(async {
assert!(
request_scope_get(TypeId::of::<Marker>()).is_some(),
"nested with_request_scope must not hide outer values"
);
request_scope_insert(
TypeId::of::<OtherMarker>(),
Arc::new(OtherMarker) as Arc<dyn Any + Send + Sync>,
);
})
.await;
assert!(
request_scope_get(TypeId::of::<OtherMarker>()).is_some(),
"nested inserts must land in the active (outer) scope"
);
})
.await;
}
#[tokio::test]
async fn with_request_scope_opens_a_fresh_scope_when_none_is_active() {
assert!(request_scope_get(TypeId::of::<Marker>()).is_none());
with_request_scope(async {
request_scope_insert(
TypeId::of::<Marker>(),
Arc::new(Marker) as Arc<dyn Any + Send + Sync>,
);
assert!(request_scope_get(TypeId::of::<Marker>()).is_some());
})
.await;
}
static REQUEST_PROVIDER_CONSTRUCTIONS: AtomicUsize = AtomicUsize::new(0);
struct CountedRequestService;
impl Injectable for CountedRequestService {
fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
REQUEST_PROVIDER_CONSTRUCTIONS.fetch_add(1, Ordering::SeqCst);
Arc::new(Self)
}
fn scope() -> ProviderScope {
ProviderScope::Request
}
}
static SNAPSHOT_CONSTRUCTIONS: AtomicUsize = AtomicUsize::new(0);
struct SnapshottedService;
impl Injectable for SnapshottedService {
fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
SNAPSHOT_CONSTRUCTIONS.fetch_add(1, Ordering::SeqCst);
Arc::new(Self)
}
fn scope() -> ProviderScope {
ProviderScope::Request
}
}
static OFF_SCOPE_CONSTRUCTIONS: AtomicUsize = AtomicUsize::new(0);
struct OffScopeService;
impl Injectable for OffScopeService {
fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
OFF_SCOPE_CONSTRUCTIONS.fetch_add(1, Ordering::SeqCst);
Arc::new(Self)
}
fn scope() -> ProviderScope {
ProviderScope::Request
}
}
#[tokio::test]
async fn nested_scope_does_not_rebuild_request_providers() {
REQUEST_PROVIDER_CONSTRUCTIONS.store(0, Ordering::SeqCst);
let mut registry = ProviderRegistry::new();
registry.register::<CountedRequestService>();
with_request_scope(async {
let outer: Arc<CountedRequestService> = registry.get();
let inner: Arc<CountedRequestService> =
with_request_scope(async { registry.get::<CountedRequestService>() }).await;
assert!(
Arc::ptr_eq(&outer, &inner),
"nested with_request_scope must reuse the request-scoped instance"
);
assert_eq!(
REQUEST_PROVIDER_CONSTRUCTIONS.load(Ordering::SeqCst),
1,
"one construction per request, not one per nested scope"
);
})
.await;
}
#[tokio::test]
async fn try_get_returns_none_off_scope_instead_of_panicking() {
let mut registry = ProviderRegistry::new();
registry.register::<CountedRequestService>();
assert!(
registry.try_get::<CountedRequestService>().is_none(),
"off-scope Request-scoped resolution is absence, not a panic"
);
}
#[test]
#[should_panic(expected = "spawn_with_request_scope")]
fn get_off_scope_panics_with_the_fix_in_the_message() {
let mut registry = ProviderRegistry::new();
registry.register::<CountedRequestService>();
let _ = registry.get::<CountedRequestService>();
}
#[tokio::test]
async fn spawned_task_sees_the_request_snapshot() {
SNAPSHOT_CONSTRUCTIONS.store(0, Ordering::SeqCst);
let mut registry = ProviderRegistry::new();
registry.register::<SnapshottedService>();
with_request_scope(async {
let outer: Arc<SnapshottedService> = registry.get();
let registry = registry.clone();
let handle = spawn_with_request_scope(async move {
let child: Arc<SnapshottedService> = registry.get();
Arc::ptr_eq(&outer, &child)
});
assert!(
handle.await.unwrap(),
"spawned task must resolve the instance the request had at spawn time"
);
assert_eq!(
SNAPSHOT_CONSTRUCTIONS.load(Ordering::SeqCst),
1,
"snapshot must carry the instance, not re-construct it"
);
})
.await;
}
#[tokio::test]
async fn spawned_task_writes_stay_in_the_child() {
with_request_scope(async {
let handle = spawn_with_request_scope(async {
request_scope_insert(
TypeId::of::<OtherMarker>(),
Arc::new(OtherMarker) as Arc<dyn Any + Send + Sync>,
);
assert!(
request_scope_get(TypeId::of::<OtherMarker>()).is_some(),
"the child sees its own inserts"
);
});
handle.await.unwrap();
assert!(
request_scope_get(TypeId::of::<OtherMarker>()).is_none(),
"child-scope writes must not leak into the parent request scope"
);
})
.await;
}
#[tokio::test]
async fn spawning_off_scope_gives_each_child_a_fresh_scope() {
OFF_SCOPE_CONSTRUCTIONS.store(0, Ordering::SeqCst);
let mut registry = ProviderRegistry::new();
registry.register::<OffScopeService>();
let a = {
let registry = registry.clone();
spawn_with_request_scope(async move { registry.get::<OffScopeService>() })
};
let b = {
let registry = registry.clone();
spawn_with_request_scope(async move { registry.get::<OffScopeService>() })
};
let a = a.await.unwrap();
let b = b.await.unwrap();
assert!(
!Arc::ptr_eq(&a, &b),
"off-scope spawns must not share request-scoped instances"
);
assert_eq!(
OFF_SCOPE_CONSTRUCTIONS.load(Ordering::SeqCst),
2,
"one construction per spawned scope"
);
}
}
#[cfg(test)]
mod provider_lifecycle_tests {
use super::*;
use std::sync::Mutex;
type Log = Arc<Mutex<Vec<String>>>;
fn assert_log(log: &Log, expected: &[&str]) {
let got = log.lock().unwrap();
let expected: Vec<String> = expected.iter().map(|s| s.to_string()).collect();
assert_eq!(*got, expected, "hook firing order");
}
struct Tagged<const TAG: char> {
log: Log,
}
impl<const TAG: char> Tagged<TAG> {
fn record(&self, event: &str) {
self.log.lock().unwrap().push(format!("{TAG}:{event}"));
}
}
#[async_trait]
impl<const TAG: char> ProviderLifecycle for Tagged<TAG> {
async fn on_module_init(&self) {
self.record("init");
}
async fn on_module_destroy(&self) {
self.record("destroy");
}
async fn on_application_bootstrap(&self) {
self.record("bootstrap");
}
async fn on_before_application_shutdown(&self) {
self.record("before_shutdown");
}
async fn on_application_shutdown(&self) {
self.record("shutdown");
}
}
#[tokio::test]
async fn use_value_lifecycle_hooks_fire_in_framework_order() {
let log: Log = Arc::default();
let mut registry = ProviderRegistry::new();
registry.register_use_value_with_lifecycle(Arc::new(Tagged::<'V'> { log: log.clone() }));
registry.run_on_module_init().await;
registry.run_on_application_bootstrap().await;
registry.run_on_before_application_shutdown().await;
registry.run_on_application_shutdown().await;
registry.run_on_module_destroy().await;
assert_log(
&log,
&[
"V:init",
"V:bootstrap",
"V:before_shutdown",
"V:shutdown",
"V:destroy",
],
);
}
#[tokio::test]
async fn lifecycle_hooks_register_order_init_reverse_destroy() {
let log: Log = Arc::default();
let mut registry = ProviderRegistry::new();
registry.register_use_value_with_lifecycle(Arc::new(Tagged::<'A'> { log: log.clone() }));
registry.register_use_value_with_lifecycle(Arc::new(Tagged::<'B'> { log: log.clone() }));
registry.run_on_module_init().await;
registry.run_on_module_destroy().await;
assert_log(&log, &["A:init", "B:init", "B:destroy", "A:destroy"]);
}
#[tokio::test]
async fn use_factory_lifecycle_hooks_fire_on_the_lazily_built_singleton() {
let log: Log = Arc::default();
let mut registry = ProviderRegistry::new();
registry.register_use_factory_with_lifecycle(ProviderScope::Singleton, {
let log = log.clone();
move |_r| {
log.lock().unwrap().push("F:construct".to_string());
Arc::new(Tagged::<'F'> { log: log.clone() })
}
});
registry.run_on_module_init().await;
assert_log(&log, &["F:construct", "F:init"]);
let _v: Arc<Tagged<'F'>> = registry.get();
assert_log(&log, &["F:construct", "F:init"]);
}
#[tokio::test]
async fn plain_use_value_and_use_factory_stay_hook_less() {
let log: Log = Arc::default();
let mut registry = ProviderRegistry::new();
registry.register_use_value(Arc::new(Tagged::<'V'> { log: log.clone() }));
registry.register_use_factory(ProviderScope::Singleton, {
let log = log.clone();
move |_r| Arc::new(Tagged::<'G'> { log: log.clone() })
});
registry.run_on_module_init().await;
registry.run_on_application_bootstrap().await;
registry.run_on_before_application_shutdown().await;
registry.run_on_application_shutdown().await;
registry.run_on_module_destroy().await;
assert_log(&log, &[]);
}
}
#[cfg(test)]
mod provider_dep_graph_tests {
use super::*;
struct FromA;
struct ToB;
#[test]
fn record_provider_dependency_is_idempotent_on_repeated_edges() {
let from = TypeId::of::<FromA>();
let to = TypeId::of::<ToB>();
record_provider_dependency(from, to);
record_provider_dependency(from, to);
record_provider_dependency(from, to);
let deps = provider_dep_graph().read().expect("provider dep graph");
let targets = deps.get(&from).expect("edge recorded");
assert_eq!(targets.len(), 1, "repeated edges must not duplicate");
assert_eq!(targets[0], to);
}
#[test]
fn record_provider_dependency_dedupes_racing_first_recordings() {
let from = TypeId::of::<FromA>();
let to = TypeId::of::<ToB>();
std::thread::scope(|s| {
for _ in 0..16 {
s.spawn(|| record_provider_dependency(from, to));
}
});
let deps = provider_dep_graph().read().expect("provider dep graph");
let targets = deps.get(&from).expect("edge recorded");
assert_eq!(
targets.len(),
1,
"racing recorders must not duplicate an edge"
);
}
}
#[cfg(test)]
mod override_provider_tests {
use super::*;
struct SingletonSvc;
impl Injectable for SingletonSvc {
fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
unreachable!("overridden before construction")
}
}
struct RequestSvc;
impl Injectable for RequestSvc {
fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
unreachable!("overridden before construction")
}
fn scope() -> ProviderScope {
ProviderScope::Request
}
}
struct TransientSvc;
impl Injectable for TransientSvc {
fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
unreachable!("overridden before construction")
}
fn scope() -> ProviderScope {
ProviderScope::Transient
}
}
fn declared_scope<T: 'static>(registry: &ProviderRegistry) -> ProviderScope {
let name = std::any::type_name::<T>();
registry
.provider_summaries()
.into_iter()
.find(|s| s.type_name == name)
.map(|s| s.scope)
.unwrap_or_else(|| panic!("{name} not registered"))
}
#[test]
fn singleton_override_returns_the_instance_and_keeps_singleton_scope() {
let mut registry = ProviderRegistry::new();
registry.register::<SingletonSvc>();
let mock = Arc::new(SingletonSvc);
registry.override_provider::<SingletonSvc>(mock.clone());
let resolved = registry.get::<SingletonSvc>();
assert!(Arc::ptr_eq(&resolved, &mock), "override instance served");
assert_eq!(
declared_scope::<SingletonSvc>(®istry),
ProviderScope::Singleton,
"singleton overrides stay singleton"
);
}
#[tokio::test]
async fn request_override_keeps_request_scope_and_resolves_to_the_instance() {
let mut registry = ProviderRegistry::new();
registry.register::<RequestSvc>();
let mock = Arc::new(RequestSvc);
registry.override_provider::<RequestSvc>(mock.clone());
let first = with_request_scope(async {
let a = registry.get::<RequestSvc>();
let b = registry.get::<RequestSvc>();
assert!(Arc::ptr_eq(&a, &b), "one resolution per request");
a
})
.await;
assert!(Arc::ptr_eq(&first, &mock), "override served inside request");
let second = with_request_scope(async { registry.get::<RequestSvc>() }).await;
assert!(
Arc::ptr_eq(&second, &mock),
"override instance shared across requests"
);
assert_eq!(
declared_scope::<RequestSvc>(®istry),
ProviderScope::Request,
"request overrides keep request scope — no silent Singleton coercion"
);
}
#[test]
fn transient_override_keeps_transient_scope_and_resolves_each_time() {
let mut registry = ProviderRegistry::new();
registry.register::<TransientSvc>();
let mock = Arc::new(TransientSvc);
registry.override_provider::<TransientSvc>(mock.clone());
let a = registry.get::<TransientSvc>();
let b = registry.get::<TransientSvc>();
assert!(Arc::ptr_eq(&a, &mock), "first transient resolution served");
assert!(Arc::ptr_eq(&b, &mock), "second transient resolution served");
assert_eq!(
declared_scope::<TransientSvc>(®istry),
ProviderScope::Transient,
"transient overrides keep transient scope"
);
}
}