hyperion_framework/containerisation/traits.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
23// Standard
24use std::collections::HashMap;
25use std::sync::Arc as StdArc;
26use std::sync::atomic::AtomicUsize;
27
28// Package
29use async_trait::async_trait;
30use tokio::sync::Notify;
31use tokio::sync::mpsc::{Receiver, Sender};
32
33// Local
34use crate::heartbeat::config::HeartbeatConfig;
35use crate::messages::client_broker_message::ClientBrokerMessage;
36use crate::messages::container_directive::ContainerDirective;
37use crate::messages::heartbeat::{HeartbeatRequest, HeartbeatResponse};
38
39// Traits
40pub trait Initialisable {
41 type ConfigType;
42 fn initialise(
43 container_state: StdArc<AtomicUsize>,
44 container_state_notify: StdArc<Notify>,
45 config: StdArc<Self::ConfigType>,
46 ) -> Self;
47}
48// For example, Hyperion Network Containerisation - Initialise Component
49// impl Initialisable for Component {
50// type ConfigType = Config;
51// fn initialise(container_state: StdArc<AtomicUsize>, container_state_notify: StdArc<Notify>, config: StdArc<Self::ConfigType>) -> Self {
52// // Will panic if there's a problem, which we want seeing we do this on startup
53// Component::new(container_state, container_state_notify, config)
54// }
55// }
56
57#[async_trait]
58pub trait Run {
59 // Uses async_trait which isn't perfect and does introduce a small overhead, but it is a lot
60 // cleaner than the alternative. Since this is only used once to start the component, I don't
61 // see it as a big deal. Performance overhead comes from lack of return type (compiler can't optimise)
62
63 // Using self, instead of &mut self, as the clone must be consumed by Run to ensure a fresh
64 // state is kept in the container
65 type Message;
66 async fn run(
67 self,
68 comp_in_rx: Receiver<Self::Message>,
69 comp_out_tx: Sender<ClientBrokerMessage<Self::Message>>,
70 );
71}
72// For example, Hyperion Network Containerisation - Run Component
73// #[async_trait]
74// impl Run for Component {
75// type Message = ContainerMessage;
76// async fn run(mut self, mut comp_in_rx: Receiver<Self::Message>, comp_out_tx: Sender<ClientBrokerMessage<Self::Message>>) {
77// log::debug!("{} has started successfully", self.config.container_id.name);
78// loop {
79// if self.component_state == ComponentState::Dead { break; }
80// tokio::select! {
81// Some(message) = comp_in_rx.recv() => {
82// log::trace!("{} received message: {:?}", self.config.container_id.name, message);
83// if let Some(result) = self.process_incoming_message(message).await {
84// let from_location = format!("{} main loop", self.config.container_id.name);
85// let to_location = format!("{} Container", self.config.container_id.name);
86// add_to_tx_with_retry(&comp_out_tx, &result, &from_location, &to_location).await;
87// }
88// }
89// _ = self.container_state_notify.notified() => {
90// // Check if container is shutting down
91// if self.container_state.load(Ordering::SeqCst) == ContainerState::ShuttingDown as usize {
92// self.component_state = ComponentState::Dormant;
93// break;
94// }
95// }
96// }
97// }
98// log::info!("{} task has closed", self.config.container_id.name);
99// }
100// }
101
102pub trait HyperionContainerDirectiveMessage {
103 fn get_container_directive_message(&self) -> Option<&ContainerDirective>;
104}
105// For example,
106// impl HyperionContainerDirectiveMessage for ContainerMessage {
107// // Gets ContainerDirective if is instance
108// fn get_container_directive_message(&self) -> Option<&ContainerDirective> {
109// if let ContainerMessage::ContainerDirectiveMsg(directive) = self {
110// Some(directive)
111// } else {
112// None
113// }
114// }
115// }
116
117pub trait ContainerIdentidy {
118 fn container_identity(&self) -> HashMap<String, String>;
119}
120// For example,
121// impl ContainerIdentidy for Config {
122// fn container_identity(&self) -> HashMap<String, String> {
123// let mut identity = HashMap::new();
124// identity.insert("name".to_string(), self.container.name.clone());
125// identity.insert("version".to_string(), self.container.version.clone());
126// identity.insert("version_title".to_string(), self.container.version_title.clone());
127// identity.insert("software_collection".to_string(), self.container.software_collection.clone());
128// identity
129// }
130// }
131
132pub trait LogLevel {
133 fn log_level(&self) -> &str;
134}
135
136/// Provides heartbeat configuration from a component's parsed XML config.
137/// Implement this on your top-level `Config` struct. Return `None` to disable heartbeats.
138pub trait HeartbeatConfigProvider {
139 fn heartbeat_config(&self) -> Option<HeartbeatConfig>;
140}
141// For example,
142// impl LogLevel for Config {
143// fn log_level(&self) -> &str {
144// &self.logging.level
145// }
146// }
147
148/// Allows the container infrastructure to construct and parse heartbeat messages from the
149/// application's message type `T` without knowing what `T` is.
150/// Implement this on your top-level message enum (e.g. `ContainerMessage`).
151pub trait HyperionHeartbeatMessage {
152 fn as_heartbeat_request(&self) -> Option<HeartbeatRequest>;
153 fn as_heartbeat_response(&self) -> Option<HeartbeatResponse>;
154 fn make_heartbeat_request(
155 request_id: u64,
156 sender_name: String,
157 timestamp_ms: u64,
158 ) -> Option<Self>
159 where
160 Self: Sized;
161 fn make_heartbeat_response(
162 request_id: u64,
163 timestamp_ms: u64,
164 component_alive: bool,
165 ms_since_last_activity: u64,
166 container_state_val: usize,
167 ) -> Option<Self>
168 where
169 Self: Sized;
170}