Skip to main content

nautilus_betfair/stream/
config.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Configuration for the Betfair stream client.
17
18use crate::common::consts::{
19    BETFAIR_STREAM_HOST, BETFAIR_STREAM_PORT, BETFAIR_STREAM_SERVER_HEARTBEAT_MS,
20};
21
22pub const BETFAIR_STREAM_HEARTBEAT_MIN_MS: u64 = 500;
23pub const BETFAIR_STREAM_HEARTBEAT_MAX_MS: u64 = 5_000;
24const DEAD_PEER_TIMEOUT_MIN_SECS: u64 = BETFAIR_STREAM_SERVER_HEARTBEAT_MS
25    .saturating_mul(2)
26    .div_ceil(1_000);
27
28/// Configuration for the Betfair Exchange Stream API client.
29#[derive(Debug, Clone)]
30pub struct BetfairStreamConfig {
31    /// Stream host (default: `stream-api.betfair.com`).
32    pub host: String,
33    /// Stream TLS port (default: 443).
34    pub port: u16,
35    /// Optional interval between outbound client heartbeat messages in seconds (default: `None`).
36    pub heartbeat_secs: Option<u64>,
37    /// Optional dead-peer timeout override in seconds.
38    ///
39    /// When unset, the timeout is two server heartbeat intervals.
40    pub heartbeat_timeout_secs: Option<u64>,
41    /// Initial reconnection back-off delay in milliseconds (default: 2 000).
42    pub reconnect_delay_initial_ms: u64,
43    /// Maximum reconnection back-off delay in milliseconds (default: 30 000).
44    pub reconnect_delay_max_ms: u64,
45    /// Use TLS (default: true). Override with `false` only for local testing.
46    #[doc(hidden)]
47    pub use_tls: bool,
48}
49
50impl Default for BetfairStreamConfig {
51    fn default() -> Self {
52        Self {
53            host: BETFAIR_STREAM_HOST.to_string(),
54            port: BETFAIR_STREAM_PORT,
55            heartbeat_secs: None,
56            heartbeat_timeout_secs: None,
57            reconnect_delay_initial_ms: 2_000,
58            reconnect_delay_max_ms: 30_000,
59            use_tls: true,
60        }
61    }
62}
63
64impl BetfairStreamConfig {
65    #[must_use]
66    pub fn dead_peer_timeout_secs(&self) -> u64 {
67        self.heartbeat_timeout_secs
68            .unwrap_or(DEAD_PEER_TIMEOUT_MIN_SECS)
69    }
70
71    /// Validates heartbeat settings.
72    ///
73    /// # Errors
74    ///
75    /// Returns an error if an outbound interval is zero or an explicit dead-peer timeout is
76    /// shorter than two server intervals.
77    pub fn validate(&self) -> anyhow::Result<()> {
78        if self.heartbeat_secs == Some(0) {
79            anyhow::bail!("heartbeat_secs must be positive when set");
80        }
81
82        if let Some(timeout_secs) = self.heartbeat_timeout_secs
83            && timeout_secs < DEAD_PEER_TIMEOUT_MIN_SECS
84        {
85            anyhow::bail!(
86                "heartbeat_timeout_secs must cover at least two server heartbeat intervals \
87                 ({DEAD_PEER_TIMEOUT_MIN_SECS}s), was {timeout_secs}s",
88            );
89        }
90
91        Ok(())
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use rstest::rstest;
98
99    use super::*;
100
101    #[rstest]
102    fn test_stream_config_defaults() {
103        let config = BetfairStreamConfig::default();
104        assert_eq!(config.host, BETFAIR_STREAM_HOST);
105        assert_eq!(config.port, BETFAIR_STREAM_PORT);
106        assert_eq!(config.heartbeat_secs, None);
107        assert_eq!(config.heartbeat_timeout_secs, None);
108        assert_eq!(config.dead_peer_timeout_secs(), 10);
109        assert_eq!(config.reconnect_delay_initial_ms, 2_000);
110        assert_eq!(config.reconnect_delay_max_ms, 30_000);
111        assert!(config.use_tls);
112    }
113
114    #[rstest]
115    fn test_stream_config_rejects_short_dead_peer_override() {
116        let config = BetfairStreamConfig {
117            heartbeat_timeout_secs: Some(9),
118            ..Default::default()
119        };
120
121        assert!(config.validate().is_err());
122    }
123}