ferrox_app/lib.rs
1//! # Ferrox App (`ferrox-app`)
2//!
3//! `ferrox-app` provides the primary application bootstrapper and multi-transport lifecycle orchestrator
4//! for the Ferrox framework. It manages concurrent server instances (HTTP, gRPC, WebSockets, etc.) and enforces
5//! graceful shutdown handling across UNIX signals (`SIGTERM`) and cross-platform interrupts (`Ctrl+C`).
6//!
7//! ## Architectural Role
8//! In enterprise applications, backends often need to serve multiple network protocols simultaneously (e.g. Axum for HTTP/REST,
9//! Tonic for gRPC inter-service communication). `FerroxApp` encapsulates these network transports into unified `Arc<dyn Transport>`
10//! workers and manages their startup, execution lifecycle, and teardown concurrently.
11//!
12//! ## Key Features
13//! - 🌐 **Multi-Transport Execution**: Boot HTTP, gRPC, and background listeners concurrently.
14//! - 🛡️ **Graceful Shutdown Orchestration**: Catches OS termination signals and shuts down active transport threads cleanly.
15//! - ⚡ **Integration with Tower & Sentry**: Built-in support for middleware, cors, timeouts, and error capturing.
16//!
17//! ## Example Usage
18//! ```rust,no_run
19//! use ferrox_app::FerroxApp;
20//! use ferrox_transports::http::HttpTransport;
21//! use axum::{Router, routing::get};
22//!
23//! #[tokio::main]
24//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
25//! let router = Router::new().route("/health", get(|| async { "OK" }));
26//! let transport = HttpTransport::new(router, 8080);
27//!
28//! FerroxApp::new()
29//! .add_transport(transport)
30//! .start()
31//! .await?;
32//!
33//! Ok(())
34//! }
35//! ```
36
37use std::sync::Arc;
38use tokio::signal;
39use tokio::task::JoinHandle;
40use tracing::{info, error};
41use ferrox_errors::AppError;
42use ferrox_transports::Transport;
43
44pub struct FerroxApp {
45 transports: Vec<Arc<dyn Transport>>,
46}
47
48impl Default for FerroxApp {
49 fn default() -> Self {
50 Self::new()
51 }
52}
53
54impl FerroxApp {
55 pub fn new() -> Self {
56 Self {
57 transports: Vec::new(),
58 }
59 }
60
61 /// Add a transport layer to the app (e.g. HttpTransport, GrpcTransport, FtpTransport)
62 pub fn add_transport<T: Transport + 'static>(mut self, transport: T) -> Self {
63 self.transports.push(Arc::new(transport));
64 self
65 }
66
67 /// Starts all configured transports concurrently and waits for shutdown signal
68 pub async fn start(self) -> Result<(), AppError> {
69 info!("Starting FerroxApp multi-transport system...");
70
71 if self.transports.is_empty() {
72 return Err(AppError::InternalServerError(
73 "Cannot start FerroxApp: no transports configured!".into(),
74 ));
75 }
76
77 let mut join_handles: Vec<JoinHandle<Result<(), AppError>>> = Vec::new();
78
79 for transport in self.transports {
80 let t = Arc::clone(&transport);
81
82 let handle = tokio::spawn(async move {
83 info!("Booting transport: {}", t.name());
84 if let Err(e) = t.start().await {
85 error!("Transport {} crashed: {:?}", t.name(), e);
86 return Err(e);
87 }
88 Ok(())
89 });
90
91 join_handles.push(handle);
92 }
93
94 // Wait for shutdown signal
95 shutdown_signal().await;
96
97 info!("Graceful shutdown initiated...");
98
99 // In a real implementation we would send a cancellation token to all join handles
100 for handle in join_handles {
101 handle.abort();
102 }
103
104 info!("FerroxApp stopped gracefully.");
105 Ok(())
106 }
107}
108
109async fn shutdown_signal() {
110 let ctrl_c = async {
111 signal::ctrl_c()
112 .await
113 .expect("failed to install Ctrl+C handler");
114 };
115
116 #[cfg(unix)]
117 let terminate = async {
118 signal::unix::signal(signal::unix::SignalKind::terminate())
119 .expect("failed to install signal handler")
120 .recv()
121 .await;
122 };
123
124 #[cfg(not(unix))]
125 let terminate = std::future::pending::<()>();
126
127 tokio::select! {
128 _ = ctrl_c => {},
129 _ = terminate => {},
130 }
131
132 info!("Shutdown signal received, starting graceful shutdown...");
133}
134
135pub fn setup() {
136 println!("ferrox-app initialized: Multi-Transport FerroxApp bootstrap ready.");
137}