Client APIs¶
Client is the synchronous TCP client; AsyncClient is the tokio equivalent. Both speak the same protocol and expose the same set of helpers.
Connecting and Calling¶
use repe::Client;
use serde::{Deserialize, Serialize};
use serde_json::json;
#[derive(Serialize)]
struct AddReq { a: i64, b: i64 }
#[derive(Deserialize)]
struct AddResp { sum: i64 }
let client = Client::connect("127.0.0.1:8081")?;
let pong = client.call_json("/ping", &json!({}))?;
assert_eq!(pong["pong"], true);
let typed: AddResp = client.call_typed_json("/add", &AddReq { a: 2, b: 3 })?;
assert_eq!(typed.sum, 5);
client.notify_typed_json("/jobs/refresh", &AddReq { a: 0, b: 0 })?;
client.notify_typed_beve("/jobs/refresh_beve", &AddReq { a: 1, b: 2 })?;
# Ok::<(), Box<dyn std::error::Error>>(())
BEVE typed helpers work the same way:
let beve_sum: AddResp = client.call_typed_beve("/add", &AddReq { a: 4, b: 5 })?;
assert_eq!(beve_sum.sum, 9);
# Ok::<(), Box<dyn std::error::Error>>(())
Async Client¶
use repe::{AsyncClient, AsyncServer, Router};
use serde::{Deserialize, Serialize};
use serde_json::json;
# #[tokio::main]
# async fn main() -> std::io::Result<()> {
let router = Router::new().with("/ping", |_v| Ok(json!({"pong": true})));
let listener = AsyncServer::listen(("127.0.0.1", 0)).await?;
let addr = listener.local_addr()?;
tokio::spawn(async move { let _ = AsyncServer::new(router).serve(listener).await; });
#[derive(Serialize)]
struct AddReq { a: i64, b: i64 }
#[derive(Deserialize)]
struct AddResp { sum: i64 }
let client = AsyncClient::connect(addr).await.unwrap();
let pong = client.call_json("/ping", &json!({})).await.unwrap();
assert_eq!(pong["pong"], true);
let typed: AddResp = client
.call_typed_json("/add", &AddReq { a: 8, b: 1 })
.await
.unwrap();
assert_eq!(typed.sum, 9);
# Ok(()) }
Registry Helpers¶
For servers that mount a Registry (see registry.md):
- Empty-body READs:
registry_read("/api/v1/counter")orcall_message(...) - Typed READs:
registry_read_typed::<_, MyType>(...) - JSON WRITE/CALL:
registry_write_json(...)andregistry_call_json(...) - Custom wire formats:
call_with_formats(...)andnotify_with_formats(...)
Multiplexing, Timeouts, and Batching¶
Both Client and AsyncClient can run multiple in-flight requests on a single connection. Clone the client handle and issue calls concurrently; responses are matched by request id even if the server replies out of order.
Per-call timeout helpers:
call_json_with_timeoutcall_typed_json_with_timeoutcall_typed_beve_with_timeout
Batch helpers for JSON calls:
batch_json(Vec<(String, Value)>)batch_json_with_timeout(Vec<(String, Value)>, Duration)
Notify Semantics¶
A request marked notify = true is processed by the server but produces no response. The client id still increments, but no matching response is expected.
Client::notify_json/AsyncClient::notify_jsonset the flag forserde_json::Valuebodies.Client::notify_typed_json/notify_typed_beveand their async counterparts send typed payloads without waiting for a response, mirroring the call helpers.
Error Handling¶
Errors are returned in-band as responses with ec != Ok and a UTF-8 body describing the error. Common mappings:
- Header parse/validation issues:
InvalidHeaderorParseError. - JSON serialization or deserialization issues:
ParseError. - Missing routes:
MethodNotFoundwith the requested path in the message. - Application failures from handlers: return
(ErrorCode, String)to control both fields.
Clients surface mismatched protocol versions as RepeError::VersionMismatch. Responses with unknown request IDs are logged and dropped by default, including late responses for timed-out requests. For bounded latency, prefer *_with_timeout APIs so a dropped response ID cannot leave a call waiting indefinitely.