elif_http/
simple_stateful_server.rs

1//! Simple HTTP server with DI integration using closure approach
2//! 
3//! This approach avoids Router<State> issues by using closures to capture
4//! DI container context in handlers.
5
6use crate::{HttpConfig, HttpError, HttpResult};
7use elif_core::Container;
8use axum::{
9    Router,
10    routing::get,
11    response::Json,
12};
13use serde_json::{json, Value};
14use std::net::SocketAddr;
15use std::sync::Arc;
16use tokio::signal;
17use tracing::{info, warn};
18
19/// Simple HTTP server with DI container integration
20pub struct SimpleStatefulHttpServer {
21    router: Router,
22    addr: SocketAddr,
23}
24
25impl SimpleStatefulHttpServer {
26    /// Create new HTTP server with DI container
27    pub fn new(container: Arc<Container>, config: HttpConfig) -> HttpResult<Self> {
28        let app_config = container.config();
29        let addr = format!("{}:{}", app_config.server.host, app_config.server.port)
30            .parse::<SocketAddr>()
31            .map_err(|e| HttpError::config(format!("Invalid server address: {}", e)))?;
32
33        // Create health check handler with captured container
34        let health_container = container.clone();
35        let health_config = config.clone();
36        let health_handler = move || {
37            let container = health_container.clone();
38            let config = health_config.clone();
39            async move {
40                health_check_with_di(container, config).await
41            }
42        };
43
44        // Create router with captured DI container
45        let router = Router::new()
46            .route(&config.health_check_path, get(health_handler));
47
48        Ok(Self { router, addr })
49    }
50
51    /// Start the server
52    pub async fn run(self) -> HttpResult<()> {
53        info!("Starting simple stateful HTTP server on {}", self.addr);
54
55        let listener = tokio::net::TcpListener::bind(self.addr)
56            .await
57            .map_err(|e| HttpError::startup(format!("Failed to bind to {}: {}", self.addr, e)))?;
58
59        info!("Simple stateful HTTP server listening on {}", self.addr);
60
61        axum::serve(listener, self.router)
62            .with_graceful_shutdown(shutdown_signal())
63            .await
64            .map_err(|e| HttpError::startup(format!("Server failed: {}", e)))?;
65
66        info!("Simple stateful HTTP server stopped gracefully");
67        Ok(())
68    }
69}
70
71/// Health check handler with DI container access via closure capture
72async fn health_check_with_di(container: Arc<Container>, config: HttpConfig) -> Json<Value> {
73    // Check database connection
74    let database = container.database();
75    let db_healthy = database.is_connected();
76    
77    let app_config = container.config();
78    let response = json!({
79        "status": if db_healthy { "healthy" } else { "degraded" },
80        "timestamp": chrono::Utc::now().to_rfc3339(),
81        "version": "0.1.0",
82        "environment": format!("{:?}", app_config.environment),
83        "server": "simple-stateful",
84        "services": {
85            "database": if db_healthy { "healthy" } else { "unhealthy" },
86            "container": "healthy"
87        },
88        "config": {
89            "request_timeout": config.request_timeout_secs,
90            "health_check_path": config.health_check_path,
91            "tracing_enabled": config.enable_tracing
92        }
93    });
94
95    if !db_healthy {
96        warn!("Health check degraded: database not connected");
97    }
98
99    Json(response)
100}
101
102/// Graceful shutdown signal handler
103async fn shutdown_signal() {
104    let ctrl_c = async {
105        signal::ctrl_c()
106            .await
107            .expect("failed to install Ctrl+C handler");
108    };
109
110    #[cfg(unix)]
111    let terminate = async {
112        signal::unix::signal(signal::unix::SignalKind::terminate())
113            .expect("failed to install signal handler")
114            .recv()
115            .await;
116    };
117
118    #[cfg(not(unix))]
119    let terminate = std::future::pending::<()>();
120
121    tokio::select! {
122        _ = ctrl_c => {
123            info!("Received Ctrl+C, initiating graceful shutdown");
124        },
125        _ = terminate => {
126            info!("Received terminate signal, initiating graceful shutdown");
127        },
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134    use elif_core::container::test_implementations::*;
135
136    fn create_test_container() -> Arc<Container> {
137        let config = Arc::new(create_test_config());
138        let database = Arc::new(TestDatabase::new()) as Arc<dyn elif_core::DatabaseConnection>;
139        
140        Container::builder()
141            .config(config)
142            .database(database)
143            .build()
144            .unwrap()
145            .into()
146    }
147
148    #[test]
149    fn test_simple_stateful_server_creation() {
150        let container = create_test_container();
151        let config = HttpConfig::default();
152
153        let server = SimpleStatefulHttpServer::new(container, config);
154        assert!(server.is_ok());
155
156        let server = server.unwrap();
157        assert_eq!(server.addr.port(), 8080);
158    }
159
160    #[tokio::test]
161    async fn test_health_check_with_di() {
162        let container = create_test_container();
163        let config = HttpConfig::default();
164
165        let result = health_check_with_di(container, config).await;
166        let value = result.0;
167        
168        assert_eq!(value.get("status").and_then(|v| v.as_str()).unwrap(), "healthy");
169        assert_eq!(value.get("server").and_then(|v| v.as_str()).unwrap(), "simple-stateful");
170        
171        // Check that we have DI container info
172        assert!(value.get("services").is_some());
173        assert!(value.get("config").is_some());
174    }
175}