Skip to main content

AsyncDispatcher

Struct AsyncDispatcher 

Source
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 → P conversion happens once, inside the routing table lookup — not once per arm of an if doc.type_uri == type_uri_of::<P>() chain that re-parses P::TYPE_URI and re-serialises the document for every spec it does not match.

  • unsupportedType vs unsupportedVersion. Registering a handler records the Type URI and its slug. A document whose slug is known at a MAJOR.MINOR nobody registered is RejectReason::UnsupportedVersion (SPEC §5.2 / §8.3); only an unknown slug is RejectReason::UnsupportedType. A hand-rolled match on the whole URI string cannot tell the two apart, so it answers unsupportedType to 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 Payload flags are reachable — the dispatcher applies TrustTask::enforce_spec_policy, covering §7.2 items 5b (recipient REQUIRED), 7A (proof REQUIRED) and 8 (audience binding). This is the same method consume_inbound and 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>

Source

pub fn new() -> AsyncDispatcher<Ctx, R>

Build an empty AsyncDispatcher. Add handlers with Self::on_async.

Source

pub fn on_async<P, F, Fut>(self, handler: F) -> AsyncDispatcher<Ctx, R>
where P: Payload + 'static, F: Fn(TrustTask<P>, Ctx) -> Fut + Send + Sync + 'static, Fut: Future<Output = R> + Send + 'static, R: Send + 'static,

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.

Source

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’s MAJOR.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 against P.
  • any RejectReason TrustTask::enforce_spec_policy raises — ProofRequired most often.
Source

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.

Source

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>

Source§

fn default() -> AsyncDispatcher<Ctx, R>

Returns the “default value” for a type. Read more

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>
where HashMap<String, Box<dyn Fn(TrustTask<Value>, Ctx) -> Pin<Box<dyn Future<Output = Result<R, RejectReason>> + Send>> + Sync + Send>>: Freeze,

§

impl<Ctx, R> Send for AsyncDispatcher<Ctx, R>
where HashMap<String, Box<dyn Fn(TrustTask<Value>, Ctx) -> Pin<Box<dyn Future<Output = Result<R, RejectReason>> + Send>> + Sync + Send>>: Send,

§

impl<Ctx, R> Sync for AsyncDispatcher<Ctx, R>
where HashMap<String, Box<dyn Fn(TrustTask<Value>, Ctx) -> Pin<Box<dyn Future<Output = Result<R, RejectReason>> + Send>> + Sync + Send>>: Sync,

§

impl<Ctx, R> Unpin for AsyncDispatcher<Ctx, R>
where HashMap<String, Box<dyn Fn(TrustTask<Value>, Ctx) -> Pin<Box<dyn Future<Output = Result<R, RejectReason>> + Send>> + Sync + Send>>: Unpin,

§

impl<Ctx, R> UnsafeUnpin for AsyncDispatcher<Ctx, R>
where HashMap<String, Box<dyn Fn(TrustTask<Value>, Ctx) -> Pin<Box<dyn Future<Output = Result<R, RejectReason>> + Send>> + Sync + Send>>: UnsafeUnpin,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> At for T

Source§

fn at<M>(self, metadata: M) -> Meta<T, M>

Wraps self inside a Meta<Self, M> using the given metadata. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> BorrowStripped for T

Source§

fn stripped(&self) -> &Stripped<T>

Source§

impl<T> BorrowUnordered for T

Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> Clear for T

Source§

fn clear(&mut self)

Completely overwrites this value.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, C> FromWithContext<T, C> for T

Source§

fn from_with(value: T, _context: &C) -> T

Source§

impl<T> InitializableFromZeroed for T
where T: Default,

Source§

unsafe fn initialize(place: *mut T)

Called to initialize a place to a valid value, after it is set to all-bits-zero. Read more
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoRequest<T> for T

Source§

fn into_request(self) -> Request<T>

Wrap the input message T in a tonic::Request
Source§

impl<T, U, C> IntoWithContext<U, C> for T
where U: FromWithContext<T, C>,

Source§

fn into_with(self, context: &C) -> U

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> ResourceProvider<()> for T

Source§

fn get_resource(&self) -> &()

Returns a reference to the resource of type T.
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T

Source§

type Owned = T

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U, C> TryFromWithContext<U, C> for T
where U: IntoWithContext<T, C>,

Source§

type Error = !

Source§

fn try_from_with( value: U, context: &C, ) -> Result<T, <T as TryFromWithContext<U, C>>::Error>

Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T, U, C> TryIntoWithContext<U, C> for T
where U: TryFromWithContext<T, C>,

Source§

type Error = <U as TryFromWithContext<T, C>>::Error

Source§

fn try_into_with( self, context: &C, ) -> Result<U, <T as TryIntoWithContext<U, C>>::Error>

Source§

impl<S, T> Upcast<T> for S
where T: UpcastFrom<S> + ?Sized, S: ?Sized,

Source§

fn upcast(&self) -> &T
where Self: ErasableGeneric, T: Sized + ErasableGeneric<Repr = Self::Repr>,

Perform a zero-cost type-safe upcast to a wider ref type within the Wasm bindgen generics type system. Read more
Source§

fn upcast_into(self) -> T
where Self: Sized + ErasableGeneric, T: Sized + ErasableGeneric<Repr = Self::Repr>,

Perform a zero-cost type-safe upcast to a wider type within the Wasm bindgen generics type system. Read more
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithContext for T
where T: ?Sized,

Source§

fn with<C>(&self, context: C) -> Contextual<&T, C>

Source§

fn into_with<C>(self, context: C) -> Contextual<T, C>
where T: Sized,

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more