macro_rules! rpc_service { ( Request = $request:ident; Response = $response:ident; Service = $service:ident; CreateDispatch = $create_dispatch:tt; $($m_pattern:ident $m_name:ident = $m_input:ident, $m_update:tt -> $m_output:ident);+$(;)? ) => { ... }; }
Expand description
Derive a set of RPC types and message implementation from a declaration.
The macros are completely optional. They generate the request and response message enums and the service zerosized struct. Optionally, a function can be created to dispatch RPC calls to methods on a struct of your choice. It can also create a type-safe RPC client for the service.
Usage is as follows:
// Define your message types
#[derive(Debug, Serialize, Deserialize)]
struct Add(pub i32, pub i32);
#[derive(Debug, Serialize, Deserialize)]
pub struct Sum(pub i32);
#[derive(Debug, Serialize, Deserialize)]
pub struct Multiply(pub i32);
#[derive(Debug, Serialize, Deserialize)]
pub struct MultiplyUpdate(pub i32);
#[derive(Debug, Serialize, Deserialize)]
pub struct MultiplyOutput(pub i32);
// Derive the RPC types.
rpc_service! {
// Name of the created request enum.
Request = MyRequest;
// Name of the created response enum.
Response = MyResponse;
// Name of the created service struct enum.
Service = MyService;
// Name of the macro to create a dispatch function.
// Optional, if not needed pass _ (underscore) as name.
CreateDispatch = create_my_dispatch;
// Name of the macro to create an RPC client.
Rpc add = Add, _ -> Sum;
BidiStreaming multiply = Multiply, MultiplyUpdate -> MultiplyOutput
}
This will generate a request enum MyRequest
, a response enum MyRespone
and a service declaration MyService
.
It will also generate two macros to create an RPC client and a dispatch function.
To use the client, invoke the macro with a name. The macro will generate a struct that takes a client channel and exposes typesafe methods for each RPC method.
create_store_client!(MyClient);
let client = quic_rpc::quinn::Channel::new(client);
let client = quic_rpc::client::RpcClient::<MyService, _>::new(client);
let mut client = MyClient(client);
let sum = client.add(Add(3, 4)).await?;
// Sum(7)
let (send, mut recv) = client.multiply(Multiply(2));
send(Update(3));
let res = recv.next().await?;
// Some(MultiplyOutput(6))
To use the dispatch function, invoke the macro with a struct that implements your RPC methods and the name of the generated function. You can then use this dispatch function to dispatch the RPC calls to the methods on your target struct.
#[derive(Clone)]
pub struct Calculator;
impl Calculator {
async fn add(self, req: Add) -> Sum {
Sum(req.0 + req.1)
}
async fn multiply(
self,
req: Multiply,
updates: impl Stream<Item = MultiplyUpdate>
) -> impl Stream<Item = MultiplyOutput> {
stream! {
tokio::pin!(updates);
while let Some(MultiplyUpdate(n)) = updates.next().await {
yield MultiplyResponse(req.0 * n);
}
}
}
}
create_my_dispatch!(Calculator, dispatch_calculator_request);
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let server_addr: std::net::SocketAddr = "127.0.0.1:12345".parse()?;
let (server, _server_certs) = make_server_endpoint(server_addr)?;
let accept = server.accept().await.context("accept failed")?.await?;
let connection = quic_rpc::quinn::Channel::new(accept);
let calculator = Calculator;
let server_handle = spawn_server(
StoreService,
quic_rpc::quinn::QuinnChannelTypes,
connection,
calculator,
dispatch_calculator_request,
);
server_handle.await??;
Ok(())
}
The generation of the macros in CreateDispatch
and CreateClient
is optional. If you don’t need them, pass _
instead:
rpc_service! {
Request = MyRequest;
Response = MyResponse;
Service = MyService;
CreateDispatch = _;
CreateClient = _;
Rpc add = Add, _ -> Sum;
ClientStreaming stream = Input, Update -> Output;
}
`