elif_http/
server.rs

1//! # Elif HTTP Server
2//! 
3//! A NestJS-like HTTP server that provides a clean, intuitive API while using Axum under the hood.
4//! Users interact only with framework types - Axum is completely abstracted away.
5
6use crate::{
7    HttpConfig, HttpError, HttpResult,
8    ElifRouter, 
9    MiddlewarePipeline, Middleware,
10};
11use elif_core::Container;
12use std::net::SocketAddr;
13use std::sync::Arc;
14use tokio::signal;
15use tracing::{info, warn};
16
17/// The main HTTP server - NestJS-like experience
18/// 
19/// # Example
20/// 
21/// ```rust,no_run
22/// use elif_http::{Server, HttpConfig};
23/// use elif_core::{Container, container::test_implementations::*};
24/// use std::sync::Arc;
25/// 
26/// #[tokio::main]
27/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
28///     let config = Arc::new(create_test_config());
29///     let database = Arc::new(TestDatabase::new()) as Arc<dyn elif_core::DatabaseConnection>;
30///     
31///     let container = Container::builder()
32///         .config(config)
33///         .database(database)
34///         .build()?;
35///         
36///     let server = Server::new(container, HttpConfig::default())?;
37///     server.listen("0.0.0.0:3000").await?;
38///     
39///     Ok(())
40/// }
41/// ```
42pub struct Server {
43    container: Arc<Container>,
44    config: HttpConfig,
45    router: Option<ElifRouter>,
46    middleware: MiddlewarePipeline,
47}
48
49impl Server {
50    /// Create a new server instance
51    pub fn new(container: Container, config: HttpConfig) -> HttpResult<Self> {
52        Ok(Self {
53            container: Arc::new(container),
54            config,
55            router: None,
56            middleware: MiddlewarePipeline::new(),
57        })
58    }
59
60    /// Create a new server with existing Arc<Container>
61    pub fn with_container(container: Arc<Container>, config: HttpConfig) -> HttpResult<Self> {
62        Ok(Self {
63            container,
64            config,
65            router: None,
66            middleware: MiddlewarePipeline::new(),
67        })
68    }
69
70    /// Set custom routes using framework router
71    /// 
72    /// # Example
73    /// 
74    /// ```rust,no_run
75    /// use elif_http::{Server, ElifRouter, HttpConfig};
76    /// use elif_core::{Container, container::test_implementations::*};
77    /// use std::sync::Arc;
78    /// 
79    /// # async fn get_users() -> &'static str { "users" }
80    /// # async fn create_user() -> &'static str { "created" }
81    /// 
82    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
83    /// let config = Arc::new(create_test_config());
84    /// let database = Arc::new(TestDatabase::new()) as Arc<dyn elif_core::DatabaseConnection>;
85    /// 
86    /// let container = Container::builder()
87    ///     .config(config)
88    ///     .database(database)
89    ///     .build()?;
90    ///     
91    /// let mut server = Server::new(container, HttpConfig::default())?;
92    /// 
93    /// let router = ElifRouter::new()
94    ///     .get("/users", get_users)
95    ///     .post("/users", create_user);
96    /// 
97    /// server.use_router(router);
98    /// # Ok(())
99    /// # }
100    /// ```
101    pub fn use_router(&mut self, router: ElifRouter) -> &mut Self {
102        self.router = Some(router);
103        self
104    }
105
106    /// Add middleware to the server
107    /// 
108    /// # Example
109    /// 
110    /// ```rust,no_run
111    /// use elif_http::{Server, HttpConfig};
112    /// use elif_core::{Container, container::test_implementations::*};
113    /// use std::sync::Arc;
114    /// 
115    /// # struct LoggingMiddleware;
116    /// # impl LoggingMiddleware { 
117    /// #     fn default() -> Self { LoggingMiddleware } 
118    /// # }
119    /// # impl elif_http::Middleware for LoggingMiddleware {}
120    /// 
121    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
122    /// let config = Arc::new(create_test_config());
123    /// let database = Arc::new(TestDatabase::new()) as Arc<dyn elif_core::DatabaseConnection>;
124    /// 
125    /// let container = Container::builder()
126    ///     .config(config)
127    ///     .database(database)
128    ///     .build()?;
129    ///     
130    /// let mut server = Server::new(container, HttpConfig::default())?;
131    /// server.use_middleware(LoggingMiddleware::default());
132    /// # Ok(())
133    /// # }
134    /// ```
135    pub fn use_middleware<M>(&mut self, middleware: M) -> &mut Self 
136    where 
137        M: Middleware + 'static,
138    {
139        self.middleware = std::mem::take(&mut self.middleware).add(middleware);
140        self
141    }
142
143    /// Start the server on the specified address
144    /// 
145    /// # Example
146    /// 
147    /// ```rust,no_run
148    /// # use elif_http::{Server, HttpConfig};
149    /// # use elif_core::{Container, container::test_implementations::*};
150    /// # use std::sync::Arc;
151    /// # 
152    /// # #[tokio::main]
153    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
154    /// #     let config = Arc::new(create_test_config());
155    /// #     let database = Arc::new(TestDatabase::new()) as Arc<dyn elif_core::DatabaseConnection>;
156    /// #     
157    /// #     let container = Container::builder()
158    /// #         .config(config)
159    /// #         .database(database)
160    /// #         .build()?;
161    /// #         
162    /// #     let server = Server::new(container, HttpConfig::default())?;
163    /// server.listen("0.0.0.0:3000").await?;
164    /// #     Ok(())
165    /// # }
166    /// ```
167    pub async fn listen<A: Into<String>>(self, addr: A) -> HttpResult<()> {
168        let addr_str = addr.into();
169        let socket_addr: SocketAddr = addr_str.parse()
170            .map_err(|e| HttpError::config(format!("Invalid address '{}': {}", addr_str, e)))?;
171
172        self.listen_on(socket_addr).await
173    }
174
175    /// Start the server on the specified SocketAddr
176    pub async fn listen_on(self, addr: SocketAddr) -> HttpResult<()> {
177        info!("🚀 Starting Elif server on {}", addr);
178        info!("📋 Health check endpoint: {}", self.config.health_check_path);
179        
180        // Build the internal router
181        let axum_router = self.build_internal_router().await?;
182        
183        // Create TCP listener
184        let listener = tokio::net::TcpListener::bind(addr)
185            .await
186            .map_err(|e| HttpError::startup(format!("Failed to bind to {}: {}", addr, e)))?;
187
188        info!("✅ Server listening on {}", addr);
189        info!("🔧 Framework: Elif.rs (Axum under the hood)");
190
191        // Serve with graceful shutdown
192        axum::serve(listener, axum_router.into_make_service_with_connect_info::<SocketAddr>())
193            .with_graceful_shutdown(shutdown_signal())
194            .await
195            .map_err(|e| HttpError::internal(format!("Server error: {}", e)))?;
196
197        info!("🛑 Server shut down gracefully");
198        Ok(())
199    }
200
201    /// Build the internal Axum router (hidden from users)
202    async fn build_internal_router(self) -> HttpResult<axum::Router> {
203        let container = self.container.clone();
204        let config = self.config.clone();
205
206        // Create health check handler with captured context
207        let health_container = container.clone();
208        let health_config = config.clone();
209        let health_handler = move || {
210            let container = health_container.clone();
211            let config = health_config.clone();
212            async move {
213                health_check(container, config).await
214            }
215        };
216
217        // Start with framework router
218        let mut router = if let Some(user_router) = self.router {
219            user_router
220        } else {
221            ElifRouter::new()
222        };
223
224        // Add health check route
225        router = router.get(&config.health_check_path, health_handler);
226
227        // Convert to Axum router
228        Ok(router.into_axum_router())
229    }
230}
231
232/// Default health check handler
233pub async fn health_check(_container: Arc<Container>, _config: HttpConfig) -> axum::response::Json<serde_json::Value> {
234    use serde_json::json;
235
236    let response = json!({
237        "status": "healthy",
238        "framework": "Elif.rs",
239        "version": env!("CARGO_PKG_VERSION"),
240        "timestamp": std::time::SystemTime::now()
241            .duration_since(std::time::UNIX_EPOCH)
242            .unwrap()
243            .as_secs(),
244        "server": {
245            "ready": true,
246            "uptime": "N/A"
247        }
248    });
249
250    axum::response::Json(response)
251}
252
253/// Graceful shutdown signal handler
254async fn shutdown_signal() {
255    let ctrl_c = async {
256        signal::ctrl_c()
257            .await
258            .expect("Failed to install Ctrl+C handler");
259    };
260
261    #[cfg(unix)]
262    let terminate = async {
263        signal::unix::signal(signal::unix::SignalKind::terminate())
264            .expect("Failed to install signal handler")
265            .recv()
266            .await;
267    };
268
269    #[cfg(not(unix))]
270    let terminate = std::future::pending::<()>();
271
272    tokio::select! {
273        _ = ctrl_c => {
274            warn!("📡 Received Ctrl+C, shutting down gracefully...");
275        },
276        _ = terminate => {
277            warn!("📡 Received terminate signal, shutting down gracefully...");
278        },
279    }
280}
281
282#[cfg(test)]
283mod tests {
284    use super::*;
285    use elif_core::{
286        container::test_implementations::*,
287        app_config::AppConfigTrait,
288    };
289
290    fn create_test_container() -> Arc<Container> {
291        let config = Arc::new(create_test_config());
292        let database = Arc::new(TestDatabase::new()) as Arc<dyn elif_core::DatabaseConnection>;
293        
294        Container::builder()
295            .config(config)
296            .database(database)
297            .build()
298            .unwrap()
299            .into()
300    }
301
302    #[test]
303    fn test_server_creation() {
304        let container = create_test_container();
305        let config = HttpConfig::default();
306        
307        let server = Server::with_container(container, config);
308        assert!(server.is_ok());
309    }
310
311    #[test]
312    fn test_server_with_arc_container() {
313        let container = create_test_container();
314        let config = HttpConfig::default();
315        
316        let server = Server::with_container(container, config);
317        assert!(server.is_ok());
318    }
319
320    #[tokio::test]
321    async fn test_health_check_handler() {
322        let container = create_test_container();
323        let config = HttpConfig::default();
324        
325        let response = health_check(container, config).await;
326        // Test that response is properly formatted JSON
327        assert!(response.0.get("status").is_some());
328        assert_eq!(response.0["status"], "healthy");
329    }
330
331    #[test]
332    fn test_invalid_address() {
333        let container = create_test_container();
334        let config = HttpConfig::default();
335        let server = Server::with_container(container, config).unwrap();
336        
337        // This should be tested with an actual tokio runtime in integration tests
338        // For now, we just verify the server can be created
339    }
340}