ruvector_scipix/api/
mod.rs

1pub mod handlers;
2pub mod jobs;
3pub mod middleware;
4pub mod requests;
5pub mod responses;
6pub mod routes;
7pub mod state;
8
9use anyhow::Result;
10use axum::Router;
11use std::net::SocketAddr;
12use tokio::signal;
13use tracing::{info, warn};
14
15use self::state::AppState;
16
17/// Main API server structure
18pub struct ApiServer {
19    state: AppState,
20    addr: SocketAddr,
21}
22
23impl ApiServer {
24    /// Create a new API server instance
25    pub fn new(state: AppState, addr: SocketAddr) -> Self {
26        Self { state, addr }
27    }
28
29    /// Start the API server with graceful shutdown
30    pub async fn start(self) -> Result<()> {
31        let app = self.create_router();
32
33        info!("Starting Scipix API server on {}", self.addr);
34
35        let listener = tokio::net::TcpListener::bind(self.addr).await?;
36
37        axum::serve(listener, app)
38            .with_graceful_shutdown(shutdown_signal())
39            .await?;
40
41        info!("Server shutdown complete");
42        Ok(())
43    }
44
45    /// Create the application router with all routes and middleware
46    fn create_router(&self) -> Router {
47        routes::router(self.state.clone())
48    }
49}
50
51/// Graceful shutdown signal handler
52async fn shutdown_signal() {
53    let ctrl_c = async {
54        signal::ctrl_c()
55            .await
56            .expect("failed to install Ctrl+C handler");
57    };
58
59    #[cfg(unix)]
60    let terminate = async {
61        signal::unix::signal(signal::unix::SignalKind::terminate())
62            .expect("failed to install signal handler")
63            .recv()
64            .await;
65    };
66
67    #[cfg(not(unix))]
68    let terminate = std::future::pending::<()>();
69
70    tokio::select! {
71        _ = ctrl_c => {
72            warn!("Received Ctrl+C, shutting down...");
73        },
74        _ = terminate => {
75            warn!("Received terminate signal, shutting down...");
76        },
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[tokio::test]
85    async fn test_server_creation() {
86        let state = AppState::new();
87        let addr = "127.0.0.1:3000".parse().unwrap();
88        let server = ApiServer::new(state, addr);
89        assert_eq!(server.addr, addr);
90    }
91}