Skip to main content

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}