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
54 where
55 B: IPipelineBehavior<Req, Rsp> + Default + 'static,
56 Req: Send + 'static,
57 Rsp: Send + 'static;
58
59 fn add_typed_pipeline_with<F, Req, Rsp>(self, factory: F) -> Self
65 where
66 F: Fn(&dyn IServiceResolver) -> Arc<dyn IPipelineBehavior<Req, Rsp>>
67 + Send
68 + Sync
69 + 'static,
70 Req: Send + 'static,
71 Rsp: Send + 'static;
72
73 fn add_hosted_service<T>(self) -> Self
92 where
93 T: IHostedService + Default + Send + Sync + 'static;
94
95 fn add_hosted_service_with<T, F>(self, factory: F) -> Self
114 where
115 T: IHostedService + 'static,
116 F: Fn(&dyn IServiceResolver) -> Arc<T> + Send + Sync + 'static;
117
118 fn add_hosted_service_ordered<T>(self, order: i32) -> Self
123 where
124 T: IHostedService + Default + Send + Sync + 'static;
125
126 fn add_hosted_service_with_ordered<T, F>(self, order: i32, factory: F) -> Self
128 where
129 T: IHostedService + 'static,
130 F: Fn(&dyn IServiceResolver) -> Arc<T> + Send + Sync + 'static;
131
132 fn add_hosted_service_if<T>(self, enabled: bool) -> Self
141 where
142 T: IHostedService + Default + Send + Sync + 'static;
143
144 fn add_hosted_service_with_if<T, F>(self, enabled: bool, factory: F) -> Self
146 where
147 T: IHostedService + 'static,
148 F: Fn(&dyn IServiceResolver) -> Arc<T> + Send + Sync + 'static;
149
150 fn add_background_service<B>(self) -> Self
157 where
158 B: crate::hosted::BackgroundService + Default;
159
160 fn add_background_service_with<B, F>(self, factory: F) -> Self
168 where
169 B: crate::hosted::BackgroundService,
170 F: Fn(&dyn IServiceResolver) -> Arc<B> + Send + Sync + 'static;
171
172 fn add_background_service_with_if<B, F>(self, enabled: bool, factory: F) -> Self
174 where
175 B: crate::hosted::BackgroundService,
176 F: Fn(&dyn IServiceResolver) -> Arc<B> + Send + Sync + 'static;
177}
178
179impl IServiceCollectionExt for ServiceCollection {
180 fn add_mediator(self) -> Self {
181 MEDIATOR_ACTIVE.store(true, std::sync::atomic::Ordering::SeqCst);
186 self.transient::<Mediator>(|resolver| {
187 let provider = resolver
188 .provider_arc()
189 .expect("ServiceProvider not available from DI resolver");
190 let handler_cache = resolver
191 .get_any(std::any::type_name::<Arc<crate::route::scan::HandlerCache>>())
192 .and_then(|service| {
193 service
194 .downcast::<Arc<crate::route::scan::HandlerCache>>()
195 .ok()
196 })
197 .map(|service| Arc::clone(&*service))
198 .unwrap_or_else(|| Arc::new(crate::route::scan::HandlerCache::build()));
199 Arc::new(Mediator::new(provider, handler_cache))
200 })
201 }
202
203 fn add_request_endpoints(self) -> Self {
204 ENDPOINT_SCAN.store(true, std::sync::atomic::Ordering::SeqCst);
207 self
208 }
209
210 fn add_controllers(self) -> Self {
211 self
212 }
213
214 fn add_middleware<T>(self) -> Self
215 where
216 T: IMiddleware + Default + Send + Sync + 'static,
217 {
218 self.singleton::<dyn IMiddleware>(|_| Arc::new(T::default()))
219 }
220
221 fn add_typed_pipeline<B, Req, Rsp>(self) -> Self
222 where
223 B: IPipelineBehavior<Req, Rsp> + Default + 'static,
224 Req: Send + 'static,
225 Rsp: Send + 'static,
226 {
227 self.add_typed_pipeline_with::<_, Req, Rsp>(|_| {
228 Arc::new(B::default()) as Arc<dyn IPipelineBehavior<Req, Rsp>>
229 })
230 }
231
232 fn add_typed_pipeline_with<F, Req, Rsp>(self, factory: F) -> Self
233 where
234 F: Fn(&dyn IServiceResolver) -> Arc<dyn IPipelineBehavior<Req, Rsp>>
235 + Send
236 + Sync
237 + 'static,
238 Req: Send + 'static,
239 Rsp: Send + 'static,
240 {
241 self.add(ServiceLifetime::Singleton, factory)
244 }
245
246 fn add_hosted_service<T>(self) -> Self
247 where
248 T: IHostedService + Default + Send + Sync + 'static,
249 {
250 self.singleton::<dyn IHostedService>(|_| Arc::new(T::default()))
251 }
252
253 fn add_hosted_service_with<T, F>(self, factory: F) -> Self
254 where
255 T: IHostedService + 'static,
256 F: Fn(&dyn IServiceResolver) -> Arc<T> + Send + Sync + 'static,
257 {
258 self.add(
259 ServiceLifetime::Singleton,
260 move |resolver: &dyn IServiceResolver| -> Arc<dyn IHostedService> { factory(resolver) },
261 )
262 }
263
264 fn add_hosted_service_ordered<T>(self, order: i32) -> Self
265 where
266 T: IHostedService + Default + Send + Sync + 'static,
267 {
268 self.add_hosted_service_with_ordered(order, |_| Arc::new(T::default()))
269 }
270
271 fn add_hosted_service_with_ordered<T, F>(self, order: i32, factory: F) -> Self
272 where
273 T: IHostedService + 'static,
274 F: Fn(&dyn IServiceResolver) -> Arc<T> + Send + Sync + 'static,
275 {
276 self.add(
277 ServiceLifetime::Singleton,
278 move |resolver: &dyn IServiceResolver| -> Arc<dyn IHostedService> {
279 Arc::new(crate::hosted::OrderedHostedService::new(
280 order,
281 factory(resolver),
282 ))
283 },
284 )
285 }
286
287 fn add_hosted_service_if<T>(self, enabled: bool) -> Self
288 where
289 T: IHostedService + Default + Send + Sync + 'static,
290 {
291 if enabled {
292 self.add_hosted_service::<T>()
293 } else {
294 self
295 }
296 }
297
298 fn add_hosted_service_with_if<T, F>(self, enabled: bool, factory: F) -> Self
299 where
300 T: IHostedService + 'static,
301 F: Fn(&dyn IServiceResolver) -> Arc<T> + Send + Sync + 'static,
302 {
303 if enabled {
304 self.add_hosted_service_with(factory)
305 } else {
306 self
307 }
308 }
309
310 fn add_background_service<B>(self) -> Self
311 where
312 B: crate::hosted::BackgroundService + Default,
313 {
314 self.add_background_service_with(|_| Arc::new(B::default()))
315 }
316
317 fn add_background_service_with<B, F>(self, factory: F) -> Self
318 where
319 B: crate::hosted::BackgroundService,
320 F: Fn(&dyn IServiceResolver) -> Arc<B> + Send + Sync + 'static,
321 {
322 self.add(
323 ServiceLifetime::Singleton,
324 move |resolver: &dyn IServiceResolver| -> Arc<dyn IHostedService> {
325 Arc::new(crate::hosted::BackgroundServiceHost::new(factory(resolver)))
326 },
327 )
328 }
329
330 fn add_background_service_with_if<B, F>(self, enabled: bool, factory: F) -> Self
331 where
332 B: crate::hosted::BackgroundService,
333 F: Fn(&dyn IServiceResolver) -> Arc<B> + Send + Sync + 'static,
334 {
335 if enabled {
336 self.add_background_service_with(factory)
337 } else {
338 self
339 }
340 }
341}
342
343use std::sync::atomic::AtomicBool;
345static ENDPOINT_SCAN: AtomicBool = AtomicBool::new(false);
346static MEDIATOR_ACTIVE: AtomicBool = AtomicBool::new(false);
347
348pub fn should_scan_endpoints() -> bool {
349 ENDPOINT_SCAN.load(std::sync::atomic::Ordering::SeqCst)
350}
351
352pub fn is_mediator_active() -> bool {
353 MEDIATOR_ACTIVE.load(std::sync::atomic::Ordering::SeqCst)
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359 use crate::error::ActionResult;
360 use crate::pipeline::{IPipelineBehavior, Next};
361 use std::sync::atomic::{AtomicU32, Ordering};
362
363 fn resolve<T: Send + Sync + 'static>(resolver: &dyn IServiceResolver) -> Arc<T> {
366 resolver
367 .get_any(std::any::type_name::<T>())
368 .and_then(|service| service.downcast::<Arc<T>>().ok())
369 .map(|service| Arc::clone(&*service))
370 .expect("the dependency must be registered before the pipeline")
371 }
372
373 #[derive(Default)]
376 struct Uppercase;
377
378 #[async_trait::async_trait]
379 impl IPipelineBehavior<u32, String> for Uppercase {
380 async fn handle(&self, req: u32, next: Next<u32, String>) -> ActionResult<String> {
381 let inner = next.run(req + 1).await?;
382 Ok(inner.to_uppercase())
383 }
384 }
385
386 fn string_terminal() -> Next<u32, String> {
387 Next::new(|n: u32| async move { Ok::<String, crate::error::Error>(format!("n={n}")) })
388 }
389
390 #[tokio::test]
391 async fn typed_behavior_registers_only_for_its_request_response_pair() {
392 let provider = ServiceCollection::new()
393 .add_typed_pipeline::<Uppercase, u32, String>()
394 .build()
395 .expect("provider must build");
396
397 let behaviors = provider.get_all::<dyn IPipelineBehavior<u32, String>>();
398 assert_eq!(behaviors.len(), 1);
399 let out = behaviors[0]
400 .handle(7, string_terminal())
401 .await
402 .expect("typed behavior must run");
403 assert_eq!(out, "N=8");
404
405 assert!(
406 provider
407 .get_all::<dyn IPipelineBehavior<u64, String>>()
408 .is_empty(),
409 "a typed behavior must only attach to its own request/response pair"
410 );
411 }
412
413 struct TickBehavior {
414 ticks: Arc<AtomicU32>,
415 }
416
417 #[async_trait::async_trait]
418 impl IPipelineBehavior<u32, u32> for TickBehavior {
419 async fn handle(&self, req: u32, next: Next<u32, u32>) -> ActionResult<u32> {
420 self.ticks.fetch_add(1, Ordering::SeqCst);
421 next.run(req).await
422 }
423 }
424
425 #[tokio::test]
426 async fn typed_add_pipeline_with_constructor_injects_from_the_resolver() {
427 let ticks = Arc::new(AtomicU32::new(0));
428
429 let provider = ServiceCollection::new()
430 .instance(Arc::clone(&ticks))
431 .add_typed_pipeline_with(|resolver| {
432 let ticks: Arc<AtomicU32> = resolve::<AtomicU32>(resolver);
433 Arc::new(TickBehavior { ticks }) as Arc<dyn IPipelineBehavior<u32, u32>>
434 })
435 .build()
436 .expect("provider must build");
437
438 let behavior = &provider.get_all::<dyn IPipelineBehavior<u32, u32>>()[0];
439 let out = behavior
440 .handle(5, Next::new(|n: u32| async move { Ok(n * 2) }))
441 .await
442 .expect("typed behavior must run");
443 assert_eq!(out, 10);
444 assert_eq!(ticks.load(Ordering::SeqCst), 1);
445 }
446
447 struct ShortCircuit;
448
449 #[async_trait::async_trait]
450 impl IPipelineBehavior<u32, String> for ShortCircuit {
451 async fn handle(&self, _req: u32, _next: Next<u32, String>) -> ActionResult<String> {
452 Ok("short".to_string())
453 }
454 }
455
456 #[tokio::test]
457 async fn try_add_keeps_the_existing_registration() {
458 let provider = ServiceCollection::new()
459 .instance(Arc::new(1u32))
460 .try_add(|_| Arc::new(2u32))
461 .build()
462 .expect("provider must build");
463
464 let value = provider.get::<u32>().expect("registered");
465 assert_eq!(
466 *value, 1,
467 "try_add must not replace an earlier registration (MEDI TryAdd); rust-dix 0.8+ resolves the last registration otherwise"
468 );
469 }
470
471 #[tokio::test]
472 async fn typed_behavior_may_short_circuit_with_its_response_type() {
473 let out = ShortCircuit
474 .handle(1, string_terminal())
475 .await
476 .expect("short-circuit must return the typed response");
477 assert_eq!(out, "short");
478 }
479}