amaters_sdk_rust/
config.rs

1//! Client configuration types
2
3use std::path::PathBuf;
4use std::time::Duration;
5
6/// Client configuration
7#[derive(Debug, Clone)]
8pub struct ClientConfig {
9    /// Server address (e.g., "http://localhost:50051")
10    pub server_addr: String,
11
12    /// Connection timeout
13    pub connect_timeout: Duration,
14
15    /// Request timeout
16    pub request_timeout: Duration,
17
18    /// Enable keep-alive
19    pub keep_alive: bool,
20
21    /// Keep-alive interval
22    pub keep_alive_interval: Duration,
23
24    /// Keep-alive timeout
25    pub keep_alive_timeout: Duration,
26
27    /// Maximum number of connections in pool
28    pub max_connections: usize,
29
30    /// Connection idle timeout
31    pub idle_timeout: Duration,
32
33    /// Enable TLS
34    pub tls_enabled: bool,
35
36    /// TLS configuration
37    pub tls_config: Option<TlsConfig>,
38
39    /// Retry configuration
40    pub retry_config: RetryConfig,
41}
42
43impl Default for ClientConfig {
44    fn default() -> Self {
45        Self {
46            server_addr: "http://localhost:50051".to_string(),
47            connect_timeout: Duration::from_secs(10),
48            request_timeout: Duration::from_secs(30),
49            keep_alive: true,
50            keep_alive_interval: Duration::from_secs(60),
51            keep_alive_timeout: Duration::from_secs(20),
52            max_connections: 10,
53            idle_timeout: Duration::from_secs(300),
54            tls_enabled: false,
55            tls_config: None,
56            retry_config: RetryConfig::default(),
57        }
58    }
59}
60
61impl ClientConfig {
62    /// Create a new client configuration with server address
63    pub fn new(server_addr: impl Into<String>) -> Self {
64        Self {
65            server_addr: server_addr.into(),
66            ..Default::default()
67        }
68    }
69
70    /// Set connection timeout
71    pub fn with_connect_timeout(mut self, timeout: Duration) -> Self {
72        self.connect_timeout = timeout;
73        self
74    }
75
76    /// Set request timeout
77    pub fn with_request_timeout(mut self, timeout: Duration) -> Self {
78        self.request_timeout = timeout;
79        self
80    }
81
82    /// Set maximum connections
83    pub fn with_max_connections(mut self, max: usize) -> Self {
84        self.max_connections = max;
85        self
86    }
87
88    /// Enable TLS with configuration
89    pub fn with_tls(mut self, tls_config: TlsConfig) -> Self {
90        self.tls_enabled = true;
91        self.tls_config = Some(tls_config);
92        self
93    }
94
95    /// Set retry configuration
96    pub fn with_retry_config(mut self, retry_config: RetryConfig) -> Self {
97        self.retry_config = retry_config;
98        self
99    }
100
101    /// Disable keep-alive
102    pub fn without_keep_alive(mut self) -> Self {
103        self.keep_alive = false;
104        self
105    }
106}
107
108/// TLS configuration
109#[derive(Debug, Clone, Default)]
110pub struct TlsConfig {
111    /// Path to CA certificate file
112    pub ca_cert_path: Option<PathBuf>,
113
114    /// Path to client certificate file (for mTLS)
115    pub client_cert_path: Option<PathBuf>,
116
117    /// Path to client key file (for mTLS)
118    pub client_key_path: Option<PathBuf>,
119
120    /// Domain name for SNI
121    pub domain_name: Option<String>,
122
123    /// Accept invalid certificates (for testing only)
124    pub accept_invalid_certs: bool,
125}
126
127impl TlsConfig {
128    /// Create a new TLS configuration
129    pub fn new() -> Self {
130        Self::default()
131    }
132
133    /// Set CA certificate path
134    pub fn with_ca_cert(mut self, path: impl Into<PathBuf>) -> Self {
135        self.ca_cert_path = Some(path.into());
136        self
137    }
138
139    /// Set client certificate and key for mTLS
140    pub fn with_client_cert(
141        mut self,
142        cert_path: impl Into<PathBuf>,
143        key_path: impl Into<PathBuf>,
144    ) -> Self {
145        self.client_cert_path = Some(cert_path.into());
146        self.client_key_path = Some(key_path.into());
147        self
148    }
149
150    /// Set domain name for SNI
151    pub fn with_domain_name(mut self, domain: impl Into<String>) -> Self {
152        self.domain_name = Some(domain.into());
153        self
154    }
155
156    /// Accept invalid certificates (for testing only)
157    pub fn accept_invalid_certs(mut self) -> Self {
158        self.accept_invalid_certs = true;
159        self
160    }
161}
162
163/// Retry configuration
164#[derive(Debug, Clone)]
165pub struct RetryConfig {
166    /// Maximum number of retry attempts
167    pub max_retries: usize,
168
169    /// Initial backoff duration
170    pub initial_backoff: Duration,
171
172    /// Maximum backoff duration
173    pub max_backoff: Duration,
174
175    /// Backoff multiplier
176    pub backoff_multiplier: f64,
177
178    /// Enable jitter for backoff
179    pub jitter: bool,
180}
181
182impl Default for RetryConfig {
183    fn default() -> Self {
184        Self {
185            max_retries: 3,
186            initial_backoff: Duration::from_millis(100),
187            max_backoff: Duration::from_secs(30),
188            backoff_multiplier: 2.0,
189            jitter: true,
190        }
191    }
192}
193
194impl RetryConfig {
195    /// Create a new retry configuration
196    pub fn new() -> Self {
197        Self::default()
198    }
199
200    /// Set maximum retries
201    pub fn with_max_retries(mut self, max: usize) -> Self {
202        self.max_retries = max;
203        self
204    }
205
206    /// Set initial backoff
207    pub fn with_initial_backoff(mut self, backoff: Duration) -> Self {
208        self.initial_backoff = backoff;
209        self
210    }
211
212    /// Disable retries
213    pub fn no_retry() -> Self {
214        Self {
215            max_retries: 0,
216            ..Default::default()
217        }
218    }
219
220    /// Calculate backoff duration for attempt
221    pub fn backoff_duration(&self, attempt: usize) -> Duration {
222        if attempt == 0 {
223            return Duration::from_secs(0);
224        }
225
226        let base = self.initial_backoff.as_millis() as f64
227            * self.backoff_multiplier.powi((attempt - 1) as i32);
228        let backoff = Duration::from_millis(base.min(self.max_backoff.as_millis() as f64) as u64);
229
230        if self.jitter {
231            // Add jitter: random value between 0.5 and 1.5 times the backoff
232            let jitter_factor = 0.5 + (attempt % 10) as f64 / 10.0; // Simple deterministic jitter
233            Duration::from_millis((backoff.as_millis() as f64 * jitter_factor) as u64)
234        } else {
235            backoff
236        }
237    }
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243
244    #[test]
245    fn test_default_config() {
246        let config = ClientConfig::default();
247        assert_eq!(config.server_addr, "http://localhost:50051");
248        assert_eq!(config.max_connections, 10);
249        assert!(config.keep_alive);
250    }
251
252    #[test]
253    fn test_config_builder() {
254        let config = ClientConfig::new("http://example.com:50051")
255            .with_connect_timeout(Duration::from_secs(5))
256            .with_max_connections(20)
257            .without_keep_alive();
258
259        assert_eq!(config.server_addr, "http://example.com:50051");
260        assert_eq!(config.connect_timeout, Duration::from_secs(5));
261        assert_eq!(config.max_connections, 20);
262        assert!(!config.keep_alive);
263    }
264
265    #[test]
266    fn test_tls_config() {
267        let tls = TlsConfig::new()
268            .with_ca_cert("/path/to/ca.pem")
269            .with_domain_name("example.com");
270
271        assert!(tls.ca_cert_path.is_some());
272        assert_eq!(tls.domain_name, Some("example.com".to_string()));
273    }
274
275    #[test]
276    fn test_retry_config() {
277        let retry = RetryConfig::default();
278        assert_eq!(retry.max_retries, 3);
279
280        let backoff1 = retry.backoff_duration(1);
281        let backoff2 = retry.backoff_duration(2);
282        assert!(backoff2 > backoff1);
283    }
284
285    #[test]
286    fn test_no_retry() {
287        let retry = RetryConfig::no_retry();
288        assert_eq!(retry.max_retries, 0);
289    }
290}