Skip to main content

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}