pub trait IPipelineBehavior: Send + Sync {
// Required method
fn handle<'life0, 'async_trait>(
&'life0 self,
req: Box<dyn Any + Send>,
next: Box<dyn FnOnce(Box<dyn Any + Send>) -> Pin<Box<dyn Future<Output = Result<Box<dyn Any + Send>, Error>> + Send>> + Send>,
) -> Pin<Box<dyn Future<Output = Result<Box<dyn Any + Send>, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait,
Self: 'async_trait;
}Expand description
Pipeline behavior that wraps around request handling.
Multiple behaviors form a chain. Each behavior can:
- Inspect or modify the request before passing it on
- Inspect or modify the response after it returns
- Short-circuit and skip the rest of the chain (by not calling
next)
Analogous to MediatR’s IPipelineBehavior<TRequest, TResponse>.
§Lifetime and dependencies
Behaviors are registered as singletons: one instance per host, shared by
every request. The DI resolver is available only while the behavior is being
constructed — use IServiceCollectionExt::add_pipeline_with and take
your dependencies in the factory closure:
svc.add_pipeline_with(|resolver| {
// `&dyn IServiceResolver` is object-safe: resolve type-erased.
let clock: Arc<Clock> = resolver
.get_any(std::any::type_name::<Clock>())
.and_then(|service| service.downcast::<Arc<Clock>>().ok())
.map(|service| Arc::clone(&*service))
.expect("register Clock before the pipeline");
Arc::new(TimingBehavior { clock }) as Arc<dyn IPipelineBehavior>
})Inside IPipelineBehavior::handle there is no resolver and no typed
request/response: the request and the eventual response are
Box<dyn Any + Send>. Cross-cutting mechanics (timing, logging, cancellation)
belong here; business rules that need the typed request belong in the handler
or a use case it calls. IServiceCollectionExt::add_pipeline remains the
zero-dependency convenience registration for Default behaviors.
§Response type contract
A behavior that short-circuits must return the same response type the
handler would return (downcast to it happens after the chain, before the
caller sees the value). Short-circuiting with a different type — for example
Box::new(()) — fails with an explicit type-mismatch error instead of
silently succeeding.
Required Methods§
fn handle<'life0, 'async_trait>(
&'life0 self,
req: Box<dyn Any + Send>,
next: Box<dyn FnOnce(Box<dyn Any + Send>) -> Pin<Box<dyn Future<Output = Result<Box<dyn Any + Send>, Error>> + Send>> + Send>,
) -> Pin<Box<dyn Future<Output = Result<Box<dyn Any + Send>, Error>> + Send + 'async_trait>>where
'life0: 'async_trait,
Self: 'async_trait,
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".