sqlite_graphrag/commands/
namespace_detect.rs1use crate::errors::AppError;
4use crate::namespace;
5use crate::output;
6use serde::Serialize;
7
8#[derive(clap::Args)]
9#[command(after_long_help = "EXAMPLES:\n \
10 # Resolve namespace using current environment and cwd\n \
11 sqlite-graphrag namespace-detect\n\n \
12 # Override with an explicit namespace flag\n \
13 sqlite-graphrag namespace-detect --namespace my-project\n\n \
14 # Resolve via SQLITE_GRAPHRAG_NAMESPACE env var\n \
15 SQLITE_GRAPHRAG_NAMESPACE=ci-runner sqlite-graphrag namespace-detect")]
16pub struct NamespaceDetectArgs {
17 #[arg(long)]
18 pub namespace: Option<String>,
19 #[arg(long, env = "SQLITE_GRAPHRAG_DB_PATH")]
21 pub db: Option<String>,
22 #[arg(long, default_value_t = false)]
24 pub json: bool,
25}
26
27#[derive(Serialize)]
28struct NamespaceDetectResponse {
29 namespace: String,
30 source: namespace::NamespaceSource,
31 cwd: String,
32 elapsed_ms: u64,
34}
35
36pub fn run(args: NamespaceDetectArgs) -> Result<(), AppError> {
37 let inicio = std::time::Instant::now();
38 let _ = args.db;
39 let _ = args.json; let resolution = namespace::detect_namespace(args.namespace.as_deref())?;
41 output::emit_json(&NamespaceDetectResponse {
42 namespace: resolution.namespace,
43 source: resolution.source,
44 cwd: resolution.cwd,
45 elapsed_ms: inicio.elapsed().as_millis() as u64,
46 })?;
47 Ok(())
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53 use crate::namespace::NamespaceSource;
54 use clap::Parser;
55 use serial_test::serial;
56
57 #[test]
58 #[serial]
59 fn namespace_detect_default_returns_global_via_detect() {
60 std::env::remove_var("SQLITE_GRAPHRAG_NAMESPACE");
62 let resolution = namespace::detect_namespace(None).unwrap();
63 assert_eq!(resolution.namespace, "global");
64 assert_eq!(resolution.source, NamespaceSource::Default);
65 }
66
67 #[test]
68 #[serial]
69 fn namespace_detect_explicit_flag_overrides_env() {
70 std::env::set_var("SQLITE_GRAPHRAG_NAMESPACE", "env-namespace");
71 let resolution = namespace::detect_namespace(Some("flag-namespace")).unwrap();
72 assert_eq!(resolution.namespace, "flag-namespace");
73 assert_eq!(resolution.source, NamespaceSource::ExplicitFlag);
74 std::env::remove_var("SQLITE_GRAPHRAG_NAMESPACE");
75 }
76
77 #[test]
78 #[serial]
79 fn namespace_detect_env_var_used_when_no_flag() {
80 std::env::remove_var("SQLITE_GRAPHRAG_NAMESPACE");
81 std::env::set_var("SQLITE_GRAPHRAG_NAMESPACE", "namespace-de-env");
82 let resolution = namespace::detect_namespace(None).unwrap();
83 assert_eq!(resolution.namespace, "namespace-de-env");
84 assert_eq!(resolution.source, NamespaceSource::Environment);
85 std::env::remove_var("SQLITE_GRAPHRAG_NAMESPACE");
86 }
87
88 #[test]
89 fn namespace_detect_response_serializes_all_fields() {
90 let resp = NamespaceDetectResponse {
91 namespace: "meu-projeto".to_string(),
92 source: NamespaceSource::ExplicitFlag,
93 cwd: "/home/usuario/projeto".to_string(),
94 elapsed_ms: 3,
95 };
96 let json = serde_json::to_value(&resp).unwrap();
97 assert_eq!(json["namespace"], "meu-projeto");
98 assert_eq!(json["source"], "explicit_flag");
99 assert!(json["cwd"].is_string());
100 assert_eq!(json["elapsed_ms"], 3);
101 }
102
103 #[test]
104 fn namespace_source_serializes_in_snake_case() {
105 let casos = vec![
106 (NamespaceSource::ExplicitFlag, "explicit_flag"),
107 (NamespaceSource::Environment, "environment"),
108 (NamespaceSource::Default, "default"),
109 ];
110 for (source, esperado) in casos {
111 let json = serde_json::to_value(source).unwrap();
112 assert_eq!(
113 json, esperado,
114 "NamespaceSource::{source:?} deve serializar como \"{esperado}\""
115 );
116 }
117 }
118
119 #[test]
120 fn namespace_detect_accepts_db_as_noop() {
121 let cli = crate::cli::Cli::try_parse_from([
122 "sqlite-graphrag",
123 "namespace-detect",
124 "--db",
125 "/tmp/graphrag.sqlite",
126 ])
127 .expect("namespace-detect deve aceitar --db como no-op");
128
129 match cli.command {
130 crate::cli::Commands::NamespaceDetect(args) => {
131 assert_eq!(args.db.as_deref(), Some("/tmp/graphrag.sqlite"));
132 }
133 _ => unreachable!("comando incorreto parseado"),
134 }
135 }
136}