cratefield_core/ports/dispatcher.rs
1//! The `Dispatcher` port (ADR 0009): forwards a request to the Worker that
2//! serves a sidecar-mounted module.
3//!
4//! Deliberately **not** a [`Port`](super::Port) variant, so it never appears in
5//! a module's `requires()` or `optional()` and `view_for` can never hand it to
6//! one. Only the harness router dispatches; a module that could reach this
7//! trait could call any sidecar the venture has bound, which is not a
8//! capability any module has a reason to hold.
9
10use async_trait::async_trait;
11use bytes::Bytes;
12use thiserror::Error;
13
14#[derive(Debug, Clone, Error)]
15pub enum DispatchError {
16 /// The runtime has no binding of that name. Discovered on the first
17 /// request that needs it, never at build time: `HarnessBuilder::build`
18 /// has no `Env` to look in (ADR 0009).
19 #[error("no service binding named `{0}`")]
20 NotBound(String),
21 /// The binding exists but the sidecar did not answer.
22 #[error("sidecar `{binding}` did not answer: {reason}")]
23 Unavailable { binding: String, reason: String },
24}
25
26#[async_trait]
27pub trait Dispatcher: Send + Sync {
28 /// Whether a binding of this name exists. Cheap; called per request before
29 /// dispatching so a missing binding degrades one prefix rather than
30 /// surfacing as a transport error.
31 fn has(&self, binding: &str) -> bool;
32
33 /// Forward `request` to the bound Worker and return its response
34 /// unaltered. Implementations must not retry: a sidecar call is inside
35 /// the caller's request, and a retry would double any side effect.
36 async fn dispatch(
37 &self,
38 binding: &str,
39 request: http::Request<Bytes>,
40 ) -> Result<http::Response<Bytes>, DispatchError>;
41}