pub struct AsyncDispatcher<Ctx, R> { /* private fields */ }Expand description
Routes a TrustTask<Value> to an async handler registered for
its Type URI, carrying a caller-supplied context value alongside it.
The async, context-carrying sibling of Dispatcher.
Dispatcher::on takes Fn(TrustTask<P>) -> R: synchronous, and with
nowhere to put the request-scoped state a real handler needs. A handler
that must await a database, a DID resolution or an approval prompt
cannot be written against it, which is why every receiver in the wild
hand-rolls its own router instead — and why none of them get
unsupportedType vs unsupportedVersion right.
AsyncDispatcher is a sibling, not a replacement: Dispatcher is
unchanged and stays the right tool for a synchronous match.
use trust_tasks_rs::{specs::acl, AsyncDispatcher};
let dispatcher = AsyncDispatcher::<AppState, Outcome>::new()
.on_async::<acl::grant::v0_1::Payload, _, _>(|req, ctx| async move {
ctx.db.record_grant(&req.payload.entry).await;
Outcome::Granted
})
.on_async::<acl::revoke::v0_1::Payload, _, _>(|req, ctx| async move {
ctx.db.revoke(&req.payload.subject).await;
Outcome::Revoked
});
let outcome = dispatcher.dispatch(inbound, state.clone()).await?;§What falls out by construction
-
One downcast per message. The
Value → Pconversion happens once, inside the routing table lookup — not once per arm of anif doc.type_uri == type_uri_of::<P>()chain that re-parsesP::TYPE_URIand re-serialises the document for every spec it does not match. -
unsupportedTypevsunsupportedVersion. Registering a handler records the Type URI and its slug. A document whose slug is known at aMAJOR.MINORnobody registered isRejectReason::UnsupportedVersion(SPEC §5.2 / §8.3); only an unknown slug isRejectReason::UnsupportedType. A hand-rolledmatchon the whole URI string cannot tell the two apart, so it answersunsupportedTypeto a producer whose real problem is that it needs to downgrade. -
The typed §7.2 checks. After the downcast — the first moment the codegen-emitted
Payloadflags are reachable — the dispatcher appliesTrustTask::enforce_spec_policy, covering §7.2 items 5b (recipientREQUIRED), 7A (proofREQUIRED) and 8 (audience binding). This is the same methodconsume_inboundand the HTTPS server call, so the three cannot diverge. It is applied to request documents only: a#response-variant URI routes straight to its handler, because those items govern what a consumer demands of an inbound request.The checks that do not need the payload type — expiry, recipient match, the §4.8.1 transport cross-check, proof verification, the §7.2 item 11 duplicate-execution record — are unchanged and still belong to
consume_inbound, which a handler can call around its own body.
§Shape
Ctx is whatever the caller wants each handler to receive alongside the
document: a connection pool, the transport’s authenticated sender, a
request-scoped span. It is passed by value, so Arc<AppState> is the
usual choice. R is the handler’s return type, uniform across handlers
exactly as it is for Dispatcher — commonly
Result<TrustTask<Value>, ErrorResponse> for a consumer that emits a
response document, or () for a fire-and-forget receiver.
The type is Send + Sync whatever Ctx and R are (handlers are held
behind Send + Sync trait objects), so it can live in an Arc on
shared state and be dispatched from any tokio task.
Implementations§
Source§impl<Ctx, R> AsyncDispatcher<Ctx, R>
impl<Ctx, R> AsyncDispatcher<Ctx, R>
Sourcepub fn new() -> AsyncDispatcher<Ctx, R>
pub fn new() -> AsyncDispatcher<Ctx, R>
Build an empty AsyncDispatcher. Add handlers with
Self::on_async.
Sourcepub fn on_async<P, F, Fut>(self, handler: F) -> AsyncDispatcher<Ctx, R>
pub fn on_async<P, F, Fut>(self, handler: F) -> AsyncDispatcher<Ctx, R>
Register an async handler for the Type URI declared by P.
The handler receives the downcast TrustTask<P> and the Ctx
value supplied to Self::dispatch, and returns any future
resolving to R — so it can await whatever it needs to.
Routing matches Dispatcher::on: lookup is
against the canonical form
(TypeUri::for_routing), so a
producer emitting either the bare URI or the #request-fragmented
form per SPEC.md §4.4.1 item 1 reaches the same handler, while
#response-fragmented URIs stay distinct. Registering the same
canonical Type URI twice replaces the earlier handler.
Before the handler runs, a request-variant document is checked with
TrustTask::enforce_spec_policy — see the type-level docs.
Sourcepub async fn dispatch(
&self,
doc: TrustTask<Value>,
ctx: Ctx,
) -> Result<R, RejectReason>
pub async fn dispatch( &self, doc: TrustTask<Value>, ctx: Ctx, ) -> Result<R, RejectReason>
Route doc to the handler registered for its type URI, passing
ctx alongside it, and await the result.
Returns:
Ok(R)— handler invoked successfully.Err(RejectReason::UnsupportedVersion)— the slug is registered but not at this Type URI’sMAJOR.MINOR(SPEC §5.2 / §8.3).Err(RejectReason::UnsupportedType)— the slug is not registered at all.Err(RejectReason::MalformedRequest)— the URI matched but the payload failed to deserialize againstP.- any
RejectReasonTrustTask::enforce_spec_policyraises —ProofRequiredmost often.
Sourcepub async fn dispatch_or_reject(
&self,
doc: TrustTask<Value>,
ctx: Ctx,
error_id: impl Into<String>,
) -> Result<R, TrustTask<ErrorPayload>>
pub async fn dispatch_or_reject( &self, doc: TrustTask<Value>, ctx: Ctx, error_id: impl Into<String>, ) -> Result<R, TrustTask<ErrorPayload>>
Route doc to the registered handler, returning either the
handler’s value or an ErrorResponse already routed per SPEC.md
§8.1.
The async mirror of
Dispatcher::dispatch_or_reject;
see it for the routing rationale. error_id supplies the id for
the error response — UUIDv4 is the recommended default (SPEC.md
§4.3).
None of the rejections this method can produce carry the
identity_mismatch transport-routing exception, so the §8.1 safe
default (address the original producer) applies to all of them.
ErrorResponse is intentionally large; boxing it in the Err
variant would push the allocation onto every caller.
Sourcepub fn registered_uris(&self) -> Vec<&str>
pub fn registered_uris(&self) -> Vec<&str>
The Type URIs this dispatcher currently routes for, in canonical
form and sorted for stable output. Handy for an unsupportedType
error response that lists what the consumer does implement.