1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//! The low-level routing contract.
use crateRouteDecision;
use crateSpiError;
use crateRequestCtx;
/// Decides where and how a single request is routed.
///
/// This is the low-level contract for a single routing *decision*: full control
/// over the destination and the transforms. Most implementers instead provide a
/// [`crate::TenancySpi`], which `osproxy-tenancy` adapts into a `RoutingSpi`.
///
/// Note this yields only a [`RouteDecision`]. The engine pipeline needs more than
/// a decision (the resolved partition, epoch, and migration phase, to construct
/// ids, demux bulk, and gate writes), so it is generic over the richer
/// `osproxy_tenancy::Router` seam rather than this trait. Implement `Router` to
/// drive the engine with custom routing; implement `RoutingSpi` where only a
/// `RouteDecision` is required.
///
/// # Invariants
///
/// - MUST resolve to exactly one [`Target`](osproxy_core::Target), no
/// synchronous fan-out in v1 (ADR-002).
/// - MUST NOT panic; return [`SpiError`] for every failure (NFR-R1).
/// - The returned [`RouteDecision::epoch`] MUST come from the placement state
/// the decision was derived from, so the sink can detect a stale-epoch write
/// during a migration (`docs/06` §2).
///
/// The engine drives implementations through generics (monomorphized, no dyn
/// dispatch on the hot path), so the future's `Send`-ness is checked at the
/// spawn site.
///
/// # Examples
///
/// ```
/// use osproxy_core::{ClusterId, Epoch, IndexName, Target};
/// use osproxy_spi::{Protocol, RequestCtx, RouteDecision, RoutingSpi, SpiError};
///
/// struct PinToOne;
///
/// impl RoutingSpi for PinToOne {
/// async fn route(&self, _ctx: &RequestCtx<'_>) -> Result<RouteDecision, SpiError> {
/// let target = Target::new(ClusterId::from("only"), IndexName::from("logs"));
/// Ok(RouteDecision::passthrough(target, Protocol::Http1, Epoch::ZERO))
/// }
/// }
/// ```