use crate::error::{ComposeError, Result};
const KNOWN_SIGNALS: &[&str] = &[
"ABRT", "ALRM", "BUS", "CHLD", "CLD", "CONT", "EMT", "FPE", "HUP", "ILL", "INT", "IO", "IOT",
"KILL", "LOST", "PIPE", "POLL", "PROF", "PWR", "QUIT", "RTMAX", "RTMIN", "SEGV", "STKFLT",
"STOP", "SYS", "TERM", "TRAP", "TSTP", "TTIN", "TTOU", "UNUSED", "URG", "USR1", "USR2",
"VTALRM", "WINCH", "XCPU", "XFSZ",
];
pub(crate) fn validate_signal(signal: &str) -> Result<()> {
let trimmed = signal.trim();
if trimmed.is_empty() {
return Err(ComposeError::InvalidSignal(
"signal must not be empty".into(),
));
}
if trimmed.chars().all(|c| c.is_ascii_digit()) {
return match trimmed.parse::<u32>() {
Ok(n) if (1..=64).contains(&n) => Ok(()),
_ => Err(ComposeError::InvalidSignal(signal.into())),
};
}
let upper = trimmed.to_ascii_uppercase();
let name = upper.strip_prefix("SIG").unwrap_or(&upper);
if KNOWN_SIGNALS.contains(&name) {
Ok(())
} else {
Err(ComposeError::InvalidSignal(signal.into()))
}
}
#[cfg(test)]
mod tests {
use super::validate_signal;
use crate::error::ComposeError;
#[test]
fn accepts_common_signal_names() {
for s in ["SIGKILL", "SIGTERM", "SIGHUP", "SIGINT", "SIGUSR1"] {
assert!(validate_signal(s).is_ok(), "{s} should be accepted");
}
}
#[test]
fn accepts_names_without_sig_prefix_case_insensitive() {
assert!(validate_signal("TERM").is_ok());
assert!(validate_signal("term").is_ok());
assert!(validate_signal("Kill").is_ok());
}
#[test]
fn accepts_numeric_signals_in_range() {
assert!(validate_signal("9").is_ok());
assert!(validate_signal("15").is_ok());
assert!(validate_signal("1").is_ok());
assert!(validate_signal("64").is_ok());
}
#[test]
fn rejects_empty_signal() {
let err = validate_signal("").unwrap_err();
assert!(matches!(err, ComposeError::InvalidSignal(_)));
assert!(err.to_string().contains("invalid signal"));
}
#[test]
fn rejects_whitespace_only_signal() {
assert!(matches!(
validate_signal(" ").unwrap_err(),
ComposeError::InvalidSignal(_)
));
}
#[test]
fn rejects_out_of_range_and_zero_numbers() {
assert!(matches!(
validate_signal("0").unwrap_err(),
ComposeError::InvalidSignal(_)
));
assert!(matches!(
validate_signal("65").unwrap_err(),
ComposeError::InvalidSignal(_)
));
assert!(matches!(
validate_signal("9999").unwrap_err(),
ComposeError::InvalidSignal(_)
));
}
#[test]
fn rejects_unknown_signal_names() {
assert!(matches!(
validate_signal("SIGBOGUS").unwrap_err(),
ComposeError::InvalidSignal(_)
));
assert!(matches!(
validate_signal("not-a-signal").unwrap_err(),
ComposeError::InvalidSignal(_)
));
}
}