use crate::config::Config;
use crate::errors;
pub fn strip_xyz_flags(args: Vec<String>, cfg: &mut Config) -> errors::Result<Vec<String>> {
let mut out = Vec::with_capacity(args.len());
let mut i = 0;
while i < args.len() {
let a = args[i].clone();
if a == "--" {
out.extend(args[i..].iter().cloned());
break;
}
let matched = match a.as_str() {
"--xyz.addr" => {
cfg.addr = take_value(&args, &mut i, &a)?;
true
}
"--xyz.bearer" => {
let v = take_value(&args, &mut i, &a)?;
cfg.bearer_tokens = merge_tokens(cfg.bearer_tokens.clone(), &v);
true
}
"--xyz.log-level" => {
let v = take_value(&args, &mut i, &a)?;
cfg.log_level = crate::logx::parse_level(&v)?;
true
}
"--xyz.timeout" => {
let v = take_value(&args, &mut i, &a)?;
cfg.timeout = crate::spec::scalar::parse_duration(&v).map_err(|e| {
errors::Error::new(
errors::Kind::Internal,
format!("invalid --xyz.timeout {v:?}: {e}"),
)
})?;
true
}
"--xyz.tls-cert" => {
cfg.cert_file = take_value(&args, &mut i, &a)?;
true
}
"--xyz.tls-key" => {
cfg.key_file = take_value(&args, &mut i, &a)?;
true
}
"--xyz.cors" => {
let v = take_value(&args, &mut i, &a)?;
cfg.cors_origins = merge_tokens(cfg.cors_origins.clone(), &v);
true
}
"--xyz.default" => {
let v = take_value(&args, &mut i, &a)?;
merge_default_pairs(&mut cfg.channel_defaults, &v)?;
true
}
"--xyz.format" => {
let v = take_value(&args, &mut i, &a)?;
if crate::cli::format::Format::parse(&v).is_none() {
return Err(errors::Error::new(
errors::Kind::Internal,
format!("invalid --xyz.format {v:?} (want text|json|jsonl|markdown)"),
));
}
cfg.format = v;
true
}
"--xyz.header" => {
let v = take_value(&args, &mut i, &a)?;
merge_header_pairs(&mut cfg.response_headers, &v)?;
true
}
"--xyz.no-server-headers" => {
cfg.no_server_headers = true;
true
}
"--xyz.lang" => {
let v = take_value(&args, &mut i, &a)?;
if crate::lang::XyzLang::parse(&v).is_none() {
return Err(errors::Error::new(
errors::Kind::Internal,
format!("invalid --xyz.lang {v:?} (want en|zh-CN)"),
));
}
cfg.lang = v;
true
}
_ => {
if let Some(v) = a.strip_prefix("--xyz.addr=") {
cfg.addr = v.to_string();
true
} else if let Some(v) = a.strip_prefix("--xyz.bearer=") {
cfg.bearer_tokens = merge_tokens(cfg.bearer_tokens.clone(), v);
true
} else if let Some(v) = a.strip_prefix("--xyz.log-level=") {
cfg.log_level = crate::logx::parse_level(v)?;
true
} else if let Some(v) = a.strip_prefix("--xyz.timeout=") {
cfg.timeout = crate::spec::scalar::parse_duration(v).map_err(|e| {
errors::Error::new(
errors::Kind::Internal,
format!("invalid --xyz.timeout: {e}"),
)
})?;
true
} else if let Some(v) = a.strip_prefix("--xyz.tls-cert=") {
cfg.cert_file = v.to_string();
true
} else if let Some(v) = a.strip_prefix("--xyz.tls-key=") {
cfg.key_file = v.to_string();
true
} else if let Some(v) = a.strip_prefix("--xyz.cors=") {
cfg.cors_origins = merge_tokens(cfg.cors_origins.clone(), v);
true
} else if let Some(v) = a.strip_prefix("--xyz.default=") {
merge_default_pairs(&mut cfg.channel_defaults, v)?;
true
} else if let Some(v) = a.strip_prefix("--xyz.format=") {
if crate::cli::format::Format::parse(v).is_none() {
return Err(errors::Error::new(
errors::Kind::Internal,
format!("invalid --xyz.format {v:?} (want text|json|jsonl|markdown)"),
));
}
cfg.format = v.to_string();
true
} else if let Some(v) = a.strip_prefix("--xyz.header=") {
merge_header_pairs(&mut cfg.response_headers, v)?;
true
} else if let Some(v) = a.strip_prefix("--xyz.lang=") {
if crate::lang::XyzLang::parse(v).is_none() {
return Err(errors::Error::new(
errors::Kind::Internal,
format!("invalid --xyz.lang {v:?} (want en|zh-CN)"),
));
}
cfg.lang = v.to_string();
true
} else {
false
}
}
};
if !matched {
out.push(a);
}
i += 1;
}
Ok(out)
}
fn take_value(args: &[String], i: &mut usize, flag: &str) -> errors::Result<String> {
if *i + 1 >= args.len() {
return Err(errors::Error::new(
errors::Kind::Internal,
format!("flag {flag} needs an argument"),
));
}
*i += 1;
Ok(args[*i].clone())
}
pub fn merge_default_pairs(
target: &mut std::collections::HashMap<String, String>,
flag: &str,
) -> errors::Result<()> {
for pair in flag.split(',').map(str::trim).filter(|p| !p.is_empty()) {
let Some((k, v)) = pair.split_once('=') else {
return Err(errors::Error::new(
errors::Kind::Internal,
format!("invalid --default {pair:?} (want key=value)"),
));
};
let k = k.trim();
if k.is_empty() {
return Err(errors::Error::new(
errors::Kind::Internal,
format!("invalid --default {pair:?} (want key=value)"),
));
}
target.insert(k.to_string(), v.to_string());
}
Ok(())
}
pub fn parse_serve_args(args: &[String], mut cfg: Config) -> Config {
if cfg.addr.is_empty() {
cfg.addr = ":8080".to_string();
}
let mut i = 0;
while i < args.len() {
let a = args[i].as_str();
match a {
"--addr" | "--bearer" | "--timeout" | "--tls-cert" | "--tls-key" | "--cors"
| "--default" => {
let Some(v) = args.get(i + 1) else { break };
i += 1;
match a {
"--addr" => cfg.addr = v.clone(),
"--bearer" => cfg.bearer_tokens = merge_tokens(cfg.bearer_tokens.clone(), v),
"--timeout" => {
if let Ok(d) = crate::spec::scalar::parse_duration(v) {
cfg.timeout = d;
}
}
"--tls-cert" => cfg.cert_file = v.clone(),
"--tls-key" => cfg.key_file = v.clone(),
"--default" => {
let _ = merge_default_pairs(&mut cfg.channel_defaults, v);
}
_ => cfg.cors_origins = merge_tokens(cfg.cors_origins.clone(), v),
}
}
other => {
if let Some(v) = other.strip_prefix("--addr=") {
cfg.addr = v.to_string();
} else if let Some(v) = other.strip_prefix("--bearer=") {
cfg.bearer_tokens = merge_tokens(cfg.bearer_tokens.clone(), v);
} else if let Some(v) = other.strip_prefix("--timeout=") {
if let Ok(d) = crate::spec::scalar::parse_duration(v) {
cfg.timeout = d;
}
} else if let Some(v) = other.strip_prefix("--tls-cert=") {
cfg.cert_file = v.to_string();
} else if let Some(v) = other.strip_prefix("--tls-key=") {
cfg.key_file = v.to_string();
} else if let Some(v) = other.strip_prefix("--cors=") {
cfg.cors_origins = merge_tokens(cfg.cors_origins.clone(), v);
} else if let Some(v) = other.strip_prefix("--default=") {
let _ = merge_default_pairs(&mut cfg.channel_defaults, v);
} else if let Some(kv) = other.strip_prefix("--") {
if let Some((k, v)) = kv.split_once('=') {
let _ = merge_default_pairs(&mut cfg.channel_defaults, &format!("{k}={v}"));
} else if let Some(next) = args.get(i + 1)
&& !next.starts_with('-')
{
let _ =
merge_default_pairs(&mut cfg.channel_defaults, &format!("{kv}={next}"));
i += 1;
}
}
}
}
i += 1;
}
cfg
}
pub fn split_versions(s: &str) -> Vec<String> {
s.split(',')
.map(str::trim)
.filter(|p| !p.is_empty())
.map(str::to_string)
.collect()
}
fn merge_header_pairs(headers: &mut Vec<(String, String)>, v: &str) -> errors::Result<()> {
let (k, val) = v.split_once('=').ok_or_else(|| {
errors::Error::new(
errors::Kind::Internal,
format!("invalid --xyz.header {v:?} (want key=value)"),
)
})?;
let k = k.trim();
if k.is_empty() {
return Err(errors::Error::new(
errors::Kind::Internal,
"--xyz.header requires a non-empty key".to_string(),
));
}
if let Some(slot) = headers.iter_mut().find(|(h, _)| h == k) {
slot.1 = val.to_string();
} else {
headers.push((k.to_string(), val.to_string()));
}
Ok(())
}
pub fn merge_tokens(existing: Vec<String>, flag: &str) -> Vec<String> {
let mut seen: Vec<String> = Vec::new();
let mut out: Vec<String> = Vec::new();
for t in existing {
if !seen.contains(&t) {
seen.push(t.clone());
out.push(t);
}
}
for t in flag.split(',').map(str::trim) {
if !t.is_empty() && !seen.iter().any(|s| s == t) {
seen.push(t.to_string());
out.push(t.to_string());
}
}
out
}