diff --git a/crates/xy-protocol/src/kdl_parse.rs b/crates/xy-protocol/src/kdl_parse.rs index 82f3dae..14294ce 100644 --- a/crates/xy-protocol/src/kdl_parse.rs +++ b/crates/xy-protocol/src/kdl_parse.rs @@ -154,6 +154,8 @@ fn parse_restart(doc: &KdlDocument, path: &Path) -> Result { + single_arg(child, "restart", path)?; + let s = string_arg(child, "policy", path)?; out.policy = match s.as_str() { @@ -170,12 +172,18 @@ fn parse_restart(doc: &KdlDocument, path: &Path) -> Result { + single_arg(child, "restart", path)?; + out.backoff_initial = parse_duration_arg(child, "restart.backoff-initial", path)?; } "backoff-max" => { + single_arg(child, "restart", path)?; + out.backoff_max = parse_duration_arg(child, "restart.backoff-max", path)?; } "max-retries-per-minute" => { + single_arg(child, "restart", path)?; + let v = child .entries() .first() @@ -220,6 +228,8 @@ fn parse_stop(doc: &KdlDocument, path: &Path) -> Result for child in children.nodes() { match child.name().value() { "grace" => { + single_arg(child, "stop", path)?; + out.grace = parse_duration_arg(child, "stop.grace", path)?; } other => { @@ -274,16 +284,22 @@ fn parse_wait_for(doc: &KdlDocument, path: &Path) -> Result { + single_arg(child, "wait-for", path)?; + let raw = string_arg(child, "wait-for", path)?; conditions.push(WaitCondition::Path(expand_tilde(&raw, home.as_deref()))); } "tcp" => { + single_arg(child, "wait-for", path)?; + let addr = string_arg(child, "wait-for", path)?; conditions.push(WaitCondition::Tcp(addr)); } "command" => { + single_arg(child, "wait-for", path)?; + if command.is_some() { return Err(invalid("duplicate key `command`".into())); } @@ -304,9 +320,13 @@ fn parse_wait_for(doc: &KdlDocument, path: &Path) -> Result { + single_arg(child, "wait-for", path)?; + timeout = parse_duration_arg(child, "wait-for", path)?; } "interval" => { + single_arg(child, "wait-for", path)?; + interval = parse_duration_arg(child, "wait-for", path)?; } other => { @@ -339,6 +359,21 @@ fn parse_wait_for(doc: &KdlDocument, path: &Path) -> Result Result<(), ConfigError> { + if node.entries().len() == 1 { + return Ok(()); + } + + Err(ConfigError::InvalidValue { + path: path.to_path_buf(), + field, + message: format!( + "unexpected extra arguments for key `{}`; put each key on its own line", + node.name().value() + ), + }) +} + fn string_arg(node: &KdlNode, field: &'static str, path: &Path) -> Result { node.entries() .first() @@ -727,4 +762,52 @@ stop { assert_eq!(expand_tilde("", Some(&home)), PathBuf::from("")); } + + #[test] + fn one_line_restart_block_with_two_keys_fails() { + let text = + "command \"/bin/x\"\nport 1\nrestart { policy \"always\" backoff-initial \"10ms\" }"; + let err = parse_server_config("foo", text, p()).unwrap_err(); + + assert!(matches!( + err, + ConfigError::InvalidValue { + field: "restart", + .. + } + )); + } + + #[test] + fn one_line_wait_for_block_with_two_keys_fails() { + let text = "command \"/bin/x\"\nport 1\nwait-for { path \"/x\" timeout \"5s\" }"; + let err = parse_server_config("foo", text, p()).unwrap_err(); + + assert!(matches!( + err, + ConfigError::InvalidValue { + field: "wait-for", + .. + } + )); + } + + #[test] + fn one_line_single_key_stop_block_still_parses() { + let text = "command \"/bin/x\"\nport 1\nstop { grace \"30s\" }"; + let cfg = parse_server_config("foo", text, p()).unwrap(); + + assert_eq!(cfg.stop.grace, Duration::from_secs(30)); + } + + #[test] + fn one_line_single_key_wait_for_block_still_parses() { + let text = "command \"/bin/x\"\nport 1\nwait-for { path \"/x\" }"; + let cfg = parse_server_config("foo", text, p()).unwrap(); + + assert_eq!( + cfg.wait_for.unwrap().condition, + WaitCondition::Path(PathBuf::from("/x")) + ); + } }