diff --git a/Cargo.lock b/Cargo.lock index dc4db72..39f3dae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1652,7 +1652,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "lesavka_client" -version = "0.22.39" +version = "0.22.40" dependencies = [ "anyhow", "async-stream", @@ -1686,7 +1686,7 @@ dependencies = [ [[package]] name = "lesavka_common" -version = "0.22.39" +version = "0.22.40" dependencies = [ "anyhow", "base64", @@ -1698,7 +1698,7 @@ dependencies = [ [[package]] name = "lesavka_server" -version = "0.22.39" +version = "0.22.40" dependencies = [ "anyhow", "base64", diff --git a/client/Cargo.toml b/client/Cargo.toml index 990be87..1684464 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -4,7 +4,7 @@ path = "src/main.rs" [package] name = "lesavka_client" -version = "0.22.39" +version = "0.22.40" edition = "2024" [dependencies] diff --git a/common/Cargo.toml b/common/Cargo.toml index 9cc0b46..3238560 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lesavka_common" -version = "0.22.39" +version = "0.22.40" edition = "2024" build = "build.rs" diff --git a/server/Cargo.toml b/server/Cargo.toml index 3099e91..e26d29e 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -10,7 +10,7 @@ bench = false [package] name = "lesavka_server" -version = "0.22.39" +version = "0.22.40" edition = "2024" autobins = false diff --git a/server/src/main/entrypoint.rs b/server/src/main/entrypoint.rs index c244ee2..fd55dd5 100644 --- a/server/src/main/entrypoint.rs +++ b/server/src/main/entrypoint.rs @@ -1,19 +1,37 @@ /*──────────────── main ───────────────────────*/ -fn print_version_and_exit_requested() -> bool { - if std::env::args() - .skip(1) - .any(|arg| arg == "--version" || arg == "-V") - { - println!("{} {}", PKG_NAME, lesavka_server::VERSION); - return true; +fn usage_line() -> &'static str { + "Usage: lesavka-server [--version|-V] [--help|-h]" +} + +fn handle_startup_args() -> anyhow::Result { + let args = std::env::args().skip(1).collect::>(); + match args.as_slice() { + [] => Ok(false), + [arg] if arg == "--version" || arg == "-V" => { + println!("{} {}", PKG_NAME, lesavka_server::VERSION); + Ok(true) + } + [arg] if arg == "--help" || arg == "-h" => { + println!("{}", usage_line()); + Ok(true) + } + [arg] if arg.starts_with('-') => { + let suggestion = if "--version".starts_with(arg.as_str()) { + "; did you mean --version?" + } else { + "" + }; + anyhow::bail!("unknown option {arg:?}{suggestion}; {}", usage_line()) + } + [arg] => anyhow::bail!("unexpected positional argument {arg:?}; {}", usage_line()), + _ => anyhow::bail!("too many arguments; {}", usage_line()), } - false } #[cfg(not(coverage))] #[tokio::main(worker_threads = 4)] async fn main() -> anyhow::Result<()> { - if print_version_and_exit_requested() { + if handle_startup_args()? { return Ok(()); } @@ -58,7 +76,7 @@ async fn main() -> anyhow::Result<()> { #[cfg(coverage)] #[tokio::main(worker_threads = 2)] async fn main() -> anyhow::Result<()> { - if print_version_and_exit_requested() { + if handle_startup_args()? { return Ok(()); } diff --git a/tests/contract/server/main/server_main_process_contract.rs b/tests/contract/server/main/server_main_process_contract.rs index d8f4e69..b66cea7 100644 --- a/tests/contract/server/main/server_main_process_contract.rs +++ b/tests/contract/server/main/server_main_process_contract.rs @@ -134,6 +134,33 @@ fn server_binary_reports_version_without_starting_relay() { } } +#[test] +#[serial] +fn server_binary_rejects_truncated_version_flag_without_starting_relay() { + let Some(bin) = find_binary("lesavka-server") else { + return; + }; + let output = Command::new(bin) + .arg("--versio") + .output() + .expect("run lesavka-server --versio"); + assert!( + !output.status.success(), + "truncated flag should fail instead of starting a second relay" + ); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("unknown option") + && stderr.contains("did you mean --version") + && stderr.contains("Usage: lesavka-server"), + "truncated flag should produce a helpful CLI error; stderr was:\n{stderr}" + ); + assert!( + !stderr.contains("Address already in use") && !stderr.contains("open singleton lock"), + "argument validation should happen before relay, log, or UVC startup; stderr was:\n{stderr}" + ); +} + #[test] #[serial] fn server_binary_stays_up_with_missing_hid_nodes_and_current_version() {