1#[cfg(feature = "http")]
5#[derive(Clone, Debug)]
6pub struct HttpConfig {
7 pub url: String,
9
10 pub authorization: Option<String>,
12
13 pub base_timeout: std::time::Duration,
17
18 pub modem_timeout: Option<std::time::Duration>,
22}
23#[cfg(feature = "http")]
24impl HttpConfig {
25 pub const HTTP_DEFAULT_BASE_TIMEOUT: u64 = 5;
28
29 pub const HTTP_DEFAULT_MODEM_TIMEOUT: u64 = 20;
32
33 #[must_use]
35 pub fn new(url: impl Into<String>) -> Self {
36 Self {
37 url: url.into(),
38 authorization: None,
39 base_timeout: std::time::Duration::from_secs(Self::HTTP_DEFAULT_BASE_TIMEOUT),
40 modem_timeout: Some(std::time::Duration::from_secs(
41 Self::HTTP_DEFAULT_MODEM_TIMEOUT,
42 )),
43 }
44 }
45
46 #[must_use]
48 pub fn with_auth(mut self, token: impl Into<String>) -> Self {
49 self.authorization = Some(token.into());
50 self
51 }
52
53 #[must_use]
55 pub fn with_base_timeout(mut self, timeout: std::time::Duration) -> Self {
56 self.base_timeout = timeout;
57 self
58 }
59
60 #[must_use]
62 pub fn with_modem_timeout(mut self, timeout: Option<std::time::Duration>) -> Self {
63 self.modem_timeout = timeout;
64 self
65 }
66}
67#[cfg(feature = "http")]
68impl Default for HttpConfig {
69 fn default() -> Self {
70 Self {
71 url: "http://localhost:3000".to_string(),
72 authorization: None,
73 base_timeout: std::time::Duration::from_secs(Self::HTTP_DEFAULT_BASE_TIMEOUT),
74 modem_timeout: Some(std::time::Duration::from_secs(
75 Self::HTTP_DEFAULT_MODEM_TIMEOUT,
76 )),
77 }
78 }
79}
80
81#[cfg(feature = "websocket")]
83#[derive(Clone, Debug)]
84pub struct WebSocketConfig {
85 pub url: String,
87
88 pub authorization: Option<String>,
90
91 pub auto_reconnect: bool,
93
94 pub reconnect_interval: std::time::Duration,
96
97 pub ping_interval: std::time::Duration,
99
100 pub ping_timeout: std::time::Duration,
102
103 pub max_reconnect_attempts: Option<u32>,
105
106 pub filtered_events: Option<Vec<String>>,
110}
111#[cfg(feature = "websocket")]
112impl WebSocketConfig {
113 pub const WS_DEFAULT_RECONNECT_INTERVAL: u64 = 5;
116
117 pub const WS_DEFAULT_PING_INTERVAL: u64 = 10;
119
120 pub const WS_DEFAULT_PING_TIMEOUT: u64 = 30;
122
123 #[must_use]
125 pub fn new(url: impl Into<String>) -> Self {
126 Self {
127 url: url.into(),
128 authorization: None,
129 auto_reconnect: true,
130 reconnect_interval: std::time::Duration::from_secs(Self::WS_DEFAULT_RECONNECT_INTERVAL),
131 ping_interval: std::time::Duration::from_secs(Self::WS_DEFAULT_PING_INTERVAL),
132 ping_timeout: std::time::Duration::from_secs(Self::WS_DEFAULT_PING_TIMEOUT),
133 max_reconnect_attempts: None,
134 filtered_events: None,
135 }
136 }
137
138 #[must_use]
140 pub fn with_auth(mut self, token: impl Into<String>) -> Self {
141 self.authorization = Some(token.into());
142 self
143 }
144
145 #[must_use]
147 pub fn with_auto_reconnect(mut self, enabled: bool) -> Self {
148 self.auto_reconnect = enabled;
149 self
150 }
151
152 #[must_use]
154 pub fn with_reconnect_interval(mut self, interval: std::time::Duration) -> Self {
155 self.reconnect_interval = interval;
156 self
157 }
158
159 #[must_use]
161 pub fn with_ping_interval(mut self, interval: std::time::Duration) -> Self {
162 self.ping_interval = interval;
163 self
164 }
165
166 #[must_use]
168 pub fn with_ping_timeout(mut self, timeout: std::time::Duration) -> Self {
169 self.ping_timeout = timeout;
170 self
171 }
172
173 #[must_use]
175 pub fn with_max_reconnect_attempts(mut self, max_attempts: Option<u32>) -> Self {
176 self.max_reconnect_attempts = max_attempts;
177 self
178 }
179
180 #[must_use]
184 pub fn with_filtered_events(mut self, events: Option<Vec<impl Into<String>>>) -> Self {
185 self.filtered_events = events.map(|events| events.into_iter().map(Into::into).collect());
186 self
187 }
188}
189#[cfg(feature = "websocket")]
190impl Default for WebSocketConfig {
191 fn default() -> Self {
192 Self {
193 url: "ws://localhost:3000/ws".to_string(),
194 authorization: None,
195 auto_reconnect: true,
196 reconnect_interval: std::time::Duration::from_secs(Self::WS_DEFAULT_RECONNECT_INTERVAL),
197 ping_interval: std::time::Duration::from_secs(Self::WS_DEFAULT_PING_INTERVAL),
198 ping_timeout: std::time::Duration::from_secs(Self::WS_DEFAULT_PING_TIMEOUT),
199 max_reconnect_attempts: None,
200 filtered_events: None,
201 }
202 }
203}
204
205#[derive(Clone, Debug)]
207pub struct TLSConfig {
208 pub certificate: std::path::PathBuf,
210}
211impl TLSConfig {
212 pub fn new(certificate: impl Into<std::path::PathBuf>) -> crate::error::ClientResult<Self> {
214 Ok(Self {
215 certificate: Self::verify_path(&certificate.into())?,
216 })
217 }
218
219 fn verify_path(path: &std::path::Path) -> crate::error::ClientResult<std::path::PathBuf> {
221 if !path.exists() {
222 return Err(crate::error::ClientError::ConfigError(
223 "Certificate filepath does not exist",
224 ));
225 }
226 if !path.is_file() {
227 return Err(crate::error::ClientError::ConfigError(
228 "Certificate filepath is not a file",
229 ));
230 }
231 let canonical_path = path
232 .canonicalize()
233 .map_err(|_| crate::error::ClientError::ConfigError("Invalid certificate path"))?;
234
235 match path.extension().and_then(|s| s.to_str()) {
237 Some("pem" | "crt" | "der") => Ok(canonical_path),
238 _ => Err(crate::error::ClientError::ConfigError(
239 "Invalid certificate file extension",
240 )),
241 }
242 }
243}
244
245#[derive(Clone, Debug)]
247pub struct ClientConfig {
248 pub tls: Option<TLSConfig>,
250
251 #[cfg(feature = "http")]
253 pub http: Option<HttpConfig>,
254
255 #[cfg(feature = "websocket")]
257 pub websocket: Option<WebSocketConfig>,
258}
259impl ClientConfig {
260 #[cfg(feature = "http")]
269 #[must_use]
270 pub fn http_only(url: impl Into<String>) -> Self {
271 Self {
272 tls: None,
273 http: Some(HttpConfig::new(url)),
274
275 #[cfg(feature = "websocket")]
276 websocket: None,
277 }
278 }
279
280 #[cfg(feature = "websocket")]
289 #[must_use]
290 pub fn websocket_only(ws_url: impl Into<String>) -> Self {
291 Self {
292 tls: None,
293
294 #[cfg(feature = "http")]
295 http: None,
296
297 websocket: Some(WebSocketConfig::new(ws_url)),
298 }
299 }
300
301 #[cfg(feature = "http")]
313 #[cfg(feature = "websocket")]
314 #[must_use]
315 pub fn both(http_url: impl Into<String>, ws_url: impl Into<String>) -> Self {
316 Self {
317 tls: None,
318 http: Some(HttpConfig::new(http_url)),
319 websocket: Some(WebSocketConfig::new(ws_url)),
320 }
321 }
322
323 #[cfg(feature = "http")]
342 #[cfg(feature = "websocket")]
343 #[must_use]
344 pub fn from_parts(http: Option<HttpConfig>, websocket: Option<WebSocketConfig>) -> Self {
345 Self {
346 tls: None,
347 http,
348 websocket,
349 }
350 }
351
352 #[must_use]
354 pub fn add_tls(mut self, tls: TLSConfig) -> Self {
355 self.tls = Some(tls);
356 self
357 }
358
359 #[must_use]
372 pub fn with_auth(mut self, token: impl Into<String>) -> Self {
373 let token = token.into();
374
375 #[cfg(feature = "http")]
376 if let Some(http) = &mut self.http {
377 http.authorization = Some(token.clone());
378 }
379
380 #[cfg(feature = "websocket")]
381 if let Some(ws) = &mut self.websocket {
382 ws.authorization = Some(token);
383 }
384 self
385 }
386
387 pub fn with_certificate(
396 mut self,
397 certificate: impl Into<std::path::PathBuf>,
398 ) -> crate::error::ClientResult<Self> {
399 if let Some(tls) = &mut self.tls {
400 tls.certificate = TLSConfig::verify_path(&certificate.into())?;
401 } else {
402 self.tls = Some(TLSConfig::new(certificate)?);
403 }
404 Ok(self)
405 }
406
407 #[cfg(feature = "http")]
421 #[must_use]
422 pub fn configure_http<F>(mut self, f: F) -> Self
423 where
424 F: FnOnce(HttpConfig) -> HttpConfig,
425 {
426 if let Some(http) = self.http {
427 self.http = Some(f(http));
428 }
429 self
430 }
431
432 #[cfg(feature = "websocket")]
448 #[must_use]
449 pub fn configure_websocket<F>(mut self, f: F) -> Self
450 where
451 F: FnOnce(WebSocketConfig) -> WebSocketConfig,
452 {
453 if let Some(ws) = self.websocket {
454 self.websocket = Some(f(ws));
455 }
456 self
457 }
458
459 #[cfg(feature = "websocket")]
469 #[must_use]
470 pub fn add_websocket(mut self, websocket: WebSocketConfig) -> Self {
471 self.websocket = Some(websocket);
472 self
473 }
474}
475impl Default for ClientConfig {
476 fn default() -> Self {
477 Self {
478 tls: None,
479
480 #[cfg(feature = "http")]
481 http: Some(HttpConfig::default()),
482
483 #[cfg(feature = "websocket")]
484 websocket: Some(WebSocketConfig::default()),
485 }
486 }
487}
488
489#[cfg(feature = "http")]
490#[allow(clippy::needless_update)]
491impl From<HttpConfig> for ClientConfig {
492 fn from(http: HttpConfig) -> Self {
493 ClientConfig {
494 tls: None,
495 http: Some(http),
496 ..Default::default()
497 }
498 }
499}
500
501#[cfg(feature = "websocket")]
502#[allow(clippy::needless_update)]
503impl From<WebSocketConfig> for ClientConfig {
504 fn from(ws: WebSocketConfig) -> Self {
505 ClientConfig {
506 tls: None,
507 websocket: Some(ws),
508 ..Default::default()
509 }
510 }
511}