1use std::{fmt, sync::Arc};
4
5use crate::{BoxFuture, Command, CommandHandler, Query, QueryHandler, SoapResult};
6
7pub trait CommandDispatcher<C>: Send + Sync
13where
14 C: Command,
15{
16 fn dispatch(&self, command: C) -> BoxFuture<'_, SoapResult<C::Output>>;
18}
19
20impl<C, H> CommandDispatcher<C> for H
21where
22 C: Command,
23 H: CommandHandler<C> + ?Sized,
24{
25 fn dispatch(&self, command: C) -> BoxFuture<'_, SoapResult<C::Output>> {
26 self.command(command)
27 }
28}
29
30pub trait QueryDispatcher<Q>: Send + Sync
34where
35 Q: Query,
36{
37 fn dispatch(&self, query: Q) -> BoxFuture<'_, SoapResult<Q::Output>>;
39}
40
41impl<Q, H> QueryDispatcher<Q> for H
42where
43 Q: Query,
44 H: QueryHandler<Q> + ?Sized,
45{
46 fn dispatch(&self, query: Q) -> BoxFuture<'_, SoapResult<Q::Output>> {
47 self.query(query)
48 }
49}
50
51pub struct CommandNext<'a, C>
57where
58 C: Command + 'static,
59{
60 middleware: &'a [Arc<dyn CommandMiddleware<C>>],
61 handler: &'a dyn CommandHandler<C>,
62}
63
64impl<C> fmt::Debug for CommandNext<'_, C>
65where
66 C: Command + 'static,
67{
68 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
69 formatter
70 .debug_struct("CommandNext")
71 .field("remaining_middleware", &self.middleware.len())
72 .finish_non_exhaustive()
73 }
74}
75
76impl<'a, C> CommandNext<'a, C>
77where
78 C: Command + 'static,
79{
80 pub fn run(self, command: C) -> BoxFuture<'a, SoapResult<C::Output>> {
82 if let Some((middleware, remaining)) = self.middleware.split_first() {
83 middleware.handle(
84 command,
85 Self {
86 middleware: remaining,
87 handler: self.handler,
88 },
89 )
90 } else {
91 self.handler.command(command)
92 }
93 }
94}
95
96pub trait CommandMiddleware<C>: Send + Sync
98where
99 C: Command + 'static,
100{
101 fn handle<'a>(
103 &'a self,
104 command: C,
105 next: CommandNext<'a, C>,
106 ) -> BoxFuture<'a, SoapResult<C::Output>>;
107}
108
109pub struct CommandPipeline<C>
116where
117 C: Command + 'static,
118{
119 handler: Arc<dyn CommandHandler<C>>,
120 middleware: Vec<Arc<dyn CommandMiddleware<C>>>,
121}
122
123impl<C> fmt::Debug for CommandPipeline<C>
124where
125 C: Command + 'static,
126{
127 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
128 formatter
129 .debug_struct("CommandPipeline")
130 .field("middleware", &self.middleware.len())
131 .finish_non_exhaustive()
132 }
133}
134
135impl<C> CommandPipeline<C>
136where
137 C: Command + 'static,
138{
139 pub fn new(handler: Arc<dyn CommandHandler<C>>) -> Self {
141 Self {
142 handler,
143 middleware: Vec::new(),
144 }
145 }
146
147 #[must_use]
149 pub fn with_middleware(mut self, middleware: Arc<dyn CommandMiddleware<C>>) -> Self {
150 self.middleware.push(middleware);
151 self
152 }
153
154 pub fn push_middleware(&mut self, middleware: Arc<dyn CommandMiddleware<C>>) {
156 self.middleware.push(middleware);
157 }
158
159 pub fn middleware_count(&self) -> usize {
161 self.middleware.len()
162 }
163}
164
165impl<C> CommandHandler<C> for CommandPipeline<C>
166where
167 C: Command + 'static,
168{
169 fn command(&self, command: C) -> BoxFuture<'_, SoapResult<C::Output>> {
170 CommandNext {
171 middleware: &self.middleware,
172 handler: self.handler.as_ref(),
173 }
174 .run(command)
175 }
176}
177
178pub struct QueryNext<'a, Q>
180where
181 Q: Query + 'static,
182{
183 middleware: &'a [Arc<dyn QueryMiddleware<Q>>],
184 handler: &'a dyn QueryHandler<Q>,
185}
186
187impl<Q> fmt::Debug for QueryNext<'_, Q>
188where
189 Q: Query + 'static,
190{
191 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
192 formatter
193 .debug_struct("QueryNext")
194 .field("remaining_middleware", &self.middleware.len())
195 .finish_non_exhaustive()
196 }
197}
198
199impl<'a, Q> QueryNext<'a, Q>
200where
201 Q: Query + 'static,
202{
203 pub fn run(self, query: Q) -> BoxFuture<'a, SoapResult<Q::Output>> {
205 if let Some((middleware, remaining)) = self.middleware.split_first() {
206 middleware.handle(
207 query,
208 Self {
209 middleware: remaining,
210 handler: self.handler,
211 },
212 )
213 } else {
214 self.handler.query(query)
215 }
216 }
217}
218
219pub trait QueryMiddleware<Q>: Send + Sync
221where
222 Q: Query + 'static,
223{
224 fn handle<'a>(
226 &'a self,
227 query: Q,
228 next: QueryNext<'a, Q>,
229 ) -> BoxFuture<'a, SoapResult<Q::Output>>;
230}
231
232pub struct QueryPipeline<Q>
234where
235 Q: Query + 'static,
236{
237 handler: Arc<dyn QueryHandler<Q>>,
238 middleware: Vec<Arc<dyn QueryMiddleware<Q>>>,
239}
240
241impl<Q> fmt::Debug for QueryPipeline<Q>
242where
243 Q: Query + 'static,
244{
245 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
246 formatter
247 .debug_struct("QueryPipeline")
248 .field("middleware", &self.middleware.len())
249 .finish_non_exhaustive()
250 }
251}
252
253impl<Q> QueryPipeline<Q>
254where
255 Q: Query + 'static,
256{
257 pub fn new(handler: Arc<dyn QueryHandler<Q>>) -> Self {
259 Self {
260 handler,
261 middleware: Vec::new(),
262 }
263 }
264
265 #[must_use]
267 pub fn with_middleware(mut self, middleware: Arc<dyn QueryMiddleware<Q>>) -> Self {
268 self.middleware.push(middleware);
269 self
270 }
271
272 pub fn push_middleware(&mut self, middleware: Arc<dyn QueryMiddleware<Q>>) {
274 self.middleware.push(middleware);
275 }
276
277 pub fn middleware_count(&self) -> usize {
279 self.middleware.len()
280 }
281}
282
283impl<Q> QueryHandler<Q> for QueryPipeline<Q>
284where
285 Q: Query + 'static,
286{
287 fn query(&self, query: Q) -> BoxFuture<'_, SoapResult<Q::Output>> {
288 QueryNext {
289 middleware: &self.middleware,
290 handler: self.handler.as_ref(),
291 }
292 .run(query)
293 }
294}
295
296#[cfg(test)]
297mod tests {
298 use std::{
299 future::Future,
300 sync::{Arc, Mutex},
301 task::{Context, Poll, Waker},
302 };
303
304 use crate::{
305 BoxFuture, Command, CommandDispatcher, CommandHandler, CommandMiddleware, CommandNext,
306 CommandPipeline, Query, QueryDispatcher, QueryHandler, QueryMiddleware, QueryNext,
307 QueryPipeline, SoapError, SoapErrorKind, SoapResult,
308 };
309
310 fn block_on<F>(future: F) -> F::Output
311 where
312 F: Future,
313 {
314 let mut context = Context::from_waker(Waker::noop());
315 let mut future = Box::pin(future);
316 loop {
317 match future.as_mut().poll(&mut context) {
318 Poll::Ready(output) => return output,
319 Poll::Pending => std::thread::yield_now(),
320 }
321 }
322 }
323
324 #[derive(Debug)]
325 struct Add(i32);
326
327 impl Command for Add {
328 type Output = i32;
329 }
330
331 struct AddHandler {
332 trace: Arc<Mutex<Vec<&'static str>>>,
333 }
334
335 impl CommandHandler<Add> for AddHandler {
336 fn command(&self, command: Add) -> BoxFuture<'_, SoapResult<i32>> {
337 Box::pin(async move {
338 self.trace
339 .lock()
340 .map_err(|_| SoapError::infrastructure("command test trace lock poisoned"))?
341 .push("handler");
342 Ok(command.0)
343 })
344 }
345 }
346
347 struct AroundCommand {
348 before: &'static str,
349 after: &'static str,
350 add: i32,
351 trace: Arc<Mutex<Vec<&'static str>>>,
352 }
353
354 impl CommandMiddleware<Add> for AroundCommand {
355 fn handle<'a>(
356 &'a self,
357 command: Add,
358 next: CommandNext<'a, Add>,
359 ) -> BoxFuture<'a, SoapResult<i32>> {
360 Box::pin(async move {
361 self.trace
362 .lock()
363 .map_err(|_| SoapError::infrastructure("command test trace lock poisoned"))?
364 .push(self.before);
365 let output = next.run(command).await?;
366 self.trace
367 .lock()
368 .map_err(|_| SoapError::infrastructure("command test trace lock poisoned"))?
369 .push(self.after);
370 Ok(output + self.add)
371 })
372 }
373 }
374
375 #[test]
376 fn command_pipeline_preserves_types_and_around_order() {
377 let trace = Arc::new(Mutex::new(Vec::new()));
378 let handler: Arc<dyn CommandHandler<Add>> = Arc::new(AddHandler {
379 trace: Arc::clone(&trace),
380 });
381 let pipeline = CommandPipeline::new(handler)
382 .with_middleware(Arc::new(AroundCommand {
383 before: "outer-before",
384 after: "outer-after",
385 add: 1,
386 trace: Arc::clone(&trace),
387 }))
388 .with_middleware(Arc::new(AroundCommand {
389 before: "inner-before",
390 after: "inner-after",
391 add: 10,
392 trace: Arc::clone(&trace),
393 }));
394
395 assert_eq!(pipeline.middleware_count(), 2);
396 assert_eq!(block_on(pipeline.dispatch(Add(5))).ok(), Some(16));
397 assert_eq!(
398 trace.lock().ok().map(|items| items.clone()),
399 Some(vec![
400 "outer-before",
401 "inner-before",
402 "handler",
403 "inner-after",
404 "outer-after"
405 ])
406 );
407 }
408
409 struct RejectCommand;
410
411 impl CommandMiddleware<Add> for RejectCommand {
412 fn handle<'a>(
413 &'a self,
414 _command: Add,
415 _next: CommandNext<'a, Add>,
416 ) -> BoxFuture<'a, SoapResult<i32>> {
417 Box::pin(async { Err(SoapError::validation("command rejected by middleware")) })
418 }
419 }
420
421 #[test]
422 fn command_middleware_can_short_circuit_the_handler() {
423 let trace = Arc::new(Mutex::new(Vec::new()));
424 let handler: Arc<dyn CommandHandler<Add>> = Arc::new(AddHandler {
425 trace: Arc::clone(&trace),
426 });
427 let pipeline = CommandPipeline::new(handler).with_middleware(Arc::new(RejectCommand));
428
429 let result = block_on(pipeline.dispatch(Add(5)));
430 assert_eq!(
431 result.as_ref().map_err(SoapError::kind),
432 Err(SoapErrorKind::Validation)
433 );
434 assert_eq!(trace.lock().ok().map(|items| items.is_empty()), Some(true));
435 }
436
437 struct Double(i32);
438
439 impl Query for Double {
440 type Output = i32;
441 }
442
443 struct DoubleHandler;
444
445 impl QueryHandler<Double> for DoubleHandler {
446 fn query(&self, query: Double) -> BoxFuture<'_, SoapResult<i32>> {
447 Box::pin(async move { Ok(query.0 * 2) })
448 }
449 }
450
451 struct AddToQuery(i32);
452
453 impl QueryMiddleware<Double> for AddToQuery {
454 fn handle<'a>(
455 &'a self,
456 query: Double,
457 next: QueryNext<'a, Double>,
458 ) -> BoxFuture<'a, SoapResult<i32>> {
459 Box::pin(async move { Ok(next.run(query).await? + self.0) })
460 }
461 }
462
463 #[test]
464 fn query_pipeline_and_direct_handlers_share_typed_dispatch() {
465 assert_eq!(block_on(DoubleHandler.dispatch(Double(4))).ok(), Some(8));
466
467 let handler: Arc<dyn QueryHandler<Double>> = Arc::new(DoubleHandler);
468 let mut pipeline = QueryPipeline::new(handler);
469 pipeline.push_middleware(Arc::new(AddToQuery(3)));
470 assert_eq!(pipeline.middleware_count(), 1);
471 assert_eq!(block_on(pipeline.dispatch(Double(4))).ok(), Some(11));
472 }
473}