use std::collections::BTreeMap;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
)]
pub struct ConfigKey(pub String);
impl ConfigKey {
pub fn new(raw: &str) -> Result<Self, String> {
let trimmed = raw.trim();
if trimmed.is_empty() {
return Err("config key cannot be empty".to_string());
}
if !trimmed.is_ascii() {
return Err("config key must be ASCII".to_string());
}
if trimmed.contains('.') {
return Err("config key cannot contain section separator '.'".to_string());
}
let normalized = trimmed
.strip_prefix("BIJUXCLI_")
.or_else(|| trimmed.strip_prefix("BIJUX_"))
.unwrap_or(trimmed)
.to_ascii_lowercase();
if !normalized.chars().all(|ch| ch.is_ascii_alphanumeric() || ch == '_') {
return Err("config key must contain only alphanumerics and '_'".to_string());
}
Ok(Self(normalized))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ConfigValue(pub String);
impl ConfigValue {
pub fn new(raw: &str) -> Result<Self, String> {
if !raw.is_ascii() {
return Err("config value must be ASCII".to_string());
}
if raw.chars().any(|ch| matches!(ch, '\r' | '\n' | '\t' | '\u{000B}' | '\u{000C}')) {
return Err("config value cannot contain control characters".to_string());
}
Ok(Self(raw.to_string()))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ConfigEntry {
pub key: ConfigKey,
pub value: ConfigValue,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ConfigSnapshot {
pub entries: BTreeMap<ConfigKey, ConfigValue>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "kind", rename_all = "kebab-case")]
pub enum ConfigMutation {
Set {
key: ConfigKey,
value: ConfigValue,
},
Unset {
key: ConfigKey,
},
Clear,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "lowercase")]
pub enum ConfigSource {
Flags,
Env,
File,
Defaults,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ResolvedConfigValue {
pub key: ConfigKey,
pub value: ConfigValue,
pub source: ConfigSource,
pub source_path: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ConfigPathSet {
pub config_file: String,
pub history_file: String,
pub plugins_dir: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ConfigLoadResult {
pub snapshot: ConfigSnapshot,
pub paths: ConfigPathSet,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ConfigWriteResult {
pub updated: bool,
pub entry_count: usize,
pub target_path: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "lowercase")]
pub enum ConfigExportFormat {
Env,
Json,
Yaml,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ConfigCommandResult {
pub status: String,
pub command: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub enum ConfigErrorKind {
Validation,
Parse,
Persistence,
Conflict,
NotFound,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ConfigValidationError {
pub key: Option<ConfigKey>,
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ConfigParseError {
pub line: usize,
pub content: String,
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ConfigPersistenceError {
pub path: String,
pub operation: String,
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ConfigConflictError {
pub key: Option<ConfigKey>,
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ConfigReloadResult {
pub status: String,
pub reloaded_path: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ConfigClearResult {
pub status: String,
pub removed_keys: usize,
}