JrResponse

Struct JrResponse 

Source
pub struct JrResponse<T> { /* private fields */ }
Expand description

Represents a pending response of type R from an outgoing request.

Returned by JrConnectionCx::send_request, this type provides methods for handling the response without blocking the event loop. The API is intentionally designed to make it difficult to accidentally block.

§Anti-Footgun Design

You cannot directly .await a JrResponse. Instead, you must choose how to handle the response:

§Option 1: Schedule a Callback (Safe in Handlers)

Use await_when_result_received to schedule a task that runs when the response arrives. This doesn’t block the event loop:

cx.send_request(MyRequest {})
    .await_when_result_received(async |result| {
        match result {
            Ok(response) => {
                // Handle successful response
                Ok(())
            }
            Err(error) => {
                // Handle error
                Err(error)
            }
        }
    })?;

§Option 2: Block in a Spawned Task (Safe Only in spawn)

Use block_task to block until the response arrives, but only in a spawned task (never in a handler):

// ✅ Safe: Spawned task runs concurrently
cx.spawn({
    let cx = cx.clone();
    async move {
        let response = cx.send_request(MyRequest {})
            .block_task()
            .await?;
        // Process response...
        Ok(())
    }
})?;
// ❌ NEVER do this in a handler - blocks the event loop!
connection.on_receive_request(async |req: MyRequest, request_cx, cx| {
    let response = cx.send_request(MyRequest {})
        .block_task()  // This will deadlock!
        .await?;
    request_cx.respond(response)
})

§Why This Design?

If you block the event loop while waiting for a response, the connection cannot process the incoming response message, creating a deadlock. This API design prevents that footgun by making blocking explicit and encouraging non-blocking patterns.

Implementations§

Source§

impl<T: JrResponsePayload> JrResponse<T>

Source

pub fn method(&self) -> &str

The method of the request this is in response to.

Source

pub fn map<U>( self, map_fn: impl Fn(T) -> Result<U, Error> + 'static + Send, ) -> JrResponse<U>

Create a new response that maps the result of the response to a new type.

Source

pub fn forward_to_request_cx( self, request_cx: JrRequestCx<T>, ) -> Result<(), Error>
where T: Send,

Forward the response (success or error) to a request context when it arrives.

This is a convenience method for proxying messages between connections. When the response arrives, it will be automatically sent to the provided request context, whether it’s a successful response or an error.

§Example: Proxying requests
// Set up 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 backend and get a context to send to it
let backend_cx = cx.spawn_connection(backend, |c| Box::pin(c.serve()))?;

// Set up proxy that forwards requests to backend
UntypedRole::builder()
    .on_receive_request({
        let backend_cx = backend_cx.clone();
        async move |req: MyRequest, request_cx, cx| {
            // Forward the request to backend and proxy the response back
            backend_cx.send_request(req)
                .forward_to_request_cx(request_cx)?;
            Ok(())
        }
    });
§Type Safety

The request context’s response type must match the request’s response type, ensuring type-safe message forwarding.

§When to Use

Use this when:

  • You’re implementing a proxy or gateway pattern
  • You want to forward responses without processing them
  • The response types match between the outgoing request and incoming request

This is equivalent to calling await_when_result_received and manually forwarding the result, but more concise.

Source

pub async fn block_task(self) -> Result<T, Error>
where T: Send,

Block the current task until the response is received.

Warning: This method blocks the current async task. It is only safe to use in spawned tasks created with JrConnectionCx::spawn. Using it directly in a handler callback will deadlock the connection.

§Safe Usage (in spawned tasks)
connection.on_receive_request(async |req: MyRequest, request_cx, cx| {
    // Spawn a task to handle the request
    cx.spawn({
        let connection_cx = cx.clone();
        async move {
            // Safe: We're in a spawned task, not blocking the event loop
            let response = connection_cx.send_request(OtherRequest {})
                .block_task()
                .await?;

            // Process the response...
            Ok(())
        }
    })?;

    // Respond immediately
    request_cx.respond(MyResponse { status: "ok".into() })
})
§Unsafe Usage (in handlers - will deadlock!)
connection.on_receive_request(async |req: MyRequest, request_cx, cx| {
    // ❌ DEADLOCK: Handler blocks event loop, which can't process the response
    let response = cx.send_request(OtherRequest {})
        .block_task()
        .await?;

    request_cx.respond(MyResponse { status: response.value })
})
§When to Use

Use this method when:

  • You’re in a spawned task (via JrConnectionCx::spawn)
  • You need the response value to proceed with your logic
  • Linear control flow is more natural than callbacks

For handler callbacks, use await_when_result_received instead.

Source

pub fn await_when_ok_response_received<F>( self, request_cx: JrRequestCx<T>, task: impl FnOnce(T, JrRequestCx<T>) -> F + 'static + Send, ) -> Result<(), Error>
where F: Future<Output = Result<(), Error>> + 'static + Send, T: Send,

Schedule an async task to run when a successful response is received.

This is a convenience wrapper around await_when_result_received for the common pattern of forwarding errors to a request context while only processing successful responses.

§Behavior
  • If the response is Ok(value), your task receives the value and the request context
  • If the response is Err(error), the error is automatically sent to request_cx and your task is not called
§Example: Chaining requests
connection.on_receive_request(async |req: ValidateRequest, request_cx, cx| {
    // Send initial request
    cx.send_request(ValidateRequest { data: req.data.clone() })
        .await_when_ok_response_received(request_cx, async |validation, request_cx| {
            // Only runs if validation succeeded
            if validation.is_valid {
                // Respond to original request
                request_cx.respond(ValidateResponse { is_valid: true, error: None })
            } else {
                request_cx.respond_with_error(sacp::util::internal_error("validation failed"))
            }
        })?;

    Ok(())
})
§When to Use

Use this when:

  • You need to respond to a request based on another request’s result
  • You want errors to automatically propagate to the request context
  • You only care about the success case

For more control over error handling, use await_when_result_received.

Source

pub fn await_when_result_received<F>( self, task: impl FnOnce(Result<T, Error>) -> F + 'static + Send, ) -> Result<(), Error>
where F: Future<Output = Result<(), Error>> + 'static + Send, T: Send,

Schedule an async task to run when the response is received.

This is the recommended way to handle responses in handler callbacks, as it doesn’t block the event loop. The task will be spawned automatically when the response arrives.

§Example: Handle response in callback
connection.on_receive_request(async |req: MyRequest, request_cx, cx| {
    // Send a request and schedule a callback for the response
    cx.send_request(QueryRequest { id: 22 })
        .await_when_result_received({
            let connection_cx = cx.clone();
            async move |result| {
                match result {
                    Ok(response) => {
                        println!("Got response: {:?}", response);
                        // Can send more messages here
                        connection_cx.send_notification(QueryComplete {})?;
                        Ok(())
                }
                    Err(error) => {
                        eprintln!("Request failed: {}", error);
                        Err(error)
                    }
                }
            }
        })?;

    // Handler continues immediately without waiting
    request_cx.respond(MyResponse { status: "processing".into() })
})
§Event Loop Safety

Unlike block_task, this method is safe to use in handlers because it schedules the task to run later rather than blocking the current task. The event loop remains free to process messages, including the response itself.

§Error Handling

If the scheduled task returns Err, the entire server will shut down. Make sure to handle errors appropriately within your task.

§When to Use

Use this method when:

  • You’re in a handler callback (not a spawned task)
  • You want to process the response asynchronously
  • You don’t need the response value immediately

For spawned tasks where you need linear control flow, consider block_task.

Auto Trait Implementations§

§

impl<T> Freeze for JrResponse<T>

§

impl<T> !RefUnwindSafe for JrResponse<T>

§

impl<T> Send for JrResponse<T>

§

impl<T> !Sync for JrResponse<T>

§

impl<T> Unpin for JrResponse<T>

§

impl<T> !UnwindSafe for JrResponse<T>

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