sqlite-graphrag 1.0.9

Local GraphRAG memory for LLMs in a single SQLite file
Documentation
use crate::errors::AppError;
use crate::namespace;
use crate::output;
use serde::Serialize;

#[derive(clap::Args)]
pub struct NamespaceDetectArgs {
    #[arg(long)]
    pub namespace: Option<String>,
    /// Caminho explícito do banco. Aceito como no-op para manter o contrato global.
    #[arg(long, env = "SQLITE_GRAPHRAG_DB_PATH")]
    pub db: Option<String>,
    /// Flag explícita de saída JSON. Aceita como no-op pois o output já é JSON por default.
    #[arg(long, default_value_t = false)]
    pub json: bool,
}

#[derive(Serialize)]
struct NamespaceDetectResponse {
    namespace: String,
    source: namespace::NamespaceSource,
    cwd: String,
    /// Tempo total de execução em milissegundos desde início do handler até serialização.
    elapsed_ms: u64,
}

pub fn run(args: NamespaceDetectArgs) -> Result<(), AppError> {
    let inicio = std::time::Instant::now();
    let _ = args.db;
    let _ = args.json; // --json é no-op pois output já é JSON por default
    let resolution = namespace::detect_namespace(args.namespace.as_deref())?;
    output::emit_json(&NamespaceDetectResponse {
        namespace: resolution.namespace,
        source: resolution.source,
        cwd: resolution.cwd,
        elapsed_ms: inicio.elapsed().as_millis() as u64,
    })?;
    Ok(())
}

#[cfg(test)]
mod testes {
    use super::*;
    use crate::namespace::NamespaceSource;
    use clap::Parser;
    use serial_test::serial;

    #[test]
    #[serial]
    fn namespace_detect_default_retorna_global_via_detect() {
        // Garante que sem flag e sem env, detect_namespace retorna "global"
        std::env::remove_var("SQLITE_GRAPHRAG_NAMESPACE");
        let resolution = namespace::detect_namespace(None).unwrap();
        assert_eq!(resolution.namespace, "global");
        assert_eq!(resolution.source, NamespaceSource::Default);
    }

    #[test]
    #[serial]
    fn namespace_detect_explicit_flag_sobrepoe_env() {
        std::env::set_var("SQLITE_GRAPHRAG_NAMESPACE", "env-namespace");
        let resolution = namespace::detect_namespace(Some("flag-namespace")).unwrap();
        assert_eq!(resolution.namespace, "flag-namespace");
        assert_eq!(resolution.source, NamespaceSource::ExplicitFlag);
        std::env::remove_var("SQLITE_GRAPHRAG_NAMESPACE");
    }

    #[test]
    #[serial]
    fn namespace_detect_env_var_usada_quando_sem_flag() {
        std::env::remove_var("SQLITE_GRAPHRAG_NAMESPACE");
        std::env::set_var("SQLITE_GRAPHRAG_NAMESPACE", "namespace-de-env");
        let resolution = namespace::detect_namespace(None).unwrap();
        assert_eq!(resolution.namespace, "namespace-de-env");
        assert_eq!(resolution.source, NamespaceSource::Environment);
        std::env::remove_var("SQLITE_GRAPHRAG_NAMESPACE");
    }

    #[test]
    fn namespace_detect_response_serializa_todos_campos() {
        let resp = NamespaceDetectResponse {
            namespace: "meu-projeto".to_string(),
            source: NamespaceSource::ExplicitFlag,
            cwd: "/home/usuario/projeto".to_string(),
            elapsed_ms: 3,
        };
        let json = serde_json::to_value(&resp).unwrap();
        assert_eq!(json["namespace"], "meu-projeto");
        assert_eq!(json["source"], "explicit_flag");
        assert!(json["cwd"].is_string());
        assert_eq!(json["elapsed_ms"], 3);
    }

    #[test]
    fn namespace_source_serializa_em_snake_case() {
        let casos = vec![
            (NamespaceSource::ExplicitFlag, "explicit_flag"),
            (NamespaceSource::Environment, "environment"),
            (NamespaceSource::Default, "default"),
        ];
        for (source, esperado) in casos {
            let json = serde_json::to_value(source).unwrap();
            assert_eq!(
                json, esperado,
                "NamespaceSource::{source:?} deve serializar como \"{esperado}\""
            );
        }
    }

    #[test]
    fn namespace_detect_aceita_db_como_noop() {
        let cli = crate::cli::Cli::try_parse_from([
            "sqlite-graphrag",
            "namespace-detect",
            "--db",
            "/tmp/graphrag.sqlite",
        ])
        .expect("namespace-detect deve aceitar --db como no-op");

        match cli.command {
            crate::cli::Commands::NamespaceDetect(args) => {
                assert_eq!(args.db.as_deref(), Some("/tmp/graphrag.sqlite"));
            }
            _ => panic!("comando incorreto parseado"),
        }
    }
}