use crate::contracts::CiGateConfig;
use anyhow::{Result, bail};
pub(crate) fn validate_ci_gate_config(ci_gate: Option<&CiGateConfig>, label: &str) -> Result<()> {
let Some(ci_gate) = ci_gate else {
return Ok(());
};
if !ci_gate.is_enabled() {
return Ok(());
}
match ci_gate.argv.as_ref() {
Some(argv) => validate_ci_gate_argv(argv, label),
None => bail!("Invalid {label}.ci_gate: enabled CI gate requires argv settings."),
}
}
pub(crate) fn validate_ci_gate_argv(argv: &[String], label: &str) -> Result<()> {
if argv.is_empty() {
bail!("Invalid {label}.ci_gate.argv: at least one argv element is required.");
}
if argv.iter().any(|arg| arg.is_empty()) {
bail!("Invalid {label}.ci_gate.argv: argv entries must not be empty strings.");
}
if argv_launches_shell(argv) {
bail!(
"Invalid {label}.ci_gate.argv: shell launcher argv is not supported. Use direct executable argv instead."
);
}
Ok(())
}
fn argv_launches_shell(argv: &[String]) -> bool {
let Some(program) = argv.first() else {
return false;
};
let Some(program_name) = std::path::Path::new(program)
.file_name()
.and_then(|name| name.to_str())
else {
return false;
};
let shell_program = matches!(
program_name,
"sh" | "bash" | "zsh" | "dash" | "fish" | "cmd" | "pwsh" | "powershell"
);
shell_program
&& argv.iter().skip(1).any(|arg| {
arg == "-c" || arg.eq_ignore_ascii_case("/c") || arg.eq_ignore_ascii_case("-command")
})
}