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}