Skip to main content

sqlite_graphrag/i18n/validation/
messages_config.rs

1//! Messages about the XDG CONFIG file and stored credentials (GAP-SG-146).
2//!
3//! Parsing, ownership and symlink hardening of `config.toml`, plus the key
4//! registry's verdicts on unknown and retired settings.
5
6use crate::i18n::{current, Language};
7
8/// Config file is a symlink (potential attack).
9pub fn config_file_is_symlink(path: &str) -> String {
10    match current() {
11        Language::English => format!("config file is a symlink (potential attack): {path}"),
12        Language::Portuguese => {
13            format!("arquivo de config é um symlink (potencial ataque): {path}")
14        }
15    }
16}
17
18/// Config file owned by a different uid; refuse overwrite.
19pub fn config_file_wrong_owner(path: &str, file_uid: u32, my_uid: u32) -> String {
20    match current() {
21        Language::English => format!(
22            "config file {path} owned by uid {file_uid}, not current uid {my_uid}; refusing to overwrite"
23        ),
24        Language::Portuguese => format!(
25            "arquivo de config {path} pertence ao uid {file_uid}, não ao uid atual {my_uid}; recusando sobrescrever"
26        ),
27    }
28}
29
30/// Rejects a `config set` key that was advertised historically but never read.
31///
32/// Distinct from [`config_key_unknown`] because the operator followed the
33/// old documentation rather than mistyping, so the message names the
34/// replacement directly instead of guessing.
35pub fn config_key_retired(key: &str, replacement: &str) -> String {
36    match current() {
37        Language::English => format!(
38            "config key '{key}' was never read by this binary; \
39             use '{replacement}' instead"
40        ),
41        Language::Portuguese => format!(
42            "a chave de config '{key}' nunca foi lida por este binário; \
43             use '{replacement}' no lugar"
44        ),
45    }
46}
47
48/// Rejects a `config set` key that is not in the canonical registry.
49///
50/// `suggestion` carries the nearest known key when one is similar enough,
51/// so a typo is actionable without the operator listing every key.
52pub fn config_key_unknown(key: &str, suggestion: Option<&str>) -> String {
53    match (current(), suggestion) {
54        (Language::English, Some(s)) => format!(
55            "unknown config key '{key}'; did you mean '{s}'? \
56             list valid keys with `config doctor --json`"
57        ),
58        (Language::English, None) => format!(
59            "unknown config key '{key}'; \
60             list valid keys with `config doctor --json`"
61        ),
62        (Language::Portuguese, Some(s)) => format!(
63            "chave de config desconhecida '{key}'; você quis dizer '{s}'? \
64             liste as chaves válidas com `config doctor --json`"
65        ),
66        (Language::Portuguese, None) => format!(
67            "chave de config desconhecida '{key}'; \
68             liste as chaves válidas com `config doctor --json`"
69        ),
70    }
71}
72
73/// Localized description of the domain a [`crate::config::ValueKind`] accepts.
74///
75/// `Text` and `OneOf` never reach here: the first has no domain to describe,
76/// and the second is a list of literal spellings that must not be translated.
77pub fn config_value_expectation(kind: crate::config::ValueKind) -> String {
78    use crate::config::ValueKind as K;
79    match (current(), kind) {
80        (Language::English, K::Unsigned) => "a non-negative integer".to_string(),
81        (Language::English, K::Float) => "a decimal number".to_string(),
82        (Language::English, K::Tz) => "an IANA timezone (e.g. America/Sao_Paulo)".to_string(),
83        (Language::English, K::Url) => "an http:// or https:// URL".to_string(),
84        (Language::English, K::Path) => "a non-empty filesystem path".to_string(),
85        (Language::English, K::LogDirective) => {
86            "a tracing directive (e.g. warn, or sqlite_graphrag=debug)".to_string()
87        }
88        (Language::Portuguese, K::Unsigned) => "um inteiro não negativo".to_string(),
89        (Language::Portuguese, K::Float) => "um número decimal".to_string(),
90        (Language::Portuguese, K::Tz) => {
91            "um fuso horário IANA (ex.: America/Sao_Paulo)".to_string()
92        }
93        (Language::Portuguese, K::Url) => "uma URL http:// ou https://".to_string(),
94        (Language::Portuguese, K::Path) => {
95            "um caminho de sistema de arquivos não vazio".to_string()
96        }
97        (Language::Portuguese, K::LogDirective) => {
98            "uma diretiva de tracing (ex.: warn, ou sqlite_graphrag=debug)".to_string()
99        }
100        // Unreachable by construction: `ValueKind::expectation` handles both
101        // before delegating. Answering with the literal spellings keeps this
102        // total without a panic that a future variant could trip.
103        (_, K::Bool) => "true|false (also 1|0, yes|no, on|off)".to_string(),
104        (_, K::Text) => String::new(),
105        (_, K::OneOf(options)) => options.join("|"),
106    }
107}
108
109/// Config value outside the domain the key accepts.
110///
111/// GAP-SG-201: naming the expectation is the whole point. `invalid value` alone
112/// sends the operator to the documentation; `expected true|false` lets them fix
113/// the command they just typed.
114pub fn config_value_invalid(key: &str, value: &str, expectation: &str) -> String {
115    match current() {
116        Language::English => {
117            format!("invalid value '{value}' for config key '{key}'; expected {expectation}")
118        }
119        Language::Portuguese => format!(
120            "valor inválido '{value}' para a chave de config '{key}'; esperado {expectation}"
121        ),
122    }
123}
124
125/// Config parse error at path.
126pub fn config_parse_error(path: &str, err: &impl std::fmt::Display) -> String {
127    match current() {
128        Language::English => format!("config parse error in {path}: {err}"),
129        Language::Portuguese => format!("erro de parse de config em {path}: {err}"),
130    }
131}
132
133/// Config path has no parent directory component.
134pub fn config_path_no_parent(path: &str) -> String {
135    match current() {
136        Language::English => format!("config path has no parent: {path}"),
137        Language::Portuguese => format!("caminho de config sem diretório pai: {path}"),
138    }
139}
140
141/// API key empty after read.
142pub fn api_key_cannot_be_empty() -> String {
143    match current() {
144        Language::English => "API key cannot be empty".to_string(),
145        Language::Portuguese => "chave de API não pode ser vazia".to_string(),
146    }
147}
148
149/// Localized message for `invalid_namespace_config`.
150pub fn invalid_namespace_config(path: &str, err: &str) -> String {
151    match current() {
152        Language::English => {
153            format!("invalid project namespace config '{path}': {err}")
154        }
155        Language::Portuguese => {
156            format!("configuração de namespace de projeto inválida '{path}': {err}")
157        }
158    }
159}
160
161/// Localized message for `invalid_projects_mapping`.
162pub fn invalid_projects_mapping(path: &str, err: &str) -> String {
163    match current() {
164        Language::English => format!("invalid projects mapping '{path}': {err}"),
165        Language::Portuguese => format!("mapeamento de projetos inválido '{path}': {err}"),
166    }
167}