Skip to main content

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}