pub trait RoutingSpi:
Send
+ Sync
+ 'static {
// Required method
async fn route(
&self,
ctx: &RequestCtx<'_>,
) -> Result<RouteDecision, SpiError>;
}Expand description
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, no synchronous fan-out in v1 (ADR-002). - MUST NOT panic; return
SpiErrorfor every failure (NFR-R1). - The returned
RouteDecision::epochMUST 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))
}
}Required Methods§
Sourceasync fn route(&self, ctx: &RequestCtx<'_>) -> Result<RouteDecision, SpiError>
async fn route(&self, ctx: &RequestCtx<'_>) -> Result<RouteDecision, SpiError>
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".