hyperion_framework/containerisation/
hyperion_container_factory.rs

1// -------------------------------------------------------------------------------------------------
2// Hyperion Framework
3// https://github.com/Bazzz-1/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<A, T>
58where
59    A: Initialisable<ConfigType = C> + Run<Message = T> + Send + 'static + Sync + Clone + 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
71    let config_path: PathBuf =
72        fs::canonicalize(config_path_str).expect("Could not create path for component config");
73    let component_config: StdArc<C> =
74        load_config::load_config::<C>(config_path).expect("Component config could not be loaded");
75    let network_topology_path = fs::canonicalize(network_topology_path_str)
76        .expect("Could not create path for network config");
77    let network_topology: StdArc<NetworkTopology> =
78        load_config::load_config::<NetworkTopology>(network_topology_path)
79            .expect("Component config could not be loaded");
80
81    // Initialise logger
82    initialise_logger(Some(
83        LevelFilter::from_str(component_config.log_level()).unwrap_or(LevelFilter::Info),
84    ))
85    .unwrap();
86
87    // Initialise console - temporary startup printout
88    for (key, value) in component_config.container_identity().iter() {
89        log::debug!("{key}: {value}");
90    }
91
92    // TODO: Improve startup messaging. Implement project boilerplate printout etc
93    log::info!(
94        "Building Hyperion Container for {}...",
95        component_config
96            .container_identity()
97            .get("name")
98            .unwrap_or(&"Unknown".to_string())
99    );
100
101    // Initialise component - Ensure the component can build without errors before starting comms
102    let component_archetype = A::initialise(
103        container_state.clone(),
104        container_state_notify.clone(),
105        component_config.clone(),
106    );
107
108    // Initialise and run Server
109    let (server_tx, server_rx) = mpsc::channel::<T>(32);
110    let arc_server: StdArc<Server<T>> = Server::new(
111        network_topology.server_address.clone(),
112        server_tx,
113        container_state.clone(),
114        container_state_notify.clone(),
115    );
116    task::spawn(async move {
117        // No need to handle return as Server will set state to shutdown if it fails
118        if let Err(e) = Server::run(arc_server).await {
119            log::error!("Server encountered an error: {e:?}");
120        }
121    });
122
123    // Allow time for server to stabilise
124    sleep(Duration::from_secs(2)).await;
125
126    // Initialise and run client broker
127    let client_broker: ClientBroker<T> = ClientBroker::init(
128        network_topology,
129        container_state.clone(),
130        container_state_notify.clone(),
131    );
132
133    // Allow time for client(s) to stabilise
134    sleep(Duration::from_secs(2)).await;
135
136    // Using previous elements, build HyperionContainer
137    HyperionContainer::<A, T>::create(
138        component_archetype,
139        container_state,
140        container_state_notify,
141        client_broker,
142        main_rx,
143        server_rx,
144    )
145}