{"record":{"id":"5d33549d2aab1608","repo":"nautechsystems/nautilus_trader","slug":"heartbeat-secs-must-be-positive-when-set","errorCode":null,"errorMessage":"heartbeat_secs must be positive when set","messagePattern":"heartbeat_secs must be positive when set","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/betfair/src/stream/config.rs","lineNumber":79,"sourceCode":"    }\n}\n\nimpl BetfairStreamConfig {\n    #[must_use]\n    pub fn dead_peer_timeout_secs(&self) -> u64 {\n        self.heartbeat_timeout_secs\n            .unwrap_or(DEAD_PEER_TIMEOUT_MIN_SECS)\n    }\n\n    /// Validates heartbeat settings.\n    ///\n    /// # Errors\n    ///\n    /// Returns an error if an outbound interval is zero or an explicit dead-peer timeout is\n    /// shorter than two server intervals.\n    pub fn validate(&self) -> anyhow::Result<()> {\n        if self.heartbeat_secs == Some(0) {\n            anyhow::bail!(\"heartbeat_secs must be positive when set\");\n        }\n\n        if let Some(timeout_secs) = self.heartbeat_timeout_secs\n            && timeout_secs < DEAD_PEER_TIMEOUT_MIN_SECS\n        {\n            anyhow::bail!(\n                \"heartbeat_timeout_secs must cover at least two server heartbeat intervals \\\n                 ({DEAD_PEER_TIMEOUT_MIN_SECS}s), was {timeout_secs}s\",\n            );\n        }\n\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use rstest::rstest;","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/betfair/src/stream/config.rs#L61-L97","documentation":"The Betfair streaming config validator rejects a heartbeat interval of exactly 0 seconds. A zero heartbeat is meaningless (it would mean 'ping continuously'), so configuration with Some(0) fails validation before the stream connects. This guard runs inside StreamConfig::validate, called by connect_inner.","triggerScenarios":"Constructing a Betfair stream config with heartbeat_secs = Some(0) and then calling connect (via connect_inner -> validate).","commonSituations":"Setting the value from an environment variable parsed to 0 by default; a user typing heartbeat=0 intending 'disable heartbeat' instead of leaving it None; templated config files filled with placeholder zeros.","solutions":["Set heartbeat_secs to a positive value, e.g. Some(10) matching Betfair's recommended heartbeat (typically 5-30 seconds).","If you want no explicit heartbeat, leave the field as None instead of Some(0).","Fix the source of the value (env var, config file, CLI default) so it never produces 0."],"exampleFix":"// before\nlet mut config = StreamConfig::default();\nconfig.heartbeat_secs = Some(0); // fails validation\n// after\nconfig.heartbeat_secs = Some(10); // or leave as None","handlingStrategy":"validation","validationCode":"if let Some(hb) = heartbeat_secs {\n    assert!(hb > 0, \"heartbeat_secs must be positive when set\");\n}","typeGuard":null,"tryCatchPattern":"let config = StreamConfig { heartbeat_secs, .. };\nif let Err(e) = config.validate() {\n    return Err(format!(\"invalid stream config: {e}\"));\n}","preventionTips":["Use None, not Some(0), to mean 'no explicit heartbeat'.","Clamp parsed env/config integers to >= 1 before constructing the config.","Call validate() at config-load time, not just at connect time, for early failure."],"tags":["betfair","streaming","config","validation","rust"],"backgroundTag":"invalid-config-value","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}