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 # Explicit namespace flag\n \
15 sqlite-graphrag namespace-detect --namespace ci-runner")]
16pub struct NamespaceDetectArgs {
18 #[arg(long)]
20 pub namespace: Option<String>,
21 #[arg(long)]
23 pub db: Option<String>,
24 #[arg(long, default_value_t = false)]
26 pub json: bool,
27}
28
29#[derive(Serialize)]
30struct NamespaceDetectResponse {
31 namespace: String,
32 source: namespace::NamespaceSource,
33 cwd: String,
34 elapsed_ms: u64,
36}
37
38pub fn run(args: NamespaceDetectArgs) -> Result<(), AppError> {
40 let inicio = std::time::Instant::now();
41 let _ = args.db;
42 let _ = args.json; let resolution = namespace::detect_namespace(args.namespace.as_deref())?;
44 output::emit_json(&NamespaceDetectResponse {
45 namespace: resolution.namespace,
46 source: resolution.source,
47 cwd: resolution.cwd,
48 elapsed_ms: inicio.elapsed().as_millis() as u64,
49 })?;
50 Ok(())
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56 use crate::namespace::NamespaceSource;
57 use clap::Parser;
58 use serial_test::serial;
59
60 #[test]
61 #[serial]
62 fn namespace_detect_default_returns_global_via_detect() {
63 let resolution = namespace::detect_namespace(None).unwrap();
66 assert!(!resolution.namespace.is_empty());
68 if resolution.source == NamespaceSource::Default {
69 assert_eq!(resolution.namespace, "global");
70 }
71 }
72
73 #[test]
74 #[serial]
75 fn namespace_detect_explicit_flag_wins() {
76 let resolution = namespace::detect_namespace(Some("flag-namespace")).unwrap();
78 assert_eq!(resolution.namespace, "flag-namespace");
79 assert_eq!(resolution.source, NamespaceSource::ExplicitFlag);
80 }
81
82 #[test]
83 #[serial]
84 fn namespace_detect_default_when_no_flag() {
85 let resolution = namespace::detect_namespace(None).unwrap();
88 assert!(!resolution.namespace.is_empty());
89 assert!(
90 matches!(
91 resolution.source,
92 NamespaceSource::Default | NamespaceSource::XdgConfig
93 ),
94 "unexpected source {:?}",
95 resolution.source
96 );
97 }
98
99 #[test]
100 fn namespace_detect_response_serializes_all_fields() {
101 let resp = NamespaceDetectResponse {
102 namespace: "meu-projeto".to_string(),
103 source: NamespaceSource::ExplicitFlag,
104 cwd: "/home/usuario/projeto".to_string(),
105 elapsed_ms: 3,
106 };
107 let json = serde_json::to_value(&resp).unwrap();
108 assert_eq!(json["namespace"], "meu-projeto");
109 assert_eq!(json["source"], "explicit_flag");
110 assert!(json["cwd"].is_string());
111 assert_eq!(json["elapsed_ms"], 3);
112 }
113
114 #[test]
115 fn namespace_source_serializes_in_snake_case() {
116 let cases = vec![
117 (NamespaceSource::ExplicitFlag, "explicit_flag"),
118 (NamespaceSource::XdgConfig, "xdg_config"),
119 (NamespaceSource::Default, "default"),
120 ];
121 for (source, expected) in cases {
122 let json = serde_json::to_value(source).unwrap();
123 assert_eq!(
124 json, expected,
125 "NamespaceSource::{source:?} must serialize as \"{expected}\""
126 );
127 }
128 }
129
130 #[test]
131 fn namespace_detect_accepts_db_as_noop() {
132 let cli = crate::cli::Cli::try_parse_from([
133 "sqlite-graphrag",
134 "namespace-detect",
135 "--db",
136 "/tmp/graphrag.sqlite",
137 ])
138 .expect("namespace-detect must accept --db as a no-op");
139
140 match cli.command {
141 Some(crate::cli::Commands::NamespaceDetect(args)) => {
142 assert_eq!(args.db.as_deref(), Some("/tmp/graphrag.sqlite"));
143 }
144 _ => unreachable!("unexpected command parsed"),
145 }
146 }
147}