1use std::collections::BTreeMap;
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6#[derive(
8 Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
9)]
10pub struct ConfigKey(pub String);
11
12impl ConfigKey {
13 pub fn new(raw: &str) -> Result<Self, String> {
15 let trimmed = raw.trim();
16 if trimmed.is_empty() {
17 return Err("config key cannot be empty".to_string());
18 }
19 if !trimmed.is_ascii() {
20 return Err("config key must be ASCII".to_string());
21 }
22 if trimmed.contains('.') {
23 return Err("config key cannot contain section separator '.'".to_string());
24 }
25
26 let normalized = trimmed
27 .strip_prefix("BIJUXCLI_")
28 .or_else(|| trimmed.strip_prefix("BIJUX_"))
29 .unwrap_or(trimmed)
30 .to_ascii_lowercase();
31 if !normalized.chars().all(|ch| ch.is_ascii_alphanumeric() || ch == '_') {
32 return Err("config key must contain only alphanumerics and '_'".to_string());
33 }
34 Ok(Self(normalized))
35 }
36
37 #[must_use]
39 pub fn as_str(&self) -> &str {
40 &self.0
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
46pub struct ConfigValue(pub String);
47
48impl ConfigValue {
49 pub fn new(raw: &str) -> Result<Self, String> {
51 if !raw.is_ascii() {
52 return Err("config value must be ASCII".to_string());
53 }
54 if raw.chars().any(|ch| matches!(ch, '\r' | '\n' | '\t' | '\u{000B}' | '\u{000C}')) {
55 return Err("config value cannot contain control characters".to_string());
56 }
57 Ok(Self(raw.to_string()))
58 }
59
60 #[must_use]
62 pub fn as_str(&self) -> &str {
63 &self.0
64 }
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
69pub struct ConfigEntry {
70 pub key: ConfigKey,
72 pub value: ConfigValue,
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
78pub struct ConfigSnapshot {
79 pub entries: BTreeMap<ConfigKey, ConfigValue>,
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
85#[serde(tag = "kind", rename_all = "kebab-case")]
86pub enum ConfigMutation {
87 Set {
89 key: ConfigKey,
91 value: ConfigValue,
93 },
94 Unset {
96 key: ConfigKey,
98 },
99 Clear,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
105#[serde(rename_all = "lowercase")]
106pub enum ConfigSource {
107 Flags,
109 Env,
111 File,
113 Defaults,
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
119pub struct ResolvedConfigValue {
120 pub key: ConfigKey,
122 pub value: ConfigValue,
124 pub source: ConfigSource,
126 pub source_path: Option<String>,
128}
129
130#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
132pub struct ConfigPathSet {
133 pub config_file: String,
135 pub history_file: String,
137 pub plugins_dir: String,
139}
140
141#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
143pub struct ConfigLoadResult {
144 pub snapshot: ConfigSnapshot,
146 pub paths: ConfigPathSet,
148}
149
150#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
152pub struct ConfigWriteResult {
153 pub updated: bool,
155 pub entry_count: usize,
157 pub target_path: String,
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
163#[serde(rename_all = "snake_case")]
164pub enum ConfigSchemaValueKindV1 {
165 String,
167 Integer,
169 Boolean,
171 Path,
173 Json,
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
179#[serde(rename_all = "snake_case")]
180pub enum ConfigSchemaSourceV1 {
181 BuiltIn,
183 BuiltInShared,
185}
186
187#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
189#[serde(rename_all = "snake_case")]
190pub enum ConfigDeprecationStatusV1 {
191 Active,
193 Deprecated,
195}
196
197#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
199pub struct ConfigSchemaFieldV1 {
200 pub scope: String,
202 pub logical_key: String,
204 pub storage_key: String,
206 pub env_vars: Vec<String>,
208 pub value_kind: ConfigSchemaValueKindV1,
210 pub sensitive: bool,
212 pub default_value: Option<String>,
214 pub deprecation_status: ConfigDeprecationStatusV1,
216 pub description: String,
218}
219
220#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
222pub struct ConfigSchemaScopeV1 {
223 pub scope: String,
225 pub source: ConfigSchemaSourceV1,
227 pub fields: Vec<ConfigSchemaFieldV1>,
229}
230
231#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
233pub struct ConfigSchemaRegistryV1 {
234 pub schema_version: String,
236 pub scopes: Vec<ConfigSchemaScopeV1>,
238}
239
240#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
242#[serde(rename_all = "lowercase")]
243pub enum ConfigExportFormat {
244 Env,
246 Json,
248 Yaml,
250}
251
252#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
254pub struct ConfigCommandResult {
255 pub status: String,
257 pub command: String,
259}
260
261#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
263#[serde(rename_all = "kebab-case")]
264pub enum ConfigErrorKind {
265 Validation,
267 Parse,
269 Persistence,
271 Conflict,
273 NotFound,
275}
276
277#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
279pub struct ConfigValidationError {
280 pub key: Option<ConfigKey>,
282 pub message: String,
284}
285
286#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
288pub struct ConfigParseError {
289 pub line: usize,
291 pub content: String,
293 pub message: String,
295}
296
297#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
299pub struct ConfigPersistenceError {
300 pub path: String,
302 pub operation: String,
304 pub message: String,
306}
307
308#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
310pub struct ConfigConflictError {
311 pub key: Option<ConfigKey>,
313 pub message: String,
315}
316
317#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
319pub struct ConfigReloadResult {
320 pub status: String,
322 pub reloaded_path: String,
324}
325
326#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
328pub struct ConfigClearResult {
329 pub status: String,
331 pub removed_keys: usize,
333}