Skip to main content

sqlite_graphrag/commands/
namespace_detect.rs

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