lifeloop/router/seams.rs
1//! Trait seams for the router stages.
2//!
3//! These traits (introduced with the router skeleton in issue #7)
4//! name the integration shape; each is now implemented by a concrete
5//! type in a sibling module, re-exported through `super` (`mod.rs`):
6//!
7//! * [`CallbackInvoker`] — invoke a client callback for a routed
8//! event. Implemented by `DefaultCallbackInvoker` (in-process,
9//! `callbacks.rs`) and `SubprocessCallbackInvoker` (process
10//! boundary, `subprocess.rs`). The trait method signature takes a
11//! [`RoutingPlan`] plus opaque [`PayloadEnvelope`]s so an
12//! implementation can destructure without this module growing a
13//! `protocol` dependency.
14//! * [`ReceiptEmitter`] — persist or forward the
15//! [`crate::LifecycleReceipt`] produced by a dispatch. Implemented by
16//! `LifeloopReceiptEmitter` in `receipts.rs`.
17//! * [`FailureMapper`] — turn a [`super::RouteError`] (or a
18//! downstream stage's error) into the [`crate::FailureClass`] /
19//! [`crate::RetryClass`] pair the receipt carries. Implemented by
20//! `LifeloopFailureMapper` in `failure_mapping.rs`.
21
22use crate::{CallbackResponse, FailureClass, LifecycleReceipt, PayloadEnvelope, RetryClass};
23
24use super::plan::RoutingPlan;
25use super::validation::RouteError;
26
27/// Callback invocation seam.
28///
29/// Implemented by `DefaultCallbackInvoker` (in-process, `callbacks.rs`)
30/// and `SubprocessCallbackInvoker` (process boundary, `subprocess.rs`).
31/// The signature takes a [`RoutingPlan`] plus the optional
32/// [`PayloadEnvelope`]s the caller is delivering and lets the
33/// implementation destructure, so the seam stays free of any
34/// `protocol` dependency.
35///
36/// The [`PayloadEnvelope::body`] is treated as opaque bytes/value —
37/// implementations must not parse it.
38pub trait CallbackInvoker {
39 /// Error produced by the callback transport. Kept generic so the
40 /// in-process and subprocess impls (and any future network impl)
41 /// can each carry their own diagnostic shape.
42 type Error;
43
44 /// Invoke the client callback for a routed event.
45 ///
46 /// `payloads` carries any [`PayloadEnvelope`]s the caller is
47 /// delivering alongside the event. The router does not inspect
48 /// payload bodies; the invoker may, but is not required to.
49 fn invoke(
50 &self,
51 plan: &RoutingPlan,
52 payloads: &[PayloadEnvelope],
53 ) -> Result<CallbackResponse, Self::Error>;
54}
55
56/// Receipt emission seam.
57///
58/// `LifeloopReceiptEmitter` (in `receipts.rs`) synthesizes the
59/// [`crate::LifecycleReceipt`] and routes it through an idempotency
60/// store; this trait method validates and stores an already-built
61/// receipt.
62pub trait ReceiptEmitter {
63 /// Error produced by the receipt sink.
64 type Error;
65
66 /// Emit (persist, forward, or otherwise hand off) a
67 /// [`LifecycleReceipt`].
68 fn emit(&self, receipt: &LifecycleReceipt) -> Result<(), Self::Error>;
69}
70
71/// Failure-class mapping seam.
72///
73/// `LifeloopFailureMapper` (in `failure_mapping.rs`) maps
74/// [`RouteError`] (and downstream stage errors) onto the
75/// [`FailureClass`] / [`RetryClass`] pair carried on a `status=failed`
76/// receipt.
77pub trait FailureMapper {
78 /// Map a [`RouteError`] to a `(failure_class, retry_class)`
79 /// pair. Implementations must be deterministic — the same
80 /// [`RouteError`] variant must always map to the same pair so
81 /// receipt ledgers replay consistently.
82 fn map_route_error(&self, err: &RouteError) -> (FailureClass, RetryClass);
83}