use clap::ArgAction;
use clap::Parser;
use serde::de::Error as SerdeError;
use toml::Value;
#[derive(Parser, Debug, Default, Clone)]
pub struct CliConfigOverrides {
#[arg(
short = 'c',
long = "config",
value_name = "key=value",
action = ArgAction::Append,
global = true,
)]
pub raw_overrides: Vec<String>,
}
impl CliConfigOverrides {
pub fn prepend_root_overrides(&mut self, root_overrides: Self) {
self.raw_overrides
.splice(0..0, root_overrides.raw_overrides);
}
pub fn parse_overrides(&self) -> Result<Vec<(String, Value)>, String> {
self.raw_overrides
.iter()
.map(|s| {
let mut parts = s.splitn(2, '=');
let key = match parts.next() {
Some(k) => k.trim(),
None => return Err("Override missing key".to_string()),
};
let value_str = parts
.next()
.ok_or_else(|| format!("Invalid override (missing '='): {s}"))?
.trim();
if key.is_empty() {
return Err(format!("Empty key in override: {s}"));
}
let value: Value = match parse_toml_value(value_str) {
Ok(v) => v,
Err(_) => {
let trimmed = value_str.trim().trim_matches(|c| c == '"' || c == '\'');
Value::String(trimmed.to_string())
}
};
Ok((canonicalize_override_key(key), value))
})
.collect()
}
}
fn canonicalize_override_key(key: &str) -> String {
if key == "use_legacy_landlock" {
"features.use_legacy_landlock".to_string()
} else {
key.to_string()
}
}
fn parse_toml_value(raw: &str) -> Result<Value, toml::de::Error> {
let wrapped = format!("_x_ = {raw}");
let table: toml::Table = toml::from_str(&wrapped)?;
table
.get("_x_")
.cloned()
.ok_or_else(|| SerdeError::custom("missing sentinel key"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_basic_scalar() {
let v = parse_toml_value("42").expect("parse");
assert_eq!(v.as_integer(), Some(42));
}
#[test]
fn parses_bool() {
let true_literal = parse_toml_value("true").expect("parse");
assert_eq!(true_literal.as_bool(), Some(true));
let false_literal = parse_toml_value("false").expect("parse");
assert_eq!(false_literal.as_bool(), Some(false));
}
#[test]
fn fails_on_unquoted_string() {
assert!(parse_toml_value("hello").is_err());
}
#[test]
fn parses_array() {
let v = parse_toml_value("[1, 2, 3]").expect("parse");
let arr = v.as_array().expect("array");
assert_eq!(arr.len(), 3);
}
#[test]
fn canonicalizes_use_legacy_landlock_alias() {
let overrides = CliConfigOverrides {
raw_overrides: vec!["use_legacy_landlock=true".to_string()],
};
let parsed = overrides.parse_overrides().expect("parse_overrides");
assert_eq!(parsed[0].0.as_str(), "features.use_legacy_landlock");
assert_eq!(parsed[0].1.as_bool(), Some(true));
}
#[test]
fn prepends_root_overrides() {
let mut subcommand_overrides = CliConfigOverrides {
raw_overrides: vec![r#"model="gpt-5.2""#.to_string()],
};
subcommand_overrides.prepend_root_overrides(CliConfigOverrides {
raw_overrides: vec![r#"model="gpt-5.1""#.to_string()],
});
assert_eq!(
subcommand_overrides.raw_overrides,
vec![
r#"model="gpt-5.1""#.to_string(),
r#"model="gpt-5.2""#.to_string(),
]
);
}
#[test]
fn parses_inline_table() {
let v = parse_toml_value("{a = 1, b = 2}").expect("parse");
let tbl = v.as_table().expect("table");
assert_eq!(tbl.get("a").unwrap().as_integer(), Some(1));
assert_eq!(tbl.get("b").unwrap().as_integer(), Some(2));
}
}