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>
impl<T: JrResponsePayload> JrResponse<T>
Sourcepub fn map<U>(
self,
map_fn: impl Fn(T) -> Result<U, Error> + 'static + Send,
) -> JrResponse<U>
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.
Sourcepub fn forward_to_request_cx(
self,
request_cx: JrRequestCx<T>,
) -> Result<(), Error>where
T: Send,
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.
Sourcepub async fn block_task(self) -> Result<T, Error>where
T: Send,
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.
Sourcepub fn await_when_ok_response_received<F>(
self,
request_cx: JrRequestCx<T>,
task: impl FnOnce(T, JrRequestCx<T>) -> F + 'static + Send,
) -> Result<(), Error>
pub fn await_when_ok_response_received<F>( self, request_cx: JrRequestCx<T>, task: impl FnOnce(T, JrRequestCx<T>) -> F + 'static + Send, ) -> Result<(), Error>
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 torequest_cxand 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.
Sourcepub fn await_when_result_received<F>(
self,
task: impl FnOnce(Result<T, Error>) -> F + 'static + Send,
) -> Result<(), Error>
pub fn await_when_result_received<F>( self, task: impl FnOnce(Result<T, Error>) -> F + 'static + Send, ) -> Result<(), Error>
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.