1use crate::handler::IHostedService;
4use crate::mediator::Mediator;
5use crate::middleware::IMiddleware;
6use crate::pipeline::IPipelineBehavior;
7use rust_dix::{IServiceResolver, ServiceCollection, ServiceLifetime};
8use std::sync::Arc;
9
10pub trait IServiceCollectionExt: Sized {
13 fn add_mediator(self) -> Self;
18
19 fn add_request_endpoints(self) -> Self;
23
24 fn add_controllers(self) -> Self;
26
27 fn add_middleware<T>(self) -> Self
29 where
30 T: IMiddleware + Default + Send + Sync + 'static;
31
32 fn add_typed_pipeline<B, Req, Rsp>(self) -> Self
53 where
54 B: IPipelineBehavior<Req, Rsp> + Default + 'static,
55 Req: Send + 'static,
56 Rsp: Send + 'static;
57
58 fn add_typed_pipeline_with<F, Req, Rsp>(self, factory: F) -> Self
64 where
65 F: Fn(&dyn IServiceResolver) -> Arc<dyn IPipelineBehavior<Req, Rsp>>
66 + Send
67 + Sync
68 + 'static,
69 Req: Send + 'static,
70 Rsp: Send + 'static;
71
72 fn add_hosted_service<T>(self) -> Self
91 where
92 T: IHostedService + Default + Send + Sync + 'static;
93}
94
95impl IServiceCollectionExt for ServiceCollection {
96 fn add_mediator(self) -> Self {
97 MEDIATOR_ACTIVE.store(true, std::sync::atomic::Ordering::SeqCst);
102 self.transient::<Mediator>(|resolver| {
103 let provider = resolver
104 .provider_arc()
105 .expect("ServiceProvider not available from DI resolver");
106 let handler_cache = resolver
107 .get_any(std::any::type_name::<Arc<crate::route::scan::HandlerCache>>())
108 .and_then(|service| {
109 service
110 .downcast::<Arc<crate::route::scan::HandlerCache>>()
111 .ok()
112 })
113 .map(|service| Arc::clone(&*service))
114 .unwrap_or_else(|| Arc::new(crate::route::scan::HandlerCache::build()));
115 Arc::new(Mediator::new(provider, handler_cache))
116 })
117 }
118
119 fn add_request_endpoints(self) -> Self {
120 ENDPOINT_SCAN.store(true, std::sync::atomic::Ordering::SeqCst);
123 self
124 }
125
126 fn add_controllers(self) -> Self {
127 self
128 }
129
130 fn add_middleware<T>(self) -> Self
131 where
132 T: IMiddleware + Default + Send + Sync + 'static,
133 {
134 self.singleton::<dyn IMiddleware>(|_| Arc::new(T::default()))
135 }
136
137 fn add_typed_pipeline<B, Req, Rsp>(self) -> Self
138 where
139 B: IPipelineBehavior<Req, Rsp> + Default + 'static,
140 Req: Send + 'static,
141 Rsp: Send + 'static,
142 {
143 self.add_typed_pipeline_with::<_, Req, Rsp>(|_| {
144 Arc::new(B::default()) as Arc<dyn IPipelineBehavior<Req, Rsp>>
145 })
146 }
147
148 fn add_typed_pipeline_with<F, Req, Rsp>(self, factory: F) -> Self
149 where
150 F: Fn(&dyn IServiceResolver) -> Arc<dyn IPipelineBehavior<Req, Rsp>>
151 + Send
152 + Sync
153 + 'static,
154 Req: Send + 'static,
155 Rsp: Send + 'static,
156 {
157 self.add(ServiceLifetime::Singleton, factory)
160 }
161
162 fn add_hosted_service<T>(self) -> Self
163 where
164 T: IHostedService + Default + Send + Sync + 'static,
165 {
166 self.singleton::<dyn IHostedService>(|_| Arc::new(T::default()))
167 }
168}
169
170use std::sync::atomic::AtomicBool;
172static ENDPOINT_SCAN: AtomicBool = AtomicBool::new(false);
173static MEDIATOR_ACTIVE: AtomicBool = AtomicBool::new(false);
174
175pub fn should_scan_endpoints() -> bool {
176 ENDPOINT_SCAN.load(std::sync::atomic::Ordering::SeqCst)
177}
178
179pub fn is_mediator_active() -> bool {
180 MEDIATOR_ACTIVE.load(std::sync::atomic::Ordering::SeqCst)
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use crate::pipeline::{IPipelineBehavior, Next};
187 use std::sync::atomic::{AtomicU32, Ordering};
188
189 fn resolve<T: Send + Sync + 'static>(resolver: &dyn IServiceResolver) -> Arc<T> {
192 resolver
193 .get_any(std::any::type_name::<T>())
194 .and_then(|service| service.downcast::<Arc<T>>().ok())
195 .map(|service| Arc::clone(&*service))
196 .expect("the dependency must be registered before the pipeline")
197 }
198
199 #[derive(Default)]
202 struct Uppercase;
203
204 #[async_trait::async_trait]
205 impl IPipelineBehavior<u32, String> for Uppercase {
206 async fn handle(&self, req: u32, next: Next<u32, String>) -> crate::error::Result<String> {
207 let inner = next.run(req + 1).await?;
208 Ok(inner.to_uppercase())
209 }
210 }
211
212 fn string_terminal() -> Next<u32, String> {
213 Next::new(|n: u32| async move { Ok::<String, crate::error::Error>(format!("n={n}")) })
214 }
215
216 #[tokio::test]
217 async fn typed_behavior_registers_only_for_its_request_response_pair() {
218 let provider = ServiceCollection::new()
219 .add_typed_pipeline::<Uppercase, u32, String>()
220 .build()
221 .expect("provider must build");
222
223 let behaviors = provider.get_all::<dyn IPipelineBehavior<u32, String>>();
224 assert_eq!(behaviors.len(), 1);
225 let out = behaviors[0]
226 .handle(7, string_terminal())
227 .await
228 .expect("typed behavior must run");
229 assert_eq!(out, "N=8");
230
231 assert!(
232 provider
233 .get_all::<dyn IPipelineBehavior<u64, String>>()
234 .is_empty(),
235 "a typed behavior must only attach to its own request/response pair"
236 );
237 }
238
239 struct TickBehavior {
240 ticks: Arc<AtomicU32>,
241 }
242
243 #[async_trait::async_trait]
244 impl IPipelineBehavior<u32, u32> for TickBehavior {
245 async fn handle(&self, req: u32, next: Next<u32, u32>) -> crate::error::Result<u32> {
246 self.ticks.fetch_add(1, Ordering::SeqCst);
247 next.run(req).await
248 }
249 }
250
251 #[tokio::test]
252 async fn typed_add_pipeline_with_constructor_injects_from_the_resolver() {
253 let ticks = Arc::new(AtomicU32::new(0));
254
255 let provider = ServiceCollection::new()
256 .instance(Arc::clone(&ticks))
257 .add_typed_pipeline_with(|resolver| {
258 let ticks: Arc<AtomicU32> = resolve::<AtomicU32>(resolver);
259 Arc::new(TickBehavior { ticks }) as Arc<dyn IPipelineBehavior<u32, u32>>
260 })
261 .build()
262 .expect("provider must build");
263
264 let behavior = &provider.get_all::<dyn IPipelineBehavior<u32, u32>>()[0];
265 let out = behavior
266 .handle(5, Next::new(|n: u32| async move { Ok(n * 2) }))
267 .await
268 .expect("typed behavior must run");
269 assert_eq!(out, 10);
270 assert_eq!(ticks.load(Ordering::SeqCst), 1);
271 }
272
273 struct ShortCircuit;
274
275 #[async_trait::async_trait]
276 impl IPipelineBehavior<u32, String> for ShortCircuit {
277 async fn handle(
278 &self,
279 _req: u32,
280 _next: Next<u32, String>,
281 ) -> crate::error::Result<String> {
282 Ok("short".to_string())
283 }
284 }
285
286 #[tokio::test]
287 async fn try_add_keeps_the_existing_registration() {
288 let provider = ServiceCollection::new()
289 .instance(Arc::new(1u32))
290 .try_add(|_| Arc::new(2u32))
291 .build()
292 .expect("provider must build");
293
294 let value = provider.get::<u32>().expect("registered");
295 assert_eq!(
296 *value, 1,
297 "try_add must not replace an earlier registration (MEDI TryAdd); rust-dix 0.8+ resolves the last registration otherwise"
298 );
299 }
300
301 #[tokio::test]
302 async fn typed_behavior_may_short_circuit_with_its_response_type() {
303 let out = ShortCircuit
304 .handle(1, string_terminal())
305 .await
306 .expect("short-circuit must return the typed response");
307 assert_eq!(out, "short");
308 }
309}