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.
Trait Implementations§
Source§impl<Ctx, R> Default for AsyncDispatcher<Ctx, R>
impl<Ctx, R> Default for AsyncDispatcher<Ctx, R>
Source§fn default() -> AsyncDispatcher<Ctx, R>
fn default() -> AsyncDispatcher<Ctx, R>
Auto Trait Implementations§
impl<Ctx, R> !RefUnwindSafe for AsyncDispatcher<Ctx, R>
impl<Ctx, R> !UnwindSafe for AsyncDispatcher<Ctx, R>
impl<Ctx, R> Freeze for AsyncDispatcher<Ctx, R>
impl<Ctx, R> Send for AsyncDispatcher<Ctx, R>
impl<Ctx, R> Sync for AsyncDispatcher<Ctx, R>
impl<Ctx, R> Unpin for AsyncDispatcher<Ctx, R>
impl<Ctx, R> UnsafeUnpin for AsyncDispatcher<Ctx, R>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> BorrowUnordered for T
impl<T> BorrowUnordered for T
fn as_unordered(&self) -> &Unordered<T>
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> Clear for Twhere
T: InitializableFromZeroed + ?Sized,
impl<T> Clear for Twhere
T: InitializableFromZeroed + ?Sized,
Source§impl<T> InitializableFromZeroed for Twhere
T: Default,
impl<T> InitializableFromZeroed for Twhere
T: Default,
Source§unsafe fn initialize(place: *mut T)
unsafe fn initialize(place: *mut T)
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::RequestSource§impl<T, U, C> IntoWithContext<U, C> for Twhere
U: FromWithContext<T, C>,
impl<T, U, C> IntoWithContext<U, C> for Twhere
U: FromWithContext<T, C>,
Source§impl<T> Pointable for T
impl<T> Pointable for T
Source§impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
Source§impl<T> ResourceProvider<()> for T
impl<T> ResourceProvider<()> for T
Source§fn get_resource(&self) -> &()
fn get_resource(&self) -> &()
T.