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 shape | method returns | drive it with |
|---|---|---|
| unary | UnaryConn | .await?, then into_message |
| server-streaming | StreamingConn | iterate it as a Stream |
| client-streaming | UnaryConn | pass impl Stream<Item = Req>, then .await? |
| bidirectional | BidiConn | live 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:
with_ascii_metadata/with_binary_metadata— request metadata. Binary keys (suffix-bin) carry arbitrary bytes; ASCII keys carry printable-ASCII strings.with_timeout— agrpc-timeoutdeadline for this one call.cancel_handle— a cheaply-cloneableCancelHandleyou can move elsewhere and trigger to cancel the call.
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:
with_outbound_compression— compress request messages with anEncoding.with_default_timeout— a fallbackgrpc-timeoutfor calls that don’t set their own.
use trillium_grpc::{Encoding, ServiceClientExt};
let client = GreeterClient::from(/* … */)
.with_outbound_compression(Encoding::Gzip)
.with_default_timeout(std::time::Duration::from_secs(5));