use crate::{
HttpConfig, HttpError, HttpResult,
ElifRouter,
MiddlewarePipeline, Middleware,
};
use elif_core::Container;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::signal;
use tracing::{info, warn};
pub struct Server {
container: Arc<Container>,
config: HttpConfig,
router: Option<ElifRouter>,
middleware: MiddlewarePipeline,
}
impl Server {
pub fn new(container: Container, config: HttpConfig) -> HttpResult<Self> {
Ok(Self {
container: Arc::new(container),
config,
router: None,
middleware: MiddlewarePipeline::new(),
})
}
pub fn with_container(container: Arc<Container>, config: HttpConfig) -> HttpResult<Self> {
Ok(Self {
container,
config,
router: None,
middleware: MiddlewarePipeline::new(),
})
}
pub fn use_router(&mut self, router: ElifRouter) -> &mut Self {
self.router = Some(router);
self
}
pub fn use_middleware<M>(&mut self, middleware: M) -> &mut Self
where
M: Middleware + 'static,
{
self.middleware = std::mem::take(&mut self.middleware).add(middleware);
self
}
pub async fn listen<A: Into<String>>(self, addr: A) -> HttpResult<()> {
let addr_str = addr.into();
let socket_addr: SocketAddr = addr_str.parse()
.map_err(|e| HttpError::config(format!("Invalid address '{}': {}", addr_str, e)))?;
self.listen_on(socket_addr).await
}
pub async fn listen_on(self, addr: SocketAddr) -> HttpResult<()> {
info!("🚀 Starting Elif server on {}", addr);
info!("📋 Health check endpoint: {}", self.config.health_check_path);
let axum_router = self.build_internal_router().await?;
let listener = tokio::net::TcpListener::bind(addr)
.await
.map_err(|e| HttpError::startup(format!("Failed to bind to {}: {}", addr, e)))?;
info!("✅ Server listening on {}", addr);
info!("🔧 Framework: Elif.rs (Axum under the hood)");
axum::serve(listener, axum_router.into_make_service_with_connect_info::<SocketAddr>())
.with_graceful_shutdown(shutdown_signal())
.await
.map_err(|e| HttpError::internal(format!("Server error: {}", e)))?;
info!("🛑 Server shut down gracefully");
Ok(())
}
async fn build_internal_router(self) -> HttpResult<axum::Router> {
let container = self.container.clone();
let config = self.config.clone();
let health_container = container.clone();
let health_config = config.clone();
let health_handler = move || {
let container = health_container.clone();
let config = health_config.clone();
async move {
health_check(container, config).await
}
};
let mut router = if let Some(user_router) = self.router {
user_router
} else {
ElifRouter::new()
};
router = router.get(&config.health_check_path, health_handler);
Ok(router.into_axum_router())
}
}
pub async fn health_check(_container: Arc<Container>, _config: HttpConfig) -> axum::response::Json<serde_json::Value> {
use serde_json::json;
let response = json!({
"status": "healthy",
"framework": "Elif.rs",
"version": env!("CARGO_PKG_VERSION"),
"timestamp": std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs(),
"server": {
"ready": true,
"uptime": "N/A"
}
});
axum::response::Json(response)
}
async fn shutdown_signal() {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("Failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("Failed to install signal handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {
warn!("📡 Received Ctrl+C, shutting down gracefully...");
},
_ = terminate => {
warn!("📡 Received terminate signal, shutting down gracefully...");
},
}
}
#[cfg(test)]
mod tests {
use super::*;
use elif_core::{
container::test_implementations::*,
app_config::AppConfigTrait,
};
fn create_test_container() -> Arc<Container> {
let config = Arc::new(create_test_config());
let database = Arc::new(TestDatabase::new()) as Arc<dyn elif_core::DatabaseConnection>;
Container::builder()
.config(config)
.database(database)
.build()
.unwrap()
.into()
}
#[test]
fn test_server_creation() {
let container = create_test_container();
let config = HttpConfig::default();
let server = Server::with_container(container, config);
assert!(server.is_ok());
}
#[test]
fn test_server_with_arc_container() {
let container = create_test_container();
let config = HttpConfig::default();
let server = Server::with_container(container, config);
assert!(server.is_ok());
}
#[tokio::test]
async fn test_health_check_handler() {
let container = create_test_container();
let config = HttpConfig::default();
let response = health_check(container, config).await;
assert!(response.0.get("status").is_some());
assert_eq!(response.0["status"], "healthy");
}
#[test]
fn test_invalid_address() {
let container = create_test_container();
let config = HttpConfig::default();
let server = Server::with_container(container, config).unwrap();
}
}