Skip to main content

rust_webx_core/route/
ext.rs

1//! IServiceCollectionExt — Extension trait for rust_dix ServiceCollection.
2
3use crate::handler::IHostedService;
4use crate::mediator::Mediator;
5use crate::middleware::IMiddleware;
6use crate::pipeline::IPipelineBehavior;
7use rust_dix::{ServiceCollection, ServiceLifetime};
8use std::sync::Arc;
9
10/// Extension methods for `rust_dix::ServiceCollection` to enable
11/// framework-level service registration patterns.
12pub trait IServiceCollectionExt: Sized {
13    /// Register the mediator in the DI container.
14    ///
15    /// The actual `IRequestHandler` and `IEventHandler` implementations
16    /// are registered via `#[rust_dix::module]` blocks (re-exported as `webx::module`).
17    fn add_mediator(self) -> Self;
18
19    /// Signal that endpoints should be auto-discovered from `#[get]` / `#[endpoint]`
20    /// annotations at compile time.  The actual route table is built during
21    /// `Host::build()` by scanning the inventory for `RouteEntry` items.
22    fn add_request_endpoints(self) -> Self;
23
24    /// Register controller types scoped per request.
25    fn add_controllers(self) -> Self;
26
27    /// Register a middleware type in the DI container as a singleton.
28    fn add_middleware<T>(self) -> Self
29    where
30        T: IMiddleware + Default + Send + Sync + 'static;
31
32    /// Register a pipeline behavior in the DI container as a singleton.
33    fn add_pipeline<T>(self) -> Self
34    where
35        T: IPipelineBehavior + Default + Send + Sync + 'static;
36
37    /// Register a hosted service in the DI container as a singleton.
38    ///
39    /// Hosted services are started when the host starts (before accepting
40    /// requests) and stopped during graceful shutdown.
41    ///
42    /// Analogous to ASP.NET Core's `AddHostedService<T>()`.
43    ///
44    /// # Example
45    ///
46    /// ```ignore
47    /// Host::builder()
48    ///     .register(|svc| {
49    ///         svc.add_hosted_service::<DbInitService>()
50    ///     })
51    ///     .build()
52    ///     .run()
53    ///     .await
54    /// ```
55    fn add_hosted_service<T>(self) -> Self
56    where
57        T: IHostedService + Default + Send + Sync + 'static;
58}
59
60impl IServiceCollectionExt for ServiceCollection {
61    fn add_mediator(self) -> Self {
62        // Transient (not singleton) so build-time validation does not require a
63        // fully wired ServiceProvider. Each resolution shares the root provider Arc.
64        MEDIATOR_ACTIVE.store(true, std::sync::atomic::Ordering::SeqCst);
65        self.transient::<Mediator>(|resolver| {
66            let provider = resolver
67                .provider_arc()
68                .expect("ServiceProvider not available from DI resolver");
69            Arc::new(Mediator::new(provider))
70        })
71    }
72
73    fn add_request_endpoints(self) -> Self {
74        // Routes are collected from inventory at Host::build() time.
75        // This call just signals that auto-scanning is desired.
76        ENDPOINT_SCAN.store(true, std::sync::atomic::Ordering::SeqCst);
77        self
78    }
79
80    fn add_controllers(self) -> Self {
81        self
82    }
83
84    fn add_middleware<T>(self) -> Self
85    where
86        T: IMiddleware + Default + Send + Sync + 'static,
87    {
88        self.singleton::<dyn IMiddleware>(|_| Arc::new(T::default()))
89    }
90
91    fn add_pipeline<T>(self) -> Self
92    where
93        T: IPipelineBehavior + Default + Send + Sync + 'static,
94    {
95        self.add(ServiceLifetime::Singleton, |_| {
96            Arc::new(T::default()) as Arc<dyn IPipelineBehavior>
97        })
98    }
99
100    fn add_hosted_service<T>(self) -> Self
101    where
102        T: IHostedService + Default + Send + Sync + 'static,
103    {
104        self.singleton::<dyn IHostedService>(|_| Arc::new(T::default()))
105    }
106}
107
108// Global flags so HostBuilder knows what to scan at build time.
109use std::sync::atomic::AtomicBool;
110static ENDPOINT_SCAN: AtomicBool = AtomicBool::new(false);
111static MEDIATOR_ACTIVE: AtomicBool = AtomicBool::new(false);
112
113pub fn should_scan_endpoints() -> bool {
114    ENDPOINT_SCAN.load(std::sync::atomic::Ordering::SeqCst)
115}
116
117pub fn is_mediator_active() -> bool {
118    MEDIATOR_ACTIVE.load(std::sync::atomic::Ordering::SeqCst)
119}