Skip to main content

sqlite_graphrag/commands/
namespace_detect.rs

1use crate::errors::AppError;
2use crate::namespace;
3use crate::output;
4use serde::Serialize;
5
6#[derive(clap::Args)]
7pub struct NamespaceDetectArgs {
8    #[arg(long)]
9    pub namespace: Option<String>,
10    /// Caminho explícito do banco. Aceito como no-op para manter o contrato global.
11    #[arg(long, env = "SQLITE_GRAPHRAG_DB_PATH")]
12    pub db: Option<String>,
13    /// Flag explícita de saída JSON. Aceita como no-op pois o output já é JSON por default.
14    #[arg(long, default_value_t = false)]
15    pub json: bool,
16}
17
18#[derive(Serialize)]
19struct NamespaceDetectResponse {
20    namespace: String,
21    source: namespace::NamespaceSource,
22    cwd: String,
23    /// Tempo total de execução em milissegundos desde início do handler até serialização.
24    elapsed_ms: u64,
25}
26
27pub fn run(args: NamespaceDetectArgs) -> Result<(), AppError> {
28    let inicio = std::time::Instant::now();
29    let _ = args.db;
30    let _ = args.json; // --json é no-op pois output já é JSON por default
31    let resolution = namespace::detect_namespace(args.namespace.as_deref())?;
32    output::emit_json(&NamespaceDetectResponse {
33        namespace: resolution.namespace,
34        source: resolution.source,
35        cwd: resolution.cwd,
36        elapsed_ms: inicio.elapsed().as_millis() as u64,
37    })?;
38    Ok(())
39}
40
41#[cfg(test)]
42mod testes {
43    use super::*;
44    use crate::namespace::NamespaceSource;
45    use clap::Parser;
46    use serial_test::serial;
47
48    #[test]
49    #[serial]
50    fn namespace_detect_default_retorna_global_via_detect() {
51        // Garante que sem flag e sem env, detect_namespace retorna "global"
52        std::env::remove_var("SQLITE_GRAPHRAG_NAMESPACE");
53        let resolution = namespace::detect_namespace(None).unwrap();
54        assert_eq!(resolution.namespace, "global");
55        assert_eq!(resolution.source, NamespaceSource::Default);
56    }
57
58    #[test]
59    #[serial]
60    fn namespace_detect_explicit_flag_sobrepoe_env() {
61        std::env::set_var("SQLITE_GRAPHRAG_NAMESPACE", "env-namespace");
62        let resolution = namespace::detect_namespace(Some("flag-namespace")).unwrap();
63        assert_eq!(resolution.namespace, "flag-namespace");
64        assert_eq!(resolution.source, NamespaceSource::ExplicitFlag);
65        std::env::remove_var("SQLITE_GRAPHRAG_NAMESPACE");
66    }
67
68    #[test]
69    #[serial]
70    fn namespace_detect_env_var_usada_quando_sem_flag() {
71        std::env::remove_var("SQLITE_GRAPHRAG_NAMESPACE");
72        std::env::set_var("SQLITE_GRAPHRAG_NAMESPACE", "namespace-de-env");
73        let resolution = namespace::detect_namespace(None).unwrap();
74        assert_eq!(resolution.namespace, "namespace-de-env");
75        assert_eq!(resolution.source, NamespaceSource::Environment);
76        std::env::remove_var("SQLITE_GRAPHRAG_NAMESPACE");
77    }
78
79    #[test]
80    fn namespace_detect_response_serializa_todos_campos() {
81        let resp = NamespaceDetectResponse {
82            namespace: "meu-projeto".to_string(),
83            source: NamespaceSource::ExplicitFlag,
84            cwd: "/home/usuario/projeto".to_string(),
85            elapsed_ms: 3,
86        };
87        let json = serde_json::to_value(&resp).unwrap();
88        assert_eq!(json["namespace"], "meu-projeto");
89        assert_eq!(json["source"], "explicit_flag");
90        assert!(json["cwd"].is_string());
91        assert_eq!(json["elapsed_ms"], 3);
92    }
93
94    #[test]
95    fn namespace_source_serializa_em_snake_case() {
96        let casos = vec![
97            (NamespaceSource::ExplicitFlag, "explicit_flag"),
98            (NamespaceSource::Environment, "environment"),
99            (NamespaceSource::Default, "default"),
100        ];
101        for (source, esperado) in casos {
102            let json = serde_json::to_value(source).unwrap();
103            assert_eq!(
104                json, esperado,
105                "NamespaceSource::{source:?} deve serializar como \"{esperado}\""
106            );
107        }
108    }
109
110    #[test]
111    fn namespace_detect_aceita_db_como_noop() {
112        let cli = crate::cli::Cli::try_parse_from([
113            "sqlite-graphrag",
114            "namespace-detect",
115            "--db",
116            "/tmp/graphrag.sqlite",
117        ])
118        .expect("namespace-detect deve aceitar --db como no-op");
119
120        match cli.command {
121            crate::cli::Commands::NamespaceDetect(args) => {
122                assert_eq!(args.db.as_deref(), Some("/tmp/graphrag.sqlite"));
123            }
124            _ => panic!("comando incorreto parseado"),
125        }
126    }
127}