Skip to main content

Module calling

Module calling 

Source
Expand description

Calling a service.

The generated <Service>Client wraps a trillium_client::Client. Build it with From, passing a client whose base URL points at the server — the constructor appends the service path, so each method only names its own RPC:

let client = GreeterClient::from(
    trillium_client::Client::new(trillium_tokio::ClientConfig::default())
        .with_base("http://127.0.0.1:8080"),
);

Each method returns a typed call handle whose surface fits the RPC’s shape, so you never need the .proto to use it correctly. A handle is built lazily: you configure it with chainable setters, then .await and/or iterate it to run the call. Initial metadata and trailing metadata are readable off the handle once the call has run — on the error path too.

RPC shapemethod returnsdrive it with
unaryUnaryConn.await?, then into_message
server-streamingStreamingConniterate it as a Stream
client-streamingUnaryConnpass impl Stream<Item = Req>, then .await?
bidirectionalBidiConnlive send / recv

§Unary

Awaiting a UnaryConn runs the whole call — sends the request, reads the one response, reads the trailers. into_message then hands you the decoded message or the grpc-status as a Status:

let reply = client
    .say_hello(HelloRequest { name: "world".into() })
    .await?
    .into_message()?;

§Server-streaming

A StreamingConn is a Stream of Result<Resp, Status>; the first poll fires the request and reads the head. Iterate it to completion:

use futures_lite::StreamExt;

let mut stream = client.say_hello_stream(HelloRequest { name: "world".into() });
while let Some(reply) = stream.next().await {
    println!("{}", reply?.message);
}

To read the initial metadata before the first message, .await the handle first — that reads the head only and hands the handle back, still iterable.

§Client-streaming

Pass an impl Stream<Item = Req>; awaiting the returned UnaryConn drains it and reads the single response:

let names = futures_lite::stream::iter(
    ["Alice", "Bob"].map(|name| HelloRequest { name: name.into() }),
);
let reply = client.say_hello_many(names).await?.into_message()?;

§Bidirectional

A BidiConn is a live full-duplex handle: send requests, close_send when done sending, and recv responses — interleaved, turn-taking on &mut self. It also implements Stream for the receive side:

let mut chat = client.say_hello_chat();
chat.send(HelloRequest { name: "Carol".into() }).await?;
chat.close_send().await?;
while let Some(reply) = chat.recv().await? {
    println!("{}", reply.message);
}

§Per-call configuration

Every handle carries the same chainable builders before you drive it:

Read the response metadata with metadata() (initial) and trailers(), both available once the call has run.

§Error surfacing

Errors split by stage. Transport failures and a non-gRPC response head surface from the .await (the ? above). The logical grpc-status surfaces at read time — into_message, status, or a stream item — so the response metadata stays readable even when the call failed. A unary deadline can fire at either stage; to treat both as one error, write call.await.and_then(|c| c.into_message()).

§Per-client configuration

Options that apply to every call a client makes live on the ServiceClientExt trait, which every generated client implements:

use trillium_grpc::{Encoding, ServiceClientExt};

let client = GreeterClient::from(/* … */)
    .with_outbound_compression(Encoding::Gzip)
    .with_default_timeout(std::time::Duration::from_secs(5));