JrConnectionCx

Struct JrConnectionCx 

Source
pub struct JrConnectionCx<Role: JrRole> { /* private fields */ }
Expand description

Trait for types that can provide transport for JSON-RPC messages.

Implementations of this trait bridge between the internal protocol channels (which carry jsonrpcmsg::Message) and the actual I/O mechanism (byte streams, in-process channels, network sockets, etc.).

The transport layer is responsible only for moving jsonrpcmsg::Message in and out. It has no knowledge of protocol semantics like request/response correlation, ID assignment, or handler dispatch - those are handled by the protocol layer in JrConnection.

§Example

See ByteStreams for the standard byte stream implementation. Connection context for sending messages and spawning tasks.

This is the primary handle for interacting with the JSON-RPC connection from within handler callbacks. You can use it to:

  • Send requests and notifications to the other side
  • Spawn concurrent tasks that run alongside the connection
  • Respond to requests (via JrRequestCx which wraps this)

§Cloning

JrConnectionCx is cheaply cloneable - all clones refer to the same underlying connection. This makes it easy to share across async tasks.

§Event Loop and Concurrency

Handler callbacks run on the event loop, which means the connection cannot process new messages while your handler is running. Use spawn to offload any expensive or blocking work to concurrent tasks.

See the Event Loop and Concurrency section for more details.

Implementations§

Source§

impl<Role: JrRole> JrConnectionCx<Role>

Source

pub fn spawn( &self, task: impl IntoFuture<Output = Result<(), Error>, IntoFuture: Send + 'static>, ) -> Result<(), Error>

Spawns a task that will run so long as the JSON-RPC connection is being served.

This is the primary mechanism for offloading expensive work from handler callbacks to avoid blocking the event loop. Spawned tasks run concurrently with the connection, allowing the server to continue processing messages.

§Event Loop

Handler callbacks run on the event loop, which cannot process new messages while your handler is running. Use spawn for any expensive operations:

connection.on_receive_request(async |req: ProcessRequest, request_cx, cx| {
    // Clone cx for the spawned task
    cx.spawn({
        let connection_cx = cx.clone();
        async move {
            let result = expensive_operation(&req.data).await?;
            connection_cx.send_notification(ProcessComplete { result })?;
            Ok(())
        }
    })?;

    // Respond immediately
    request_cx.respond(ProcessResponse { result: "started".into() })
})
§Errors

If the spawned task returns an error, the entire server will shut down.

Source

pub fn spawn_connection<H: JrMessageHandler>( &self, connection: JrConnection<H>, serve_future: impl FnOnce(JrConnection<H>) -> BoxFuture<'static, Result<(), Error>>, ) -> Result<JrConnectionCx<H::Role>, Error>

Spawn a JSON-RPC connection in the background and return a JrConnectionCx for sending messages to it.

This is useful for creating multiple connections that communicate with each other, such as implementing proxy patterns or connecting to multiple backend services.

§Arguments
  • connection: The JrConnection to spawn (typically created via JrConnectionBuilder::connect_to())
  • serve_future: A function that drives the connection (usually |c| Box::pin(c.serve()))
§Returns

A JrConnectionCx that you can use to send requests and notifications to the spawned connection.

§Example: Proxying to a backend connection
// Set up a backend connection
let backend = UntypedRole::builder()
    .on_receive_request(async |req: MyRequest, request_cx, _cx| {
        request_cx.respond(MyResponse { status: "ok".into() })
    })
    .connect_to(MockTransport)?;

// Spawn it and get a context to send requests to it
let backend_cx = cx.spawn_connection(backend, |c| Box::pin(c.serve()))?;

// Now you can forward requests to the backend
let response = backend_cx.send_request(MyRequest {}).block_task().await?;
Source

pub fn send_proxied_message_to<End: JrEndpoint, Req: JrRequest<Response: Send>, Notif: JrNotification>( &self, end: End, message: MessageCx<Req, Notif>, ) -> Result<(), Error>
where Role: HasEndpoint<End>,

Send a request/notification and forward the response appropriately.

The request context’s response type matches the request’s response type, enabling type-safe message forwarding.

Source

pub fn send_request<Req: JrRequest>( &self, request: Req, ) -> JrResponse<Req::Response>

Send an outgoing request and return a JrResponse for handling the reply.

The returned JrResponse provides methods for receiving the response without blocking the event loop:

  • await_when_result_received - Schedule a callback to run when the response arrives (doesn’t block the event loop)
  • block_task - Block the current task until the response arrives (only safe in spawned tasks, not in handlers)
§Anti-Footgun Design

The API intentionally makes it difficult to block on the result directly to prevent the common mistake of blocking the event loop while waiting for a response:

// ❌ This doesn't compile - prevents blocking the event loop
let response = cx.send_request(MyRequest {}).await?;
// ✅ Option 1: Schedule callback (safe in handlers)
cx.send_request(MyRequest {})
    .await_when_result_received(async |result| {
        // Handle the response
        Ok(())
    })?;

// ✅ Option 2: Block in spawned task (safe because task is concurrent)
cx.spawn({
    let cx = cx.clone();
    async move {
        let response = cx.send_request(MyRequest {})
            .block_task()
            .await?;
        // Process response...
        Ok(())
    }
})?;

Send an outgoing request to the default counterpart role.

This is a convenience method that uses the connection’s remote role. For explicit control over the target role, use send_request_to.

Source

pub fn send_request_to<End: JrEndpoint, Req: JrRequest>( &self, end: End, request: Req, ) -> JrResponse<Req::Response>
where Role: HasEndpoint<End>,

Send an outgoing request to a specific counterpart role.

The message will be transformed according to the role’s SendsToRole implementation before being sent.

Source

pub fn send_notification<N: JrNotification>( &self, notification: N, ) -> Result<(), Error>

Send an outgoing notification to the default counterpart role (no reply expected).

Notifications are fire-and-forget messages that don’t have IDs and don’t expect responses. This method sends the notification immediately and returns.

This is a convenience method that uses the role’s default counterpart. For explicit control over the target role, use send_notification_to.

cx.send_notification(StatusUpdate {
    message: "Processing...".into(),
})?;
Source

pub fn send_notification_to<End: JrEndpoint, N: JrNotification>( &self, end: End, notification: N, ) -> Result<(), Error>
where Role: HasEndpoint<End>,

Send an outgoing notification to a specific counterpart role (no reply expected).

The message will be transformed according to the role’s SendsToRole implementation before being sent.

Source

pub fn send_error_notification(&self, error: Error) -> Result<(), Error>

Send an error notification (no reply expected).

Source

pub fn add_dynamic_handler( &self, handler: impl JrMessageHandlerSend<Role = Role> + 'static, ) -> Result<DynamicHandlerRegistration<Role>, Error>

Register a dynamic message handler, used to intercept messages specific to a particular session or some similar modal thing.

Dynamic message handlers are called first for every incoming message.

If they decline to handle the message, then the message is passed to the regular handler chain.

The handler will stay registered until the [DynamicHandlerRegistration] is dropped.

Trait Implementations§

Source§

impl<Role: Clone + JrRole> Clone for JrConnectionCx<Role>

Source§

fn clone(&self) -> JrConnectionCx<Role>

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<Role: Debug + JrRole> Debug for JrConnectionCx<Role>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<Role> Freeze for JrConnectionCx<Role>
where Role: Freeze,

§

impl<Role> !RefUnwindSafe for JrConnectionCx<Role>

§

impl<Role> Send for JrConnectionCx<Role>

§

impl<Role> Sync for JrConnectionCx<Role>

§

impl<Role> Unpin for JrConnectionCx<Role>
where Role: Unpin,

§

impl<Role> !UnwindSafe for JrConnectionCx<Role>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more