ruvector-scipix 2.0.4

Rust OCR engine for scientific documents - extract LaTeX, MathML from math equations, research papers, and technical diagrams with ONNX GPU acceleration
Documentation
pub mod handlers;
pub mod jobs;
pub mod middleware;
pub mod requests;
pub mod responses;
pub mod routes;
pub mod state;

use anyhow::Result;
use axum::Router;
use std::net::SocketAddr;
use tokio::signal;
use tracing::{info, warn};

use self::state::AppState;

/// Main API server structure
pub struct ApiServer {
    state: AppState,
    addr: SocketAddr,
}

impl ApiServer {
    /// Create a new API server instance
    pub fn new(state: AppState, addr: SocketAddr) -> Self {
        Self { state, addr }
    }

    /// Start the API server with graceful shutdown
    pub async fn start(self) -> Result<()> {
        let app = self.create_router();

        info!("Starting Scipix API server on {}", self.addr);

        let listener = tokio::net::TcpListener::bind(self.addr).await?;

        axum::serve(listener, app)
            .with_graceful_shutdown(shutdown_signal())
            .await?;

        info!("Server shutdown complete");
        Ok(())
    }

    /// Create the application router with all routes and middleware
    fn create_router(&self) -> Router {
        routes::router(self.state.clone())
    }
}

/// Graceful shutdown signal handler
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...");
        },
        _ = terminate => {
            warn!("Received terminate signal, shutting down...");
        },
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_server_creation() {
        let state = AppState::new();
        let addr = "127.0.0.1:3000".parse().unwrap();
        let server = ApiServer::new(state, addr);
        assert_eq!(server.addr, addr);
    }
}