1#![allow(deprecated)]
5
6use crate::handler::IHostedService;
7use crate::mediator::Mediator;
8use crate::middleware::IMiddleware;
9use crate::pipeline::{IErasedPipelineBehavior, IPipelineBehavior};
10use rust_dix::{IServiceResolver, ServiceCollection, ServiceLifetime};
11use std::sync::Arc;
12
13pub trait IServiceCollectionExt: Sized {
16 fn add_mediator(self) -> Self;
21
22 fn add_request_endpoints(self) -> Self;
26
27 fn add_controllers(self) -> Self;
29
30 fn add_middleware<T>(self) -> Self
32 where
33 T: IMiddleware + Default + Send + Sync + 'static;
34
35 fn add_typed_pipeline<B, Req, Rsp>(self) -> Self
56 where
57 B: IPipelineBehavior<Req, Rsp> + Default + 'static,
58 Req: Send + 'static,
59 Rsp: Send + 'static;
60
61 fn add_typed_pipeline_with<F, Req, Rsp>(self, factory: F) -> Self
67 where
68 F: Fn(&dyn IServiceResolver) -> Arc<dyn IPipelineBehavior<Req, Rsp>>
69 + Send
70 + Sync
71 + 'static,
72 Req: Send + 'static,
73 Rsp: Send + 'static;
74
75 #[deprecated(
81 since = "0.10.0",
82 note = "use add_typed_pipeline::<B, Req, Rsp>() with the typed IPipelineBehavior"
83 )]
84 fn add_pipeline<T>(self) -> Self
85 where
86 T: IErasedPipelineBehavior + Default + Send + Sync + 'static;
87
88 #[deprecated(
90 since = "0.10.0",
91 note = "use add_typed_pipeline_with(...) with the typed IPipelineBehavior"
92 )]
93 fn add_pipeline_with<F>(self, factory: F) -> Self
94 where
95 F: Fn(&dyn IServiceResolver) -> Arc<dyn IErasedPipelineBehavior> + Send + Sync + 'static;
96
97 fn add_hosted_service<T>(self) -> Self
116 where
117 T: IHostedService + Default + Send + Sync + 'static;
118}
119
120impl IServiceCollectionExt for ServiceCollection {
121 fn add_mediator(self) -> Self {
122 MEDIATOR_ACTIVE.store(true, std::sync::atomic::Ordering::SeqCst);
125 self.transient::<Mediator>(|resolver| {
126 let provider = resolver
127 .provider_arc()
128 .expect("ServiceProvider not available from DI resolver");
129 Arc::new(Mediator::new(provider))
130 })
131 }
132
133 fn add_request_endpoints(self) -> Self {
134 ENDPOINT_SCAN.store(true, std::sync::atomic::Ordering::SeqCst);
137 self
138 }
139
140 fn add_controllers(self) -> Self {
141 self
142 }
143
144 fn add_middleware<T>(self) -> Self
145 where
146 T: IMiddleware + Default + Send + Sync + 'static,
147 {
148 self.singleton::<dyn IMiddleware>(|_| Arc::new(T::default()))
149 }
150
151 fn add_typed_pipeline<B, Req, Rsp>(self) -> Self
152 where
153 B: IPipelineBehavior<Req, Rsp> + Default + 'static,
154 Req: Send + 'static,
155 Rsp: Send + 'static,
156 {
157 self.add_typed_pipeline_with::<_, Req, Rsp>(|_| {
158 Arc::new(B::default()) as Arc<dyn IPipelineBehavior<Req, Rsp>>
159 })
160 }
161
162 fn add_typed_pipeline_with<F, Req, Rsp>(self, factory: F) -> Self
163 where
164 F: Fn(&dyn IServiceResolver) -> Arc<dyn IPipelineBehavior<Req, Rsp>>
165 + Send
166 + Sync
167 + 'static,
168 Req: Send + 'static,
169 Rsp: Send + 'static,
170 {
171 self.add(ServiceLifetime::Singleton, factory)
174 }
175
176 #[allow(deprecated)]
177 fn add_pipeline<T>(self) -> Self
178 where
179 T: IErasedPipelineBehavior + Default + Send + Sync + 'static,
180 {
181 self.add_pipeline_with(|_| Arc::new(T::default()) as Arc<dyn IErasedPipelineBehavior>)
182 }
183
184 #[allow(deprecated)]
185 fn add_pipeline_with<F>(self, factory: F) -> Self
186 where
187 F: Fn(&dyn IServiceResolver) -> Arc<dyn IErasedPipelineBehavior> + Send + Sync + 'static,
188 {
189 self.add(ServiceLifetime::Singleton, factory)
192 }
193 fn add_hosted_service<T>(self) -> Self
194 where
195 T: IHostedService + Default + Send + Sync + 'static,
196 {
197 self.singleton::<dyn IHostedService>(|_| Arc::new(T::default()))
198 }
199}
200
201use std::sync::atomic::AtomicBool;
203static ENDPOINT_SCAN: AtomicBool = AtomicBool::new(false);
204static MEDIATOR_ACTIVE: AtomicBool = AtomicBool::new(false);
205
206pub fn should_scan_endpoints() -> bool {
207 ENDPOINT_SCAN.load(std::sync::atomic::Ordering::SeqCst)
208}
209
210pub fn is_mediator_active() -> bool {
211 MEDIATOR_ACTIVE.load(std::sync::atomic::Ordering::SeqCst)
212}
213
214#[cfg(test)]
215mod tests {
216 #![allow(deprecated)]
217
218 use super::*;
219 use crate::pipeline::{
220 BoxedNextFn, BoxedPipelineFuture, IErasedPipelineBehavior, IPipelineBehavior, Next,
221 };
222 use std::any::Any;
223 use std::sync::atomic::{AtomicU32, Ordering};
224
225 struct TickingBehavior {
229 ticks: Arc<AtomicU32>,
230 }
231
232 #[async_trait::async_trait]
233 impl IErasedPipelineBehavior for TickingBehavior {
234 async fn handle(
235 &self,
236 req: Box<dyn Any + Send>,
237 next: BoxedNextFn,
238 ) -> crate::error::Result<Box<dyn Any + Send>> {
239 self.ticks.fetch_add(1, Ordering::SeqCst);
240 next(req).await
241 }
242 }
243
244 fn terminal() -> BoxedNextFn {
246 Box::new(|_req: Box<dyn Any + Send>| -> BoxedPipelineFuture {
247 Box::pin(async { Ok(Box::new("handler-response") as Box<dyn Any + Send>) })
248 })
249 }
250
251 fn resolve<T: Send + Sync + 'static>(resolver: &dyn IServiceResolver) -> Arc<T> {
254 resolver
255 .get_any(std::any::type_name::<T>())
256 .and_then(|service| service.downcast::<Arc<T>>().ok())
257 .map(|service| Arc::clone(&*service))
258 .expect("the dependency must be registered before the pipeline")
259 }
260
261 #[tokio::test]
262 async fn legacy_add_pipeline_with_constructor_injects_from_the_resolver() {
263 let ticks = Arc::new(AtomicU32::new(0));
264
265 let provider = ServiceCollection::new()
266 .instance(Arc::clone(&ticks))
267 .add_pipeline_with(|resolver| {
268 let ticks: Arc<AtomicU32> = resolve::<AtomicU32>(resolver);
269 Arc::new(TickingBehavior { ticks }) as Arc<dyn IErasedPipelineBehavior>
270 })
271 .build()
272 .expect("provider must build");
273
274 let behavior = provider
275 .get::<dyn IErasedPipelineBehavior>()
276 .expect("the behavior must be registered as a singleton");
277 let response = behavior
278 .handle(Box::new("request"), terminal())
279 .await
280 .expect("behavior must pass through to the next step");
281 assert_eq!(
282 *response.downcast::<&str>().expect("handler response"),
283 "handler-response"
284 );
285 assert_eq!(ticks.load(Ordering::SeqCst), 1);
286
287 provider
288 .get::<dyn IErasedPipelineBehavior>()
289 .expect("the behavior must be registered as a singleton")
290 .handle(Box::new("request"), terminal())
291 .await
292 .expect("behavior must pass through to the next step");
293 assert_eq!(ticks.load(Ordering::SeqCst), 2);
294 }
295
296 #[tokio::test]
297 async fn legacy_add_pipeline_keeps_registering_default_behaviors() {
298 #[derive(Default)]
299 struct NoopBehavior;
300
301 #[async_trait::async_trait]
302 impl IErasedPipelineBehavior for NoopBehavior {
303 async fn handle(
304 &self,
305 req: Box<dyn Any + Send>,
306 next: BoxedNextFn,
307 ) -> crate::error::Result<Box<dyn Any + Send>> {
308 next(req).await
309 }
310 }
311
312 let provider = ServiceCollection::new()
313 .add_pipeline::<NoopBehavior>()
314 .build()
315 .expect("provider must build");
316
317 let response = provider
318 .get::<dyn IErasedPipelineBehavior>()
319 .expect("`add_pipeline` must still register a zero-dependency behavior")
320 .handle(Box::new("request"), terminal())
321 .await
322 .expect("behavior must pass through to the next step");
323 assert_eq!(
324 *response.downcast::<&str>().expect("handler response"),
325 "handler-response"
326 );
327 }
328
329 #[derive(Default)]
332 struct Uppercase;
333
334 #[async_trait::async_trait]
335 impl IPipelineBehavior<u32, String> for Uppercase {
336 async fn handle(&self, req: u32, next: Next<u32, String>) -> crate::error::Result<String> {
337 let inner = next.run(req + 1).await?;
338 Ok(inner.to_uppercase())
339 }
340 }
341
342 fn string_terminal() -> Next<u32, String> {
343 Next::new(|n: u32| async move { Ok::<String, crate::error::Error>(format!("n={n}")) })
344 }
345
346 #[tokio::test]
347 async fn typed_behavior_registers_only_for_its_request_response_pair() {
348 let provider = ServiceCollection::new()
349 .add_typed_pipeline::<Uppercase, u32, String>()
350 .build()
351 .expect("provider must build");
352
353 let behaviors = provider.get_all::<dyn IPipelineBehavior<u32, String>>();
354 assert_eq!(behaviors.len(), 1);
355 let out = behaviors[0]
356 .handle(7, string_terminal())
357 .await
358 .expect("typed behavior must run");
359 assert_eq!(out, "N=8");
360
361 assert!(
362 provider
363 .get_all::<dyn IPipelineBehavior<u64, String>>()
364 .is_empty(),
365 "a typed behavior must only attach to its own request/response pair"
366 );
367 }
368
369 struct TickBehavior {
370 ticks: Arc<AtomicU32>,
371 }
372
373 #[async_trait::async_trait]
374 impl IPipelineBehavior<u32, u32> for TickBehavior {
375 async fn handle(&self, req: u32, next: Next<u32, u32>) -> crate::error::Result<u32> {
376 self.ticks.fetch_add(1, Ordering::SeqCst);
377 next.run(req).await
378 }
379 }
380
381 #[tokio::test]
382 async fn typed_add_pipeline_with_constructor_injects_from_the_resolver() {
383 let ticks = Arc::new(AtomicU32::new(0));
384
385 let provider = ServiceCollection::new()
386 .instance(Arc::clone(&ticks))
387 .add_typed_pipeline_with(|resolver| {
388 let ticks: Arc<AtomicU32> = resolve::<AtomicU32>(resolver);
389 Arc::new(TickBehavior { ticks }) as Arc<dyn IPipelineBehavior<u32, u32>>
390 })
391 .build()
392 .expect("provider must build");
393
394 let behavior = &provider.get_all::<dyn IPipelineBehavior<u32, u32>>()[0];
395 let out = behavior
396 .handle(5, Next::new(|n: u32| async move { Ok(n * 2) }))
397 .await
398 .expect("typed behavior must run");
399 assert_eq!(out, 10);
400 assert_eq!(ticks.load(Ordering::SeqCst), 1);
401 }
402
403 struct ShortCircuit;
404
405 #[async_trait::async_trait]
406 impl IPipelineBehavior<u32, String> for ShortCircuit {
407 async fn handle(
408 &self,
409 _req: u32,
410 _next: Next<u32, String>,
411 ) -> crate::error::Result<String> {
412 Ok("short".to_string())
413 }
414 }
415
416 #[tokio::test]
417 async fn typed_behavior_may_short_circuit_with_its_response_type() {
418 let out = ShortCircuit
419 .handle(1, string_terminal())
420 .await
421 .expect("short-circuit must return the typed response");
422 assert_eq!(out, "short");
423 }
424}