Skip to main content

BridgePeer

Trait BridgePeer 

Source
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§

Source

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.

Source

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.

Source

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§

Source

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.

Source

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.

Source

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.

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>>

§Errors

Whatever the inner peer reports.

Source§

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.

Source§

fn send( &self, envelope: Map<String, Value>, ) -> SendFuture<'_, Result<Map<String, Value>, BridgeError>>

Source§

fn close(&self) -> SendFuture<'_, Result<(), BridgeError>>

Source§

fn wait_pending(&self) -> SendFuture<'static, ()>

Source§

fn listen(&self) -> SendFuture<'_, Result<(), BridgeError>>

Implementors§

Source§

impl BridgePeer for A2aJsonRpcPeer

Source§

impl BridgePeer for HttpClient

Carries envelopes over one HTTP client’s session.

Source§

impl BridgePeer for LegacySsePeer

Source§

impl BridgePeer for TransportPeer

Source§

impl<DW> BridgePeer for StdioPeer<DW>
where DW: AsyncWrite + Unpin + Send + 'static,

Source§

impl<U: BridgePeer + ?Sized> BridgePeer for CrossBridgePeer<U>