Skip to main content

sqlite_graphrag/
namespace.rs

1use crate::errors::AppError;
2use crate::i18n::validacao;
3use serde::Serialize;
4use std::path::Path;
5
6#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
7#[serde(rename_all = "snake_case")]
8pub enum NamespaceSource {
9    ExplicitFlag,
10    Environment,
11    Default,
12}
13
14#[derive(Debug, Clone, Serialize)]
15pub struct NamespaceResolution {
16    pub namespace: String,
17    pub source: NamespaceSource,
18    pub cwd: String,
19}
20
21/// Resolve o namespace ativo retornando apenas o nome final.
22///
23/// Atalho sobre [`detect_namespace`] quando a origem não importa.
24/// Com flag explícita válida, o namespace retornado é exatamente o valor passado.
25/// Sem flag, o fallback final é `"global"`.
26///
27/// # Errors
28///
29/// Retorna [`AppError::Validation`] se `explicit` contiver caracteres inválidos
30/// ou ultrapassar 80 caracteres.
31///
32/// # Examples
33///
34/// ```
35/// use sqlite_graphrag::namespace::resolve_namespace;
36///
37/// // Flag explícita válida é aceita e refletida no resultado.
38/// let ns = resolve_namespace(Some("meu-projeto")).unwrap();
39/// assert_eq!(ns, "meu-projeto");
40/// ```
41///
42/// ```
43/// use sqlite_graphrag::namespace::resolve_namespace;
44/// use sqlite_graphrag::errors::AppError;
45///
46/// // Namespace com caracteres inválidos causa erro de validação (exit 1).
47/// let err = resolve_namespace(Some("ns com espaço")).unwrap_err();
48/// assert_eq!(err.exit_code(), 1);
49/// ```
50pub fn resolve_namespace(explicit: Option<&str>) -> Result<String, AppError> {
51    Ok(detect_namespace(explicit)?.namespace)
52}
53
54/// Resolve o namespace ativo retornando estrutura com origem e diretório atual.
55///
56/// A precedência é: flag explícita > `SQLITE_GRAPHRAG_NAMESPACE` > fallback `"global"`.
57///
58/// # Errors
59///
60/// Retorna [`AppError::Validation`] se o namespace resolvido contiver caracteres inválidos.
61///
62/// # Examples
63///
64/// ```
65/// use sqlite_graphrag::namespace::{detect_namespace, NamespaceSource};
66///
67/// // Com flag explícita, a fonte é `ExplicitFlag`.
68/// let res = detect_namespace(Some("producao")).unwrap();
69/// assert_eq!(res.namespace, "producao");
70/// assert_eq!(res.source, NamespaceSource::ExplicitFlag);
71/// ```
72///
73/// ```
74/// use sqlite_graphrag::namespace::{detect_namespace, NamespaceSource};
75///
76/// // Sem nenhuma configuração explícita, fallback é "global".
77/// // Desativa env var para garantir comportamento determinístico.
78/// std::env::remove_var("SQLITE_GRAPHRAG_NAMESPACE");
79/// let res = detect_namespace(None).unwrap();
80/// assert_eq!(res.namespace, "global");
81/// assert_eq!(res.source, NamespaceSource::Default);
82/// ```
83pub fn detect_namespace(explicit: Option<&str>) -> Result<NamespaceResolution, AppError> {
84    let cwd = std::env::current_dir().map_err(AppError::Io)?;
85    let cwd_display = normalize_path(&cwd);
86
87    if let Some(ns) = explicit {
88        validate_namespace(ns)?;
89        return Ok(NamespaceResolution {
90            namespace: ns.to_owned(),
91            source: NamespaceSource::ExplicitFlag,
92            cwd: cwd_display,
93        });
94    }
95
96    if let Ok(ns) = std::env::var("SQLITE_GRAPHRAG_NAMESPACE") {
97        if !ns.is_empty() {
98            validate_namespace(&ns)?;
99            return Ok(NamespaceResolution {
100                namespace: ns,
101                source: NamespaceSource::Environment,
102                cwd: cwd_display,
103            });
104        }
105    }
106
107    Ok(NamespaceResolution {
108        namespace: "global".to_owned(),
109        source: NamespaceSource::Default,
110        cwd: cwd_display,
111    })
112}
113
114fn validate_namespace(ns: &str) -> Result<(), AppError> {
115    if ns.is_empty() || ns.len() > 80 {
116        return Err(AppError::Validation(validacao::namespace_comprimento()));
117    }
118    if !ns
119        .chars()
120        .all(|c| c.is_alphanumeric() || c == '-' || c == '_')
121    {
122        return Err(AppError::Validation(validacao::namespace_formato()));
123    }
124    Ok(())
125}
126
127fn normalize_path(path: &Path) -> String {
128    path.canonicalize()
129        .unwrap_or_else(|_| path.to_path_buf())
130        .display()
131        .to_string()
132}