knot-server 0.1.5

Distributed REST API server for knot codebase indexing. Manages Git repositories across a cluster with shared workspace coordination.
use clap::Parser;

#[derive(Debug, Parser)]
#[command(
    name = "knot-server",
    version,
    about = "Distributed REST API server for knot codebase indexing"
)]
pub struct ServerConfig {
    #[arg(long, env = "KNOT_SERVER_PORT", default_value_t = 3000)]
    pub port: u16,

    #[arg(long, env = "KNOT_SERVER_BIND_ADDR", default_value = "0.0.0.0")]
    pub bind_addr: String,

    #[arg(
        long,
        env = "KNOT_SERVER_QDRANT_URL",
        default_value = "http://localhost:6334"
    )]
    pub qdrant_url: String,

    #[arg(
        long,
        env = "KNOT_SERVER_QDRANT_COLLECTION",
        default_value = "knot_entities"
    )]
    pub qdrant_collection: String,

    #[arg(
        long,
        env = "KNOT_SERVER_NEO4J_URI",
        default_value = "bolt://localhost:7687"
    )]
    pub neo4j_uri: String,

    #[arg(long, env = "KNOT_SERVER_NEO4J_USER", default_value = "neo4j")]
    pub neo4j_user: String,

    #[arg(long, env = "KNOT_NEO4J_PASSWORD")]
    pub neo4j_password: String,

    #[arg(
        long,
        env = "KNOT_WORKSPACE_DIR",
        default_value = "/var/lib/knot/repos"
    )]
    pub workspace_dir: String,

    #[arg(long, env = "KNOT_SERVER_EMBED_DIM", default_value_t = 384)]
    pub embed_dim: u64,

    #[arg(long, env = "KNOT_SERVER_RAYON_THREADS")]
    pub rayon_threads: Option<usize>,

    #[arg(long, env = "KNOT_SERVER_BATCH_SIZE", default_value_t = 64)]
    pub batch_size: usize,

    #[arg(long, env = "KNOT_SERVER_INGEST_CONCURRENCY", default_value_t = 4)]
    pub ingest_concurrency: usize,

    #[arg(long, env = "KNOT_SERVER_POLL_INTERVAL_SECS", default_value_t = 86400)]
    pub poll_interval_secs: u64,

    #[arg(
        long,
        env = "KNOT_SERVER_STALE_LOCK_TIMEOUT_SECS",
        default_value_t = 3600
    )]
    pub stale_lock_timeout_secs: u64,

    #[arg(long, env = "KNOT_SERVER_MAX_INDEX_AGE_SECS", default_value_t = 86400)]
    pub max_index_age_secs: u64,

    #[arg(long, env = "KNOT_SERVER_QUEUE_CAPACITY", default_value_t = 16)]
    pub queue_capacity: usize,
}

impl ServerConfig {
    pub fn from_env() -> Self {
        Self::parse()
    }
}

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

    #[test]
    fn test_default_port() {
        let args = vec!["knot-server", "--neo4j-password", "secret"];
        let cfg = ServerConfig::try_parse_from(args).expect("Failed to parse");
        assert_eq!(cfg.port, 3000);
    }

    #[test]
    fn test_custom_port() {
        let args = vec![
            "knot-server",
            "--neo4j-password",
            "secret",
            "--port",
            "8080",
        ];
        let cfg = ServerConfig::try_parse_from(args).expect("Failed to parse");
        assert_eq!(cfg.port, 8080);
    }

    #[test]
    fn test_default_values() {
        let args = vec!["knot-server", "--neo4j-password", "secret"];
        let cfg = ServerConfig::try_parse_from(args).expect("Failed to parse");
        assert_eq!(cfg.bind_addr, "0.0.0.0");
        assert_eq!(cfg.qdrant_url, "http://localhost:6334");
        assert_eq!(cfg.qdrant_collection, "knot_entities");
        assert_eq!(cfg.neo4j_uri, "bolt://localhost:7687");
        assert_eq!(cfg.neo4j_user, "neo4j");
        assert_eq!(cfg.embed_dim, 384);
    }

    #[test]
    fn test_custom_workspace_dir() {
        let args = vec![
            "knot-server",
            "--neo4j-password",
            "secret",
            "--workspace-dir",
            "/custom/path",
        ];
        let cfg = ServerConfig::try_parse_from(args).expect("Failed to parse");
        assert_eq!(cfg.workspace_dir, "/custom/path");
    }
}