Skip to main content

hyperion_framework/heartbeat/
config.rs

1// -------------------------------------------------------------------------------------------------
2// Hyperion Framework
3// https://github.com/robert-hannah/hyperion-framework
4//
5// A lightweight component-based TCP framework for building service-oriented Rust applications with
6// CLI control, async messaging, and lifecycle management.
7//
8// Copyright 2025 Robert Hannah
9//
10// Licensed under the Apache License, Version 2.0 (the "License");
11// you may not use this file except in compliance with the License.
12// You may obtain a copy of the License at
13//
14//     http://www.apache.org/licenses/LICENSE-2.0
15//
16// Unless required by applicable law or agreed to in writing, software
17// distributed under the License is distributed on an "AS IS" BASIS,
18// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19// See the License for the specific language governing permissions and
20// limitations under the License.
21// -------------------------------------------------------------------------------------------------
22
23use serde::{Deserialize, Serialize};
24
25#[derive(Debug, Clone, PartialEq)]
26pub enum HeartbeatMode {
27    Sender,
28    Receiver,
29    Disabled,
30}
31
32#[derive(Debug, Clone)]
33pub struct HeartbeatSenderConfig {
34    pub interval_ms: u64,
35    pub response_timeout_ms: u64,
36    pub targets: Vec<String>,
37}
38
39#[derive(Debug, Clone)]
40pub struct HeartbeatReceiverConfig {
41    /// How long to wait without receiving a request before triggering the timeout handler.
42    pub timeout_ms: u64,
43}
44
45#[derive(Debug, Clone)]
46pub struct HeartbeatConfig {
47    pub mode: HeartbeatMode,
48    pub sender: Option<HeartbeatSenderConfig>,
49    pub receiver: Option<HeartbeatReceiverConfig>,
50}
51
52impl HeartbeatConfig {
53    pub fn sender(interval_ms: u64, response_timeout_ms: u64, targets: Vec<String>) -> Self {
54        Self {
55            mode: HeartbeatMode::Sender,
56            sender: Some(HeartbeatSenderConfig {
57                interval_ms,
58                response_timeout_ms,
59                targets,
60            }),
61            receiver: None,
62        }
63    }
64
65    pub fn receiver(timeout_ms: u64) -> Self {
66        Self {
67            mode: HeartbeatMode::Receiver,
68            sender: None,
69            receiver: Some(HeartbeatReceiverConfig { timeout_ms }),
70        }
71    }
72
73    pub fn disabled() -> Self {
74        Self {
75            mode: HeartbeatMode::Disabled,
76            sender: None,
77            receiver: None,
78        }
79    }
80}
81
82/// Deserializable form of heartbeat configuration as it appears in a component's
83/// `configuration.xml` under the `<container>` section.
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct HeartbeatXml {
86    pub mode: String,
87    #[serde(default)]
88    pub interval_ms: Option<u64>,
89    #[serde(default)]
90    pub response_timeout_ms: Option<u64>,
91    #[serde(default)]
92    pub targets: Option<HeartbeatTargets>,
93    #[serde(default)]
94    pub timeout_ms: Option<u64>,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct HeartbeatTargets {
99    #[serde(rename = "target")]
100    pub targets: Vec<String>,
101}
102
103impl HeartbeatXml {
104    pub fn to_config(&self) -> Option<HeartbeatConfig> {
105        match self.mode.as_str() {
106            "sender" => {
107                let interval_ms = self.interval_ms.unwrap_or_else(|| {
108                    panic!("Heartbeat mode is 'sender' but interval_ms is missing")
109                });
110                let response_timeout_ms = self.response_timeout_ms.unwrap_or_else(|| {
111                    panic!("Heartbeat mode is 'sender' but response_timeout_ms is missing")
112                });
113                let targets = self
114                    .targets
115                    .as_ref()
116                    .map(|t| t.targets.clone())
117                    .unwrap_or_default();
118                Some(HeartbeatConfig::sender(
119                    interval_ms,
120                    response_timeout_ms,
121                    targets,
122                ))
123            }
124            "receiver" => {
125                let timeout_ms = self.timeout_ms.unwrap_or_else(|| {
126                    panic!("Heartbeat mode is 'receiver' but timeout_ms is missing")
127                });
128                Some(HeartbeatConfig::receiver(timeout_ms))
129            }
130            "disabled" => None,
131            unknown => panic!(
132                "Unknown heartbeat mode: '{unknown}'. Expected 'sender', 'receiver', or 'disabled'"
133            ),
134        }
135    }
136}