sms_client/config.rs
1//! SMS-Client connection configuration.
2
3use std::time::Duration;
4
5/// HTTP-specific configuration.
6#[derive(Clone, Debug)]
7pub struct HttpConfig {
8
9 /// HTTP base URL. eg: http://192.168.1.2:3000
10 pub url: String,
11
12 /// Optional HTTP authorization header token.
13 pub authorization: Option<String>,
14
15 /// A default timeout to apply to all requests that do not have
16 /// their own timeout (this applies to all if modem_timeout is None,
17 /// otherwise only database and sys queries).
18 pub base_timeout: Duration,
19
20 /// An optional timeout to use specifically for modem requests
21 /// (requests that must send and receive modem data). This should
22 /// be higher than the default timeout as they can take longer.
23 pub modem_timeout: Option<Duration>,
24}
25impl HttpConfig {
26
27 /// The default amount of seconds before an HTTP request should time out.
28 /// If there is no modem_timeout, this is applied to all requests.
29 pub const HTTP_DEFAULT_BASE_TIMEOUT: u64 = 5;
30
31 /// The default amount of seconds before an HTTP request that interacts directly
32 /// with the modem should time out. This should be longer to allow for carrier response.
33 pub const HTTP_DEFAULT_MODEM_TIMEOUT: u64 = 20;
34
35 /// Create a new HTTP configuration with default settings.
36 pub fn new(url: impl Into<String>) -> Self {
37 Self {
38 url: url.into(),
39 authorization: None,
40 base_timeout: Duration::from_secs(Self::HTTP_DEFAULT_BASE_TIMEOUT),
41 modem_timeout: Some(Duration::from_secs(Self::HTTP_DEFAULT_MODEM_TIMEOUT))
42 }
43 }
44
45 /// Set the authorization token.
46 pub fn with_auth(mut self, token: impl Into<String>) -> Self {
47 self.authorization = Some(token.into());
48 self
49 }
50
51 /// Set the base request timeout.
52 pub fn with_base_timeout(mut self, timeout: Duration) -> Self {
53 self.base_timeout = timeout;
54 self
55 }
56
57 /// Set the modem request timeout.
58 pub fn with_modem_timeout(mut self, timeout: Option<Duration>) -> Self {
59 self.modem_timeout = timeout;
60 self
61 }
62}
63impl Default for HttpConfig {
64 fn default() -> Self {
65 Self {
66 url: "http://localhost:3000".to_string(),
67 authorization: None,
68 base_timeout: Duration::from_secs(Self::HTTP_DEFAULT_BASE_TIMEOUT),
69 modem_timeout: Some(Duration::from_secs(Self::HTTP_DEFAULT_MODEM_TIMEOUT))
70 }
71 }
72}
73
74/// WebSocket-specific configuration.
75#[cfg(feature = "websocket")]
76#[derive(Clone, Debug)]
77pub struct WebsocketConfig {
78
79 /// Websocket event channel URL. eg: ws://192.168.1.2:3000/ws
80 pub url: String,
81
82 /// Optional Websocket authorization header token.
83 pub authorization: Option<String>,
84
85 /// Should the websocket connection automatically reconnect if disconnected.
86 pub auto_reconnect: bool,
87
88 /// Interval to use between reconnection attempts.
89 pub reconnect_interval: Duration,
90
91 /// The interval between sending websocket pings.
92 pub ping_interval: Duration,
93
94 /// Timeout duration for missing pings.
95 pub ping_timeout: Duration,
96
97 /// Maximum reconnection attempts (None = unlimited).
98 pub max_reconnect_attempts: Option<u32>,
99
100 /// Optional set of events that should be listened to. This is added to
101 /// the websocket connection URI, and the server filters out events before
102 /// sending them. By default, all events are sent when none are selected.
103 pub filtered_events: Option<Vec<String>>,
104}
105#[cfg(feature = "websocket")]
106impl WebsocketConfig {
107
108 /// The default interval to use between connection attempts.
109 /// Sequential attempts use a backoff up to 60 seconds.
110 pub const WS_DEFAULT_RECONNECT_INTERVAL: u64 = 5;
111
112 /// The interval between sending ping messages.
113 pub const WS_DEFAULT_PING_INTERVAL: u64 = 10;
114
115 /// The duration between the last ping to count as a timeout.
116 pub const WS_DEFAULT_PING_TIMEOUT: u64 = 30;
117
118 /// Create a new WebSocket configuration with default settings.
119 pub fn new(url: impl Into<String>) -> Self {
120 Self {
121 url: url.into(),
122 authorization: None,
123 auto_reconnect: true,
124 reconnect_interval: Duration::from_secs(Self::WS_DEFAULT_RECONNECT_INTERVAL),
125 ping_interval: Duration::from_secs(Self::WS_DEFAULT_PING_INTERVAL),
126 ping_timeout: Duration::from_secs(Self::WS_DEFAULT_PING_TIMEOUT),
127 max_reconnect_attempts: None,
128 filtered_events: None
129 }
130 }
131
132 /// Set the authorization token.
133 pub fn with_auth(mut self, token: impl Into<String>) -> Self {
134 self.authorization = Some(token.into());
135 self
136 }
137
138 /// Enable or disable auto-reconnection.
139 pub fn with_auto_reconnect(mut self, enabled: bool) -> Self {
140 self.auto_reconnect = enabled;
141 self
142 }
143
144 /// Set the reconnection interval.
145 pub fn with_reconnect_interval(mut self, interval: Duration) -> Self {
146 self.reconnect_interval = interval;
147 self
148 }
149
150 /// Set the ping interval.
151 pub fn with_ping_interval(mut self, interval: Duration) -> Self {
152 self.ping_interval = interval;
153 self
154 }
155
156 /// Set the ping timeout.
157 pub fn with_ping_timeout(mut self, timeout: Duration) -> Self {
158 self.ping_timeout = timeout;
159 self
160 }
161
162 /// Set maximum reconnection attempts (None = unlimited).
163 pub fn with_max_reconnect_attempts(mut self, max_attempts: Option<u32>) -> Self {
164 self.max_reconnect_attempts = max_attempts;
165 self
166 }
167
168 /// Set filtered listen events, this is included in the connection query-string.
169 /// The provided Vec should contain every event name that should be sent by the server.
170 /// If None, filtering is disabled so all events are sent.
171 pub fn with_filtered_events(mut self, events: Option<Vec<impl Into<String>>>) -> Self {
172 self.filtered_events = events.map(|events| events.into_iter().map(Into::into).collect());
173 self
174 }
175}
176#[cfg(feature = "websocket")]
177impl Default for WebsocketConfig {
178 fn default() -> Self {
179 Self {
180 url: "ws://localhost:3000/ws".to_string(),
181 authorization: None,
182 auto_reconnect: true,
183 reconnect_interval: Duration::from_secs(Self::WS_DEFAULT_RECONNECT_INTERVAL),
184 ping_interval: Duration::from_secs(Self::WS_DEFAULT_PING_INTERVAL),
185 ping_timeout: Duration::from_secs(Self::WS_DEFAULT_PING_TIMEOUT),
186 max_reconnect_attempts: None,
187 filtered_events: None
188 }
189 }
190}
191
192/// Complete client configuration.
193#[derive(Clone, Debug)]
194pub struct ClientConfig {
195
196 /// HTTP configuration.
197 pub http: HttpConfig,
198
199 /// Optional WebSocket configuration.
200 #[cfg(feature = "websocket")]
201 pub websocket: Option<WebsocketConfig>
202}
203impl ClientConfig {
204
205 /// Create a new configuration with only HTTP support.
206 ///
207 /// # Example
208 /// ```
209 /// use sms_client::config::ClientConfig;
210 ///
211 /// let config = ClientConfig::http_only("http://192.168.1.2:3000");
212 /// ```
213 pub fn http_only(url: impl Into<String>) -> Self {
214 Self {
215 http: HttpConfig::new(url),
216
217 #[cfg(feature = "websocket")]
218 websocket: None
219 }
220 }
221
222 /// Create a new configuration with both HTTP and WebSocket support.
223 ///
224 /// # Example
225 /// ```
226 /// use sms_client::config::ClientConfig;
227 ///
228 /// let config = ClientConfig::with_websocket(
229 /// "http://192.168.1.2:3000",
230 /// "ws://192.168.1.2:3000/ws"
231 /// );
232 /// ```
233 #[cfg(feature = "websocket")]
234 pub fn with_websocket(http_url: impl Into<String>, ws_url: impl Into<String>) -> Self {
235 Self {
236 http: HttpConfig::new(http_url),
237 websocket: Some(WebsocketConfig::new(ws_url))
238 }
239 }
240
241 /// Create a configuration from individual HTTP and WebSocket configs.
242 ///
243 /// # Example
244 /// ```
245 /// use std::time::Duration;
246 /// use sms_client::config::{ClientConfig, HttpConfig, WebsocketConfig};
247 ///
248 /// let http = HttpConfig::new("http://192.168.1.2:3000")
249 /// .with_auth("token123")
250 /// .with_base_timeout(Duration::from_secs(30));
251 ///
252 /// let ws = WebsocketConfig::new("ws://192.168.1.2:3000/ws")
253 /// .with_auth("token123")
254 /// .with_auto_reconnect(true)
255 /// .with_max_reconnect_attempts(Some(10));
256 ///
257 /// let config = ClientConfig::from_parts(http, Some(ws));
258 /// ```
259 #[cfg(feature = "websocket")]
260 pub fn from_parts(http: HttpConfig, websocket: Option<WebsocketConfig>) -> Self {
261 Self { http, websocket }
262 }
263
264 /// Set authorization for both HTTP and WebSocket.
265 ///
266 /// # Example
267 /// ```
268 /// use sms_client::config::ClientConfig;
269 ///
270 /// let config = ClientConfig::with_websocket(
271 /// "http://192.168.1.2:3000",
272 /// "ws://192.168.1.2:3000/ws"
273 /// ).with_auth("my-token");
274 /// ```
275 pub fn with_auth(mut self, token: impl Into<String>) -> Self {
276 let token = token.into();
277 self.http.authorization = Some(token.clone());
278
279 #[cfg(feature = "websocket")]
280 if let Some(ws) = &mut self.websocket {
281 ws.authorization = Some(token);
282 }
283 self
284 }
285
286 /// Configure the HTTP component.
287 ///
288 /// # Example
289 /// ```
290 /// use sms_client::config::ClientConfig;
291 /// use std::time::Duration;
292 ///
293 /// let config = ClientConfig::http_only("http://192.168.1.2:3000")
294 /// .configure_http(|http| {
295 /// http.with_base_timeout(Duration::from_secs(30))
296 /// .with_auth("token")
297 /// });
298 /// ```
299 pub fn configure_http<F>(mut self, f: F) -> Self
300 where
301 F: FnOnce(HttpConfig) -> HttpConfig,
302 {
303 self.http = f(self.http);
304 self
305 }
306
307 /// Configure the WebSocket component if present.
308 ///
309 /// # Example
310 /// ```
311 /// use sms_client::config::ClientConfig;
312 /// use std::time::Duration;
313 ///
314 /// let config = ClientConfig::with_websocket(
315 /// "http://192.168.1.2:3000",
316 /// "ws://192.168.1.2:3000/ws"
317 /// ).configure_websocket(|ws| {
318 /// ws.with_ping_interval(Duration::from_secs(60))
319 /// .with_max_reconnect_attempts(Some(5))
320 /// });
321 /// ```
322 #[cfg(feature = "websocket")]
323 pub fn configure_websocket<F>(mut self, f: F) -> Self
324 where
325 F: FnOnce(WebsocketConfig) -> WebsocketConfig,
326 {
327 if let Some(ws) = self.websocket {
328 self.websocket = Some(f(ws));
329 }
330 self
331 }
332
333 /// Add WebSocket support to an HTTP-only configuration.
334 ///
335 /// # Example
336 /// ```
337 /// use sms_client::config::{ClientConfig, WebsocketConfig};
338 ///
339 /// let config = ClientConfig::http_only("http://192.168.1.2:3000")
340 /// .add_websocket(WebsocketConfig::new("ws://192.168.1.2:3000/ws"));
341 /// ```
342 #[cfg(feature = "websocket")]
343 pub fn add_websocket(mut self, websocket: WebsocketConfig) -> Self {
344 self.websocket = Some(websocket);
345 self
346 }
347
348 /// Remove WebSocket support from the configuration.
349 #[cfg(feature = "websocket")]
350 pub fn without_websocket(mut self) -> Self {
351 self.websocket = None;
352 self
353 }
354}
355impl Default for ClientConfig {
356 fn default() -> Self {
357 Self {
358 http: HttpConfig::default(),
359
360 #[cfg(feature = "websocket")]
361 websocket: Some(WebsocketConfig::default()),
362 }
363 }
364}
365impl From<HttpConfig> for ClientConfig {
366 fn from(http: HttpConfig) -> Self {
367 ClientConfig { http, ..Default::default() }
368 }
369}