trillium_grpc/client/service_client.rs
1//! Extension surface for generated `<Service>Client` newtypes.
2//!
3//! Codegen emits `impl ServiceClient for <Service>Client` for every service
4//! it produces. The blanket [`ServiceClientExt`] then provides the
5//! builder/setter methods every service client gets for free.
6//!
7//! New options live on [`ServiceClientExt`] — adding one is a single
8//! method here, no codegen change.
9
10use crate::{Encoding, timeout::format_grpc_timeout};
11use std::time::Duration;
12
13/// Generated `<Service>Client` newtypes implement this so extension traits
14/// can configure the underlying [`trillium_client::Client`].
15pub trait ServiceClient {
16 /// The underlying connection client.
17 fn client(&self) -> &trillium_client::Client;
18 /// The underlying connection client, mutably — the hook the
19 /// [`ServiceClientExt`] setters write through.
20 fn client_mut(&mut self) -> &mut trillium_client::Client;
21}
22
23/// Builder-style configuration available on every service client.
24/// Implemented for any `T: ServiceClient + Sized`, so service clients
25/// don't reimplement these — they just `impl ServiceClient` and inherit
26/// the full set.
27pub trait ServiceClientExt: ServiceClient + Sized {
28 /// Compress every outgoing request body with this codec. Sent on the
29 /// wire as `grpc-encoding: <codec>`.
30 ///
31 /// The server is required by spec to handle whatever the client sends
32 /// — including failing with `Unimplemented` — so picking a codec your
33 /// server is known to support is the caller's responsibility. The
34 /// server's `grpc-accept-encoding` (visible after the first response)
35 /// can be inspected by the caller to pick a future-safe codec.
36 ///
37 /// Setting `Encoding::Identity` clears any previously-set compression.
38 fn with_outbound_compression(mut self, encoding: Encoding) -> Self {
39 self.set_outbound_compression(encoding);
40 self
41 }
42
43 /// `&mut` form of [`with_outbound_compression`](Self::with_outbound_compression).
44 fn set_outbound_compression(&mut self, encoding: Encoding) -> &mut Self {
45 let headers = self.client_mut().default_headers_mut();
46 if matches!(encoding, Encoding::Identity) {
47 headers.remove("grpc-encoding");
48 } else {
49 headers.insert("grpc-encoding", encoding.as_grpc_encoding());
50 }
51 self
52 }
53
54 /// The currently-configured outbound compression. `Identity` if none
55 /// has been set.
56 fn outbound_compression(&self) -> Encoding {
57 self.client()
58 .default_headers()
59 .get_str("grpc-encoding")
60 .and_then(Encoding::from_grpc_encoding)
61 .unwrap_or(Encoding::Identity)
62 }
63
64 /// Apply this duration as the default deadline on every call. Sent on
65 /// the wire as `grpc-timeout: <unit>` so the server can enforce the
66 /// same deadline; the client also races its own dispatch future
67 /// against a local timer so we fail fast even if the server is unable
68 /// to respond.
69 ///
70 /// Setting `Duration::ZERO` clears any previously-set deadline.
71 fn with_default_timeout(mut self, duration: Duration) -> Self {
72 self.set_default_timeout(duration);
73 self
74 }
75
76 /// `&mut` form of [`with_default_timeout`](Self::with_default_timeout).
77 fn set_default_timeout(&mut self, duration: Duration) -> &mut Self {
78 let headers = self.client_mut().default_headers_mut();
79 if duration.is_zero() {
80 headers.remove("grpc-timeout");
81 } else {
82 headers.insert("grpc-timeout", format_grpc_timeout(duration));
83 }
84 self
85 }
86
87 /// The currently-configured default timeout, if any.
88 fn default_timeout(&self) -> Option<Duration> {
89 self.client()
90 .default_headers()
91 .get_str("grpc-timeout")
92 .and_then(crate::timeout::parse_grpc_timeout)
93 }
94}
95
96impl<T: ServiceClient> ServiceClientExt for T {}