Skip to main content

webx_core/mediator/
dispatch.rs

1//! Shared request dispatch used by `Mediator::send` and HTTP endpoint adapters.
2//!
3//! Looks up the `HandlerRegistration` (collected via `#[handler]`) by request
4//! type, creates a per-request DI scope, runs the pipeline (typed behaviors
5//! inside, legacy erased behaviors outside), and invokes the handler.
6
7// The legacy type-erased pipeline is deprecated but exercised on purpose here.
8#![allow(deprecated)]
9
10use std::any::Any;
11use std::sync::Arc;
12
13use rust_dix::ScopeFactory;
14
15use crate::dispatch_runtime::dispatch_handler_cache;
16use crate::error::{Error, Result};
17use crate::mediator::pipeline::{build_chain as build_erased_chain, build_typed_chain};
18use crate::mediator::IRequest;
19use crate::pipeline::{
20    BoxedNextFn, BoxedPipelineFuture, IErasedPipelineBehavior, IPipelineBehavior,
21};
22
23/// Dispatch a request through the handler registry, pipeline, and call bridge.
24pub async fn dispatch<T, R>(provider: &Arc<rust_dix::ServiceProvider>, req: T) -> Result<R>
25where
26    T: IRequest<R> + Send + 'static,
27    R: Send + 'static,
28{
29    let req_type_id = std::any::TypeId::of::<T>();
30
31    let cache = dispatch_handler_cache();
32    let entry = cache.get_by_type_id(req_type_id).ok_or_else(|| {
33        Error::Di(format!(
34            "No #[handler] registered for request type '{}'",
35            std::any::type_name::<T>()
36        ))
37    })?;
38
39    let scope = provider.create_scope();
40    let resolver: &dyn rust_dix::IServiceResolver = &scope;
41    let handler = (entry.factory)(resolver)?;
42
43    // Typed behaviors are resolved per (request, response) pair; the erased ones
44    // are still supported for one release and run as the outer chain.
45    let typed_behaviors: Vec<Arc<dyn IPipelineBehavior<T, R>>> =
46        scope.get_all::<dyn IPipelineBehavior<T, R>>();
47    #[allow(deprecated)]
48    let erased_behaviors: Vec<Arc<dyn IErasedPipelineBehavior>> =
49        scope.get_all::<dyn IErasedPipelineBehavior>();
50
51    let entry_call = entry.call;
52
53    // Inner chain: handler terminal, wrapped by typed behaviors. The handler is
54    // invoked through the compile-time call bridge, so its response arrives
55    // erased and is downcast back to `R` here — once, before typed behaviors see
56    // it. Typed behaviors themselves are compiler-checked against `R`.
57    let typed_chain = build_typed_chain::<T, R>(typed_behaviors, move |typed_req: T| {
58        Box::pin(async move {
59            let out = (entry_call)(handler, Box::new(typed_req) as Box<dyn Any + Send>).await?;
60            out.downcast::<R>().map(|value| *value).map_err(|_| {
61                Error::Internal(format!(
62                    "Handler for `{}` returned a different response type than `{}`",
63                    std::any::type_name::<T>(),
64                    std::any::type_name::<R>()
65                ))
66            })
67        })
68    });
69
70    // Outer chain: the legacy erased behaviors receive the request boxed and
71    // hand it to the typed chain through the terminal below.
72    #[allow(deprecated)]
73    let erased_terminal: BoxedNextFn =
74        Box::new(move |req: Box<dyn Any + Send>| -> BoxedPipelineFuture {
75            Box::pin(async move {
76                let typed_req = *req.downcast::<T>().map_err(|_| {
77                    Error::Internal(format!(
78                        "Pipeline request type mismatch: expected `{}`",
79                        std::any::type_name::<T>()
80                    ))
81                })?;
82                let out = typed_chain.run(typed_req).await?;
83                Ok(Box::new(out) as Box<dyn Any + Send>)
84            })
85        });
86
87    #[allow(deprecated)]
88    let erased_chain = build_erased_chain(erased_behaviors, erased_terminal);
89    let result_box = erased_chain(Box::new(req)).await?;
90    Ok(*result_box.downcast::<R>().map_err(|_| {
91        // Reached when a legacy erased behavior returned a value of a different
92        // type than `R`. Typed behaviors cannot cause this: the compiler checks
93        // their response type.
94        Error::Internal(format!(
95            "Response type mismatch: the pipeline returned a different type than the handler \
96             expects (expected `{}`). A legacy `IErasedPipelineBehavior` must not short-circuit \
97             with a different response type; return the handler's response type or call `next`. \
98             Typed `IPipelineBehavior<Req, Rsp>` values are checked at compile time.",
99            std::any::type_name::<R>()
100        ))
101    })?)
102}