fix(protocol): reject duplicate command/args keys in wait-for block
A second `command` node silently overwrote the first via a scalar Option<PathBuf>, and a second `args` node silently overwrote the first via a plain Vec, so duplicate keys last-won instead of tripping the exactly-one-condition rule. Track args as Option<Vec<String>> and error immediately on a repeated command or args key.
This commit is contained in:
@@ -267,7 +267,7 @@ fn parse_wait_for(doc: &KdlDocument, path: &Path) -> Result<Option<WaitForConfig
|
||||
|
||||
let mut conditions: Vec<WaitCondition> = Vec::new();
|
||||
let mut command: Option<PathBuf> = None;
|
||||
let mut args: Vec<String> = Vec::new();
|
||||
let mut args: Option<Vec<String>> = None;
|
||||
let mut timeout = default_wait_timeout();
|
||||
let mut interval = default_wait_interval();
|
||||
|
||||
@@ -284,14 +284,24 @@ fn parse_wait_for(doc: &KdlDocument, path: &Path) -> Result<Option<WaitForConfig
|
||||
conditions.push(WaitCondition::Tcp(addr));
|
||||
}
|
||||
"command" => {
|
||||
if command.is_some() {
|
||||
return Err(invalid("duplicate key `command`".into()));
|
||||
}
|
||||
|
||||
command = Some(PathBuf::from(string_arg(child, "wait-for", path)?));
|
||||
}
|
||||
"args" => {
|
||||
args = child
|
||||
.entries()
|
||||
.iter()
|
||||
.filter_map(|e| e.value().as_string().map(str::to_string))
|
||||
.collect();
|
||||
if args.is_some() {
|
||||
return Err(invalid("duplicate key `args`".into()));
|
||||
}
|
||||
|
||||
args = Some(
|
||||
child
|
||||
.entries()
|
||||
.iter()
|
||||
.filter_map(|e| e.value().as_string().map(str::to_string))
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
"timeout" => {
|
||||
timeout = parse_duration_arg(child, "wait-for", path)?;
|
||||
@@ -306,8 +316,11 @@ fn parse_wait_for(doc: &KdlDocument, path: &Path) -> Result<Option<WaitForConfig
|
||||
}
|
||||
|
||||
if let Some(command) = command {
|
||||
conditions.push(WaitCondition::Command { command, args });
|
||||
} else if !args.is_empty() {
|
||||
conditions.push(WaitCondition::Command {
|
||||
command,
|
||||
args: args.unwrap_or_default(),
|
||||
});
|
||||
} else if args.is_some() {
|
||||
return Err(invalid("`args` requires `command`".into()));
|
||||
}
|
||||
|
||||
@@ -670,4 +683,48 @@ stop {
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_command_key_fails() {
|
||||
let text =
|
||||
"command \"/bin/x\"\nport 1\nwait-for { command \"/bin/a\"\ncommand \"/bin/b\" }";
|
||||
let err = parse_server_config("foo", text, p()).unwrap_err();
|
||||
|
||||
assert!(matches!(
|
||||
err,
|
||||
ConfigError::InvalidValue {
|
||||
field: "wait-for",
|
||||
..
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_args_key_fails() {
|
||||
let text =
|
||||
"command \"/bin/x\"\nport 1\nwait-for { command \"docker\"\nargs \"a\"\nargs \"b\" }";
|
||||
let err = parse_server_config("foo", text, p()).unwrap_err();
|
||||
|
||||
assert!(matches!(
|
||||
err,
|
||||
ConfigError::InvalidValue {
|
||||
field: "wait-for",
|
||||
..
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expand_tilde_alone_is_left_unchanged() {
|
||||
let home = PathBuf::from("/home/someone");
|
||||
|
||||
assert_eq!(expand_tilde("~", Some(&home)), PathBuf::from("~"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expand_tilde_of_empty_string_is_left_unchanged() {
|
||||
let home = PathBuf::from("/home/someone");
|
||||
|
||||
assert_eq!(expand_tilde("", Some(&home)), PathBuf::from(""));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user