Skip to content

rpc-service

A typed RPC server using shared-memory FBMU as the transport. Client serializes a EchoRequest to a leased queue; server pulls, dispatches, returns EchoResponse on the response queue. No TLS, no HTTP — the lease is the trust boundary.

Source

cookbook/rpc-service/ in the source tree.

The recipe is the host-testable handler: take a typed EchoRequest, return a typed EchoResponse. In production, grafos-rpc::service macro generates the request/response wire types, server adapter, and client adapter.

#[serde(tag = "method", content = "params", rename_all = "snake_case")]
pub enum EchoRequest {
Ping,
Echo { message: String },
Reverse { message: String },
}
pub fn handle(req: EchoRequest) -> EchoResponse {
match req {
EchoRequest::Ping => EchoResponse::Pong,
EchoRequest::Echo { message } => {
if message.len() > 1024 {
EchoResponse::Error { reason: "message_too_large".into() }
} else {
EchoResponse::Echoed { message }
}
}
EchoRequest::Reverse { message } => {
EchoResponse::Reversed { message: message.chars().rev().collect() }
}
}
}

What’s interesting

  1. Tagged JSON discriminator. #[serde(tag = "method", content = "params")] produces wire shapes like {"method":"echo","params":{"message":"hi"}}. Versioning a method is renaming it; old clients see a typed Error { reason: "method_not_supported" }.
  2. Errors are values. EchoResponse::Error { reason } is part of the response enum, not an out-of-band error path. The client deserializes one variant or another; there’s no “is the response valid?” check separate from “what response is it?”
  3. Bounded request sizes. Echo rejects messages over 1024 bytes; the cap is part of the API contract, not a runtime fluke.
  4. Production drop-in. Replace the manual match with #[grafos_rpc::service] on a trait that has one method per EchoRequest variant. The macro generates the dispatch + queue plumbing.