Skip to main content

hyperion_framework/containerisation/
hyperion_container_factory.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::fmt::Debug;
25use std::fs;
26use std::path::PathBuf;
27use std::str::FromStr;
28use std::sync::{Arc as StdArc, atomic::AtomicUsize};
29
30// Package
31use log::LevelFilter;
32use serde::{Serialize, de::DeserializeOwned};
33use tokio::sync::{Notify, mpsc};
34use tokio::task;
35use tokio::time::{Duration, sleep};
36
37// Local
38use crate::containerisation::client_broker::ClientBroker;
39use crate::containerisation::hyperion_container::HyperionContainer;
40use crate::containerisation::traits::{
41    ContainerIdentidy, HyperionContainerDirectiveMessage, Initialisable, LogLevel, Run,
42};
43use crate::logging::logging_service::initialise_logger;
44use crate::network::network_topology::NetworkTopology;
45use crate::network::server::Server;
46use crate::utilities::load_config;
47
48// A is the HyperionContainer Component template - must implement Initialisable and Run traits
49// C is an StdArc instance of a populated config struct - specific to the component
50// T is primary Component message type
51pub async fn create<A, C, T>(
52    config_path_str: &str,
53    network_topology_path_str: &str,
54    container_state: StdArc<AtomicUsize>,
55    container_state_notify: StdArc<Notify>,
56    main_rx: mpsc::Receiver<T>,
57) -> HyperionContainer<T>
58where
59    A: Initialisable<ConfigType = C> + Run<Message = T> + Send + 'static + Sync + Debug,
60    C: Debug + Send + 'static + DeserializeOwned + Sync + LogLevel + ContainerIdentidy,
61    T: HyperionContainerDirectiveMessage
62        + Debug
63        + Send
64        + 'static
65        + DeserializeOwned
66        + Sync
67        + Clone
68        + Serialize,
69{
70    // Read Component and network configs (program should exit if this fails)
71    let config_path: PathBuf = fs::canonicalize(config_path_str)
72        .unwrap_or_else(|e| panic!("Could not canonicalize '{config_path_str}': {e}"));
73    let component_config: StdArc<C> = load_config::load_config::<C>(&config_path)
74        .unwrap_or_else(|e| panic!("Failed to load component config from '{config_path:?}': {e}"));
75    let network_topology_path: PathBuf = fs::canonicalize(network_topology_path_str)
76        .unwrap_or_else(|e| panic!("Could not canonicalize '{network_topology_path_str}': {e}"));
77    let network_topology: StdArc<NetworkTopology> =
78        load_config::load_config::<NetworkTopology>(&network_topology_path).unwrap_or_else(|e| {
79            panic!("Failed to load network topology from '{network_topology_path:?}': {e}")
80        });
81
82    // Initialise logger
83    let log_level: LevelFilter = LevelFilter::from_str(component_config.log_level())
84        .unwrap_or_else(|e| {
85            // Can't use logger here as it doesn't exist yet
86            println!("Log level was not parsed correctly: {e:?}\nDefaulting to 'Trace' log level.");
87            LevelFilter::Trace
88        });
89    initialise_logger(log_level).unwrap_or_else(|e| panic!("Failed to initialise logger: {e:?}"));
90
91    // Initialise console - temporary startup printout
92    for (key, value) in component_config.container_identity().iter() {
93        log::debug!("{key}: {value}");
94    }
95
96    // TODO: Improve startup messaging. Implement project boilerplate printout etc
97    log::info!(
98        "Building Hyperion Container for {}...",
99        component_config
100            .container_identity()
101            .get("name")
102            .unwrap_or(&"Unknown".to_string())
103    );
104
105    // Initialise component - Ensure the component can build without errors before starting comms
106    let component_archetype = A::initialise(
107        container_state.clone(),
108        container_state_notify.clone(),
109        component_config.clone(),
110    );
111
112    // Initialise and run Server
113    let (server_tx, server_rx) = mpsc::channel::<T>(32);
114    let arc_server: StdArc<Server<T>> = Server::new(
115        network_topology.server_address.clone(),
116        server_tx,
117        container_state.clone(),
118        container_state_notify.clone(),
119    );
120    task::spawn(async move {
121        // No need to handle return as Server will set state to shutdown if it fails
122        if let Err(e) = Server::run(arc_server).await {
123            log::error!("Server encountered an error: {e:?}");
124        }
125    });
126
127    // Allow time for server to stabilise
128    sleep(Duration::from_secs(2)).await;
129
130    // Initialise and run client broker
131    let client_broker: ClientBroker<T> = ClientBroker::init(
132        network_topology,
133        container_state.clone(),
134        container_state_notify.clone(),
135    );
136
137    // Allow time for client(s) to stabilise
138    sleep(Duration::from_secs(2)).await;
139
140    // Using previous elements, build HyperionContainer
141    HyperionContainer::<T>::create(
142        component_archetype,
143        container_state,
144        container_state_notify,
145        client_broker,
146        main_rx,
147        server_rx,
148    )
149}