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