Skip to main content

rust_webx_core/
dispatch_runtime.rs

1//! Per-request dispatch runtime — instance-scoped provider and handler cache.
2//!
3//! `Host` stores a [`DispatchRuntime`] and scopes it to each HTTP request via
4//! a task-local. Macro-generated dispatch and `Mediator` read from the active
5//! runtime when present, falling back to process-wide globals for backward
6//! compatibility (in-process `Mediator::send` without HTTP context).
7
8use std::future::Future;
9use std::sync::Arc;
10
11use crate::route::scan::{try_global_provider, HandlerCache};
12
13tokio::task_local! {
14    static ACTIVE_RUNTIME: Option<Arc<DispatchRuntime>>;
15}
16
17/// Provider + handler registry for one host instance.
18#[derive(Clone)]
19pub struct DispatchRuntime {
20    provider: Arc<rust_dix::ServiceProvider>,
21    handler_cache: Arc<HandlerCache>,
22}
23
24impl DispatchRuntime {
25    pub fn new(provider: Arc<rust_dix::ServiceProvider>, handler_cache: Arc<HandlerCache>) -> Self {
26        Self {
27            provider,
28            handler_cache,
29        }
30    }
31
32    pub fn provider(&self) -> &Arc<rust_dix::ServiceProvider> {
33        &self.provider
34    }
35
36    pub fn handler_cache(&self) -> &Arc<HandlerCache> {
37        &self.handler_cache
38    }
39
40    /// Run an async block with this runtime active for the current task.
41    pub async fn run<F, R>(&self, f: F) -> R
42    where
43        F: Future<Output = R>,
44    {
45        ACTIVE_RUNTIME
46            .scope(Some(Arc::new(self.clone())), f)
47            .await
48    }
49}
50
51/// Provider for HTTP dispatch and `#[handler(inject)]` factories.
52///
53/// Prefers the active [`DispatchRuntime`] (HTTP requests and hosted services started
54/// by `Host`). Falls back to the deprecated process-wide shim when
55/// [`set_global_provider`](crate::route::scan::set_global_provider) was called manually.
56pub fn dispatch_provider() -> Arc<rust_dix::ServiceProvider> {
57    if let Ok(runtime_provider) = ACTIVE_RUNTIME.try_with(|rt| {
58        rt.as_ref().map(|r| Arc::clone(&r.provider))
59    }) {
60        if let Some(provider) = runtime_provider {
61            return provider;
62        }
63    }
64
65    if let Some(provider) = try_global_provider() {
66        tracing::warn!(
67            "dispatch_provider() using deprecated global ServiceProvider shim; \
68             prefer Host::dispatch_runtime().run() or host.provider()"
69        );
70        return provider;
71    }
72
73    panic!(
74        "No active DispatchRuntime and no deprecated global ServiceProvider. \
75         HTTP and hosted services run inside Host::dispatch_runtime().run(); \
76         for tests use host.dispatch_runtime().run with an async block, or host.provider()."
77    );
78}
79
80/// Handler registry for the active dispatch context.
81///
82/// Prefers the active [`DispatchRuntime`]; falls back to a lazy-built process-wide
83/// registry (inventory is process-wide; contents are identical per host).
84pub fn dispatch_handler_cache() -> Arc<HandlerCache> {
85    if let Ok(runtime_cache) = ACTIVE_RUNTIME.try_with(|rt| {
86        rt.as_ref().map(|r| Arc::clone(&r.handler_cache))
87    }) {
88        if let Some(cache) = runtime_cache {
89            return cache;
90        }
91    }
92
93    #[allow(deprecated)]
94    {
95        HandlerCache::get_or_init()
96    }
97}