use crate::cli::args::{ConfigCommand, GlobalArgs};
use crate::cli::output::OutputWriter;
use crate::config::{self, Config};
use crate::error::{ExitStatus, GdeltError, Result};
use serde_json::json;
pub async fn handle_config(cmd: ConfigCommand, global: &GlobalArgs) -> Result<ExitStatus> {
match cmd {
ConfigCommand::Show => show_config(global),
ConfigCommand::Get(args) => get_config(&args.key, global),
ConfigCommand::Set(args) => set_config(&args.key, &args.value, global),
ConfigCommand::Reset => reset_config(global),
ConfigCommand::Validate => validate_config(global),
}
}
fn show_config(global: &GlobalArgs) -> Result<ExitStatus> {
let config = config::load_config(global.config.clone())?;
let output = OutputWriter::new(global);
output.write_value(&json!({
"config_path": config::default_config_path().map(|p| p.display().to_string()),
"data_dir": config::data_dir().map(|p| p.display().to_string()),
"cache_dir": config::cache_dir().map(|p| p.display().to_string()),
"config": config,
}))?;
Ok(ExitStatus::Success)
}
fn get_config(key: &str, global: &GlobalArgs) -> Result<ExitStatus> {
let config = config::load_config(global.config.clone())?;
let output = OutputWriter::new(global);
let parts: Vec<&str> = key.split('.').collect();
let value = get_nested_value(&config, &parts)?;
output.write_value(&value)?;
Ok(ExitStatus::Success)
}
fn get_nested_value(config: &Config, keys: &[&str]) -> Result<serde_json::Value> {
let config_json = serde_json::to_value(config)?;
let mut current = &config_json;
for key in keys {
current = current.get(key).ok_or_else(|| {
GdeltError::Config(format!("Key not found: {}", keys.join(".")))
})?;
}
Ok(current.clone())
}
fn set_config(key: &str, value: &str, global: &GlobalArgs) -> Result<ExitStatus> {
let config_path = global.config.clone().or_else(config::default_config_path)
.ok_or_else(|| GdeltError::Config("Could not determine config path".into()))?;
let mut config = if config_path.exists() {
config::load_config(Some(config_path.clone()))?
} else {
Config::default()
};
let parts: Vec<&str> = key.split('.').collect();
set_nested_value(&mut config, &parts, value)?;
let toml = config.to_toml().map_err(|e| GdeltError::Config(e.to_string()))?;
if let Some(parent) = config_path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(&config_path, toml)?;
let output = OutputWriter::new(global);
output.write_value(&json!({
"status": "success",
"key": key,
"value": value,
"config_path": config_path.display().to_string(),
}))?;
Ok(ExitStatus::Success)
}
fn set_nested_value(config: &mut Config, keys: &[&str], value: &str) -> Result<()> {
if keys.is_empty() {
return Err(GdeltError::Config("Empty key".into()));
}
match keys[0] {
"network" => match keys.get(1) {
Some(&"timeout_secs") => config.network.timeout_secs = value.parse()
.map_err(|_| GdeltError::Config("Invalid number".into()))?,
Some(&"retries") => config.network.retries = value.parse()
.map_err(|_| GdeltError::Config("Invalid number".into()))?,
Some(&"rate_limit_rps") => config.network.rate_limit_rps = value.parse()
.map_err(|_| GdeltError::Config("Invalid number".into()))?,
Some(&"proxy") => config.network.proxy = if value.is_empty() { None } else { Some(value.to_string()) },
Some(&"user_agent") => config.network.user_agent = value.to_string(),
_ => return Err(GdeltError::Config(format!("Unknown network key: {}", keys.join(".")))),
},
"cache" => match keys.get(1) {
Some(&"enabled") => config.cache.enabled = value.parse()
.map_err(|_| GdeltError::Config("Invalid boolean".into()))?,
Some(&"max_size_mb") => config.cache.max_size_mb = value.parse()
.map_err(|_| GdeltError::Config("Invalid number".into()))?,
_ => return Err(GdeltError::Config(format!("Unknown cache key: {}", keys.join(".")))),
},
"general" => match keys.get(1) {
Some(&"default_format") => config.general.default_format = value.to_string(),
Some(&"timezone") => config.general.timezone = value.to_string(),
Some(&"color") => config.general.color = value.parse()
.map_err(|_| GdeltError::Config("Invalid boolean".into()))?,
Some(&"quiet") => config.general.quiet = value.parse()
.map_err(|_| GdeltError::Config("Invalid boolean".into()))?,
_ => return Err(GdeltError::Config(format!("Unknown general key: {}", keys.join(".")))),
},
"defaults" => match keys.get(1) {
Some(&"doc") => match keys.get(2) {
Some(&"timespan") => config.defaults.doc.timespan = value.to_string(),
Some(&"max_records") => config.defaults.doc.max_records = value.parse()
.map_err(|_| GdeltError::Config("Invalid number".into()))?,
Some(&"sort") => config.defaults.doc.sort = value.to_string(),
_ => return Err(GdeltError::Config(format!("Unknown defaults.doc key: {}", keys.join(".")))),
},
_ => return Err(GdeltError::Config(format!("Unknown defaults key: {}", keys.join(".")))),
},
_ => return Err(GdeltError::Config(format!("Unknown key: {}", keys.join(".")))),
}
Ok(())
}
fn reset_config(global: &GlobalArgs) -> Result<ExitStatus> {
let config_path = global.config.clone().or_else(config::default_config_path)
.ok_or_else(|| GdeltError::Config("Could not determine config path".into()))?;
let config = Config::default();
let toml = config.to_toml().map_err(|e| GdeltError::Config(e.to_string()))?;
if let Some(parent) = config_path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(&config_path, toml)?;
let output = OutputWriter::new(global);
output.write_value(&json!({
"status": "success",
"message": "Configuration reset to defaults",
"config_path": config_path.display().to_string(),
}))?;
Ok(ExitStatus::Success)
}
fn validate_config(global: &GlobalArgs) -> Result<ExitStatus> {
let output = OutputWriter::new(global);
match config::load_config(global.config.clone()) {
Ok(config) => {
let mut warnings = Vec::new();
if config.network.timeout_secs == 0 {
warnings.push("network.timeout_secs should be greater than 0");
}
if config.network.retries > 10 {
warnings.push("network.retries > 10 may cause excessive delays");
}
output.write_value(&json!({
"valid": true,
"warnings": warnings,
}))?;
Ok(ExitStatus::Success)
}
Err(e) => {
output.write_value(&json!({
"valid": false,
"error": e.to_string(),
}))?;
Ok(ExitStatus::ConfigError)
}
}
}