pub trait BridgePeer: Send + Sync {
// Required methods
fn send(
&self,
envelope: Map<String, Value>,
) -> SendFuture<'_, Result<Map<String, Value>, BridgeError>>;
fn notify(
&self,
envelope: Map<String, Value>,
) -> SendFuture<'_, Result<(), BridgeError>>;
fn close(&self) -> SendFuture<'_, Result<(), BridgeError>>;
// Provided methods
fn take_pending(&self) -> Vec<Map<String, Value>> { ... }
fn wait_pending(&self) -> SendFuture<'static, ()> { ... }
fn listen(&self) -> SendFuture<'_, Result<(), BridgeError>> { ... }
}Expand description
One side of the bridge, at envelope granularity.
Implementors hold a real transport. A test implementor holds a recorder.
§Example
use serde_json::{Map, Value};
use protocol_droid::application::bridge::{BridgeError, BridgePeer, SendFuture};
#[derive(Default)]
struct Recorder(std::sync::Mutex<Vec<Map<String, Value>>>);
impl BridgePeer for Recorder {
fn send(
&self,
envelope: Map<String, Value>,
) -> SendFuture<'_, Result<Map<String, Value>, BridgeError>> {
Box::pin(async move {
// Answer with the request's own id, which is what a peer does.
let mut reply = Map::new();
reply.insert("jsonrpc".into(), Value::String("2.0".into()));
reply.insert(
"id".into(),
envelope.get("id").cloned().unwrap_or(Value::Null),
);
reply.insert("result".into(), Value::Object(Map::new()));
Ok(reply)
})
}
fn notify(
&self,
_envelope: Map<String, Value>,
) -> SendFuture<'_, Result<(), BridgeError>> {
Box::pin(async { Ok(()) })
}
fn close(&self) -> SendFuture<'_, Result<(), BridgeError>> {
Box::pin(async { Ok(()) })
}
}
runtime.block_on(async {
let peer = Recorder::default();
let request: Map<String, Value> = serde_json::from_str(
r#"{"jsonrpc":"2.0","id":1,"method":"ping"}"#,
)?;
peer.send(request).await?;
Ok::<(), Box<dyn std::error::Error>>(())
})?;Required Methods§
Sourcefn send(
&self,
envelope: Map<String, Value>,
) -> SendFuture<'_, Result<Map<String, Value>, BridgeError>>
fn send( &self, envelope: Map<String, Value>, ) -> SendFuture<'_, Result<Map<String, Value>, BridgeError>>
Delivers one envelope and yields the peer’s answer.
§Errors
A transport failure — the peer could not be reached, or the connection
closed. A protocol refusal comes back as a normal envelope with an
error member, not as an Err: those are two different things and a
bridge that merged them would report a network fault for a request the
server simply declined.
Returns a boxed future rather than being an async fn. Native
async fn in a trait is RPITIT and not object-safe, and this port has
to be usable as Arc<dyn BridgePeer> — the adapter holds a live
connection whose lifetime the bridge does not know. A synchronous
signature would be worse still: every real peer is an async socket, so
it would force a blocking wait on the runtime thread.
Sourcefn notify(
&self,
envelope: Map<String, Value>,
) -> SendFuture<'_, Result<(), BridgeError>>
fn notify( &self, envelope: Map<String, Value>, ) -> SendFuture<'_, Result<(), BridgeError>>
Delivers one envelope that expects no answer.
Separate from send because a notification has no id
and a conforming peer answers one with 202 and no body. A send that
waited for a reply would wait forever, and a send that did not wait
would have to invent the empty envelope it handed back — which is a
response the server never sent. notifications/initialized is one of
these, so a peer that could not notify had no way to finish connecting.
§Errors
BridgeError::Unreachable when the request could not be delivered.
Nothing else is possible: there is no reply to be wrong about.
Sourcefn close(&self) -> SendFuture<'_, Result<(), BridgeError>>
fn close(&self) -> SendFuture<'_, Result<(), BridgeError>>
Ends the conversation, flushing anything in flight.
§Errors
A transport failure on shutdown. Nothing is retried: a peer that has already been told to stop is not going to change its mind.
Provided Methods§
Sourcefn take_pending(&self) -> Vec<Map<String, Value>>
fn take_pending(&self) -> Vec<Map<String, Value>>
Frames the peer received that are the answer to nothing the bridge asked.
A server pushes. notifications/tools/list_changed,
notifications/message, notifications/progress and the rest arrive on
a stream the bridge is already reading, with no request of its own behind
them, and they belong to the client as much as a reply does — a client
whose server registers a tool mid-session learns about it through this,
or not at all. A bridge that forwards requests and drops these does not
translate versions; it silently deletes half the protocol.
Draining rather than subscribing, because the peers that hold frames
already hold them in the read loop they were written for and a second
consumer would have to fight that loop for the socket. StdioPeer and
LegacySsePeer are the two that have anywhere to put them.
§The default is a true answer, not a shrug
Empty is what a peer that only ever hears the answer to its own request
has, and HttpClient
is exactly that: it opens no standing stream, so a frame the server
volunteers has nowhere to arrive. An adapter that does buffer must
override this, and the symptom of forgetting is a client watching a tool
appear upstream and never hearing about it.
Sourcefn wait_pending(&self) -> SendFuture<'static, ()>
fn wait_pending(&self) -> SendFuture<'static, ()>
Resolves once the peer may have something for
take_pending.
§Why the default never resolves
A peer that opens no standing stream learns of a frame only while a call is in flight, so by the time a caller could wait there is nothing new and a wake-up would be a lie. Returning a future that never resolves leaves the caller exactly where it was. An adapter that does read out of band overrides this, and a caller waiting on it stops depending on the client speaking first.
'static rather than borrowing: the caller polls this alongside a
future borrowing its own source, and a borrow here would pin the peer
for the whole of that pair.
Sourcefn listen(&self) -> SendFuture<'_, Result<(), BridgeError>>
fn listen(&self) -> SendFuture<'_, Result<(), BridgeError>>
Starts delivering frames the peer pushes on its own.
§Why this is separate from wait_pending
Waiting and reading are different promises. A peer that only learns of a frame mid-call has nothing to wait for; a peer whose transport carries an out-of-band stream has to actually open it, and the caller cannot tell which it was handed. So the caller asks once, after the peer exists, and a peer that needs nothing says so.
The default succeeds without doing anything, which is the true answer for a peer that pushes only in response to a call.
§Errors
BridgeError::Unreachable when the out-of-band channel could not be
opened. Reported rather than swallowed: a caller that believes it is
listening and is not is a caller silently dropping traffic.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".
Implementations on Foreign Types§
Source§impl<T: BridgePeer + ?Sized> BridgePeer for Arc<T>
A shared peer.
impl<T: BridgePeer + ?Sized> BridgePeer for Arc<T>
A shared peer.
Blanket rather than per-adapter because sharing a peer is not a special
capability — it is the same forwarding, reached through a refcount. An
adapter that needs the peer behind an Arc for its own lifetime reasons
would otherwise have to write this impl itself, once per adapter, and get it
subtly different each time.
Source§fn notify(
&self,
envelope: Map<String, Value>,
) -> SendFuture<'_, Result<(), BridgeError>>
fn notify( &self, envelope: Map<String, Value>, ) -> SendFuture<'_, Result<(), BridgeError>>
§Errors
Whatever the inner peer reports.
Source§fn take_pending(&self) -> Vec<Map<String, Value>>
fn take_pending(&self) -> Vec<Map<String, Value>>
Forwarded, and not left to the trait default.
Without this the queue is never drained through an Arc, while
wait_pending still resolves the moment anything is held — so the loop
wakes, finds nothing, and wakes again. That is a spin at full speed
rather than a stall, which is the worst of the two: it starves the
runtime and the frames it was woken for are never delivered.
fn send( &self, envelope: Map<String, Value>, ) -> SendFuture<'_, Result<Map<String, Value>, BridgeError>>
fn close(&self) -> SendFuture<'_, Result<(), BridgeError>>
fn wait_pending(&self) -> SendFuture<'static, ()>
fn listen(&self) -> SendFuture<'_, Result<(), BridgeError>>
Implementors§
impl BridgePeer for A2aJsonRpcPeer
impl BridgePeer for HttpClient
Carries envelopes over one HTTP client’s session.