Skip to main content

dapr_durabletask/client/
options.rs

1use std::time::Duration;
2
3/// TLS configuration for a gRPC connection.
4///
5/// # Examples
6///
7/// ```rust,no_run
8/// use dapr_durabletask::client::{ClientOptions, TlsConfig};
9///
10/// // Use system CA certificates (no client cert).
11/// let opts = ClientOptions::new().with_tls(TlsConfig::default());
12///
13/// // Mutual TLS with a custom CA and client certificate.
14/// let tls = TlsConfig::new()
15///     .with_ca_cert_pem(std::fs::read("ca.pem").unwrap())
16///     .with_client_cert_pem(
17///         std::fs::read("client.pem").unwrap(),
18///         std::fs::read("client.key").unwrap(),
19///     );
20/// let opts = ClientOptions::new().with_tls(tls);
21/// ```
22#[derive(Debug, Clone, Default)]
23pub struct TlsConfig {
24    /// PEM-encoded CA certificate to use for server certificate verification.
25    /// If `None`, the system root CA store is used.
26    pub ca_cert_pem: Option<Vec<u8>>,
27
28    /// PEM-encoded client certificate for mutual TLS. Requires `client_key_pem`.
29    pub client_cert_pem: Option<Vec<u8>>,
30
31    /// PEM-encoded private key for the client certificate.
32    pub client_key_pem: Option<Vec<u8>>,
33
34    /// Skip server certificate verification. **Use only in development.**
35    pub skip_verify: bool,
36
37    /// Override the domain name used for TLS verification.
38    /// Useful when the server's certificate CN differs from the host address.
39    pub domain_name: Option<String>,
40}
41
42impl TlsConfig {
43    /// Create a TLS config that uses the system CA store.
44    pub fn new() -> Self {
45        Self::default()
46    }
47
48    /// Set a custom PEM-encoded CA certificate for server verification.
49    pub fn with_ca_cert_pem(mut self, pem: Vec<u8>) -> Self {
50        self.ca_cert_pem = Some(pem);
51        self
52    }
53
54    /// Set a PEM-encoded client certificate and private key for mutual TLS.
55    pub fn with_client_cert_pem(mut self, cert_pem: Vec<u8>, key_pem: Vec<u8>) -> Self {
56        self.client_cert_pem = Some(cert_pem);
57        self.client_key_pem = Some(key_pem);
58        self
59    }
60
61    /// Skip server certificate verification. **Use only in development.**
62    pub fn with_skip_verify(mut self) -> Self {
63        self.skip_verify = true;
64        self
65    }
66
67    /// Override the domain name used for TLS server name indication (SNI).
68    pub fn with_domain_name(mut self, name: impl Into<String>) -> Self {
69        self.domain_name = Some(name.into());
70        self
71    }
72}
73
74/// Configuration options for [`TaskHubGrpcClient`](super::TaskHubGrpcClient).
75///
76/// All fields have sensible defaults. Use [`ClientOptions::default()`] or the
77/// builder methods to customise.
78///
79/// # Examples
80///
81/// ```rust,no_run
82/// use dapr_durabletask::client::{ClientOptions, TlsConfig};
83/// use std::time::Duration;
84///
85/// let opts = ClientOptions::new()
86///     .with_tls(TlsConfig::default())
87///     .with_connect_timeout(Duration::from_secs(5))
88///     .with_max_grpc_message_size(16 * 1024 * 1024);
89/// ```
90#[derive(Debug, Clone)]
91pub struct ClientOptions {
92    /// Maximum JSON payload size in bytes for deserialisation.
93    pub max_json_payload_size: usize,
94
95    /// Maximum allowed length (in bytes) for identifiers such as orchestrator
96    /// names, instance IDs, and event names.
97    pub max_identifier_length: usize,
98
99    /// TLS configuration. When `None` the connection is plaintext.
100    pub tls: Option<TlsConfig>,
101
102    /// Timeout for establishing the initial TCP connection to the sidecar.
103    pub connect_timeout: Option<Duration>,
104
105    /// Maximum size in bytes for inbound gRPC messages.
106    /// Defaults to tonic's built-in 4 MiB limit when not set.
107    pub max_grpc_message_size: Option<usize>,
108
109    /// Duration between TCP keepalive probes sent to the sidecar.
110    pub keepalive_interval: Option<Duration>,
111
112    /// Duration to wait for a keepalive acknowledgement before closing the
113    /// connection.
114    pub keepalive_timeout: Option<Duration>,
115}
116
117impl Default for ClientOptions {
118    fn default() -> Self {
119        Self {
120            max_json_payload_size: 64 * 1024 * 1024, // 64 MiB
121            max_identifier_length: 1_024,
122            tls: None,
123            connect_timeout: None,
124            max_grpc_message_size: None,
125            keepalive_interval: None,
126            keepalive_timeout: None,
127        }
128    }
129}
130
131impl ClientOptions {
132    /// Create options with default values.
133    pub fn new() -> Self {
134        Self::default()
135    }
136
137    /// Set the maximum JSON payload size in bytes.
138    pub fn with_max_json_payload_size(mut self, limit: usize) -> Self {
139        self.max_json_payload_size = limit;
140        self
141    }
142
143    /// Set the maximum identifier length in bytes.
144    pub fn with_max_identifier_length(mut self, limit: usize) -> Self {
145        self.max_identifier_length = limit;
146        self
147    }
148
149    /// Enable TLS with the given configuration.
150    /// Pass [`TlsConfig::default()`] to use system CA certificates.
151    pub fn with_tls(mut self, tls: TlsConfig) -> Self {
152        self.tls = Some(tls);
153        self
154    }
155
156    /// Set the timeout for the initial TCP connection.
157    pub fn with_connect_timeout(mut self, timeout: Duration) -> Self {
158        self.connect_timeout = Some(timeout);
159        self
160    }
161
162    /// Set the maximum inbound gRPC message size in bytes.
163    pub fn with_max_grpc_message_size(mut self, size: usize) -> Self {
164        self.max_grpc_message_size = Some(size);
165        self
166    }
167
168    /// Set the interval between TCP keepalive probes.
169    pub fn with_keepalive_interval(mut self, interval: Duration) -> Self {
170        self.keepalive_interval = Some(interval);
171        self
172    }
173
174    /// Set the timeout for a keepalive acknowledgement.
175    pub fn with_keepalive_timeout(mut self, timeout: Duration) -> Self {
176        self.keepalive_timeout = Some(timeout);
177        self
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[test]
186    fn tls_config_defaults() {
187        let tls = TlsConfig::new();
188        assert!(tls.ca_cert_pem.is_none());
189        assert!(tls.client_cert_pem.is_none());
190        assert!(tls.client_key_pem.is_none());
191        assert!(!tls.skip_verify);
192        assert!(tls.domain_name.is_none());
193    }
194
195    #[test]
196    fn tls_config_with_ca_cert_pem() {
197        let tls = TlsConfig::new().with_ca_cert_pem(b"ca-data".to_vec());
198        assert_eq!(tls.ca_cert_pem.as_deref(), Some(b"ca-data".as_slice()));
199    }
200
201    #[test]
202    fn tls_config_with_client_cert_pem() {
203        let tls = TlsConfig::new().with_client_cert_pem(b"cert".to_vec(), b"key".to_vec());
204        assert_eq!(tls.client_cert_pem.as_deref(), Some(b"cert".as_slice()));
205        assert_eq!(tls.client_key_pem.as_deref(), Some(b"key".as_slice()));
206    }
207
208    #[test]
209    fn tls_config_with_skip_verify() {
210        let tls = TlsConfig::new().with_skip_verify();
211        assert!(tls.skip_verify);
212    }
213
214    #[test]
215    fn tls_config_with_domain_name() {
216        let tls = TlsConfig::new().with_domain_name("example.com");
217        assert_eq!(tls.domain_name.as_deref(), Some("example.com"));
218    }
219
220    #[test]
221    fn client_options_defaults() {
222        let opts = ClientOptions::default();
223        assert_eq!(opts.max_json_payload_size, 64 * 1024 * 1024);
224        assert_eq!(opts.max_identifier_length, 1024);
225        assert!(opts.tls.is_none());
226        assert!(opts.connect_timeout.is_none());
227        assert!(opts.max_grpc_message_size.is_none());
228        assert!(opts.keepalive_interval.is_none());
229        assert!(opts.keepalive_timeout.is_none());
230    }
231
232    #[test]
233    fn client_options_with_max_json_payload_size() {
234        let opts = ClientOptions::new().with_max_json_payload_size(1024);
235        assert_eq!(opts.max_json_payload_size, 1024);
236    }
237
238    #[test]
239    fn client_options_with_max_identifier_length() {
240        let opts = ClientOptions::new().with_max_identifier_length(256);
241        assert_eq!(opts.max_identifier_length, 256);
242    }
243
244    #[test]
245    fn client_options_with_tls() {
246        let opts = ClientOptions::new().with_tls(TlsConfig::default());
247        assert!(opts.tls.is_some());
248    }
249
250    #[test]
251    fn client_options_with_connect_timeout() {
252        let opts = ClientOptions::new().with_connect_timeout(Duration::from_secs(5));
253        assert_eq!(opts.connect_timeout, Some(Duration::from_secs(5)));
254    }
255
256    #[test]
257    fn client_options_with_max_grpc_message_size() {
258        let opts = ClientOptions::new().with_max_grpc_message_size(16 * 1024 * 1024);
259        assert_eq!(opts.max_grpc_message_size, Some(16 * 1024 * 1024));
260    }
261
262    #[test]
263    fn client_options_with_keepalive_interval() {
264        let opts = ClientOptions::new().with_keepalive_interval(Duration::from_secs(30));
265        assert_eq!(opts.keepalive_interval, Some(Duration::from_secs(30)));
266    }
267
268    #[test]
269    fn client_options_with_keepalive_timeout() {
270        let opts = ClientOptions::new().with_keepalive_timeout(Duration::from_secs(10));
271        assert_eq!(opts.keepalive_timeout, Some(Duration::from_secs(10)));
272    }
273
274    #[test]
275    fn client_options_builder_chaining() {
276        let opts = ClientOptions::new()
277            .with_max_json_payload_size(2048)
278            .with_max_identifier_length(512)
279            .with_tls(TlsConfig::new().with_skip_verify())
280            .with_connect_timeout(Duration::from_secs(3))
281            .with_max_grpc_message_size(8 * 1024 * 1024)
282            .with_keepalive_interval(Duration::from_secs(60))
283            .with_keepalive_timeout(Duration::from_secs(20));
284
285        assert_eq!(opts.max_json_payload_size, 2048);
286        assert_eq!(opts.max_identifier_length, 512);
287        assert!(opts.tls.as_ref().unwrap().skip_verify);
288        assert_eq!(opts.connect_timeout, Some(Duration::from_secs(3)));
289        assert_eq!(opts.max_grpc_message_size, Some(8 * 1024 * 1024));
290        assert_eq!(opts.keepalive_interval, Some(Duration::from_secs(60)));
291        assert_eq!(opts.keepalive_timeout, Some(Duration::from_secs(20)));
292    }
293}