api_testing_core/
cli_contract.rs1use std::ffi::OsString;
10
11use clap::error::ErrorKind;
12
13pub use nils_common::cli_contract::{
14 Envelope, EnvelopeError, OutputFormat, emit_parse_error, exit, schema_version_for,
15};
16
17pub fn handle_parse_error<I>(binary: &str, argv: I, err: clap::Error) -> i32
24where
25 I: IntoIterator<Item = OsString>,
26{
27 let kind = err.kind();
28 if matches!(
29 kind,
30 ErrorKind::DisplayHelp
31 | ErrorKind::DisplayVersion
32 | ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
33 ) {
34 let _ = err.print();
35 return err.exit_code();
36 }
37
38 let argv: Vec<OsString> = argv.into_iter().collect();
39 let format = detect_format_from_argv(&argv);
40 let code = match kind {
41 ErrorKind::InvalidSubcommand => "unknown-subcommand",
42 _ => "parse-error",
43 };
44 let message = render_clap_message(&err);
45 emit_parse_error(binary, format, code, &message)
46}
47
48fn detect_format_from_argv(argv: &[OsString]) -> OutputFormat {
49 let mut iter = argv.iter().skip(1);
50 while let Some(arg) = iter.next() {
51 let arg = arg.to_string_lossy();
52 if arg == "--json" {
53 return OutputFormat::Json;
54 }
55 if arg == "--format"
56 && let Some(next) = iter.next()
57 && next.to_string_lossy().eq_ignore_ascii_case("json")
58 {
59 return OutputFormat::Json;
60 }
61 if let Some(rest) = arg.strip_prefix("--format=")
62 && rest.eq_ignore_ascii_case("json")
63 {
64 return OutputFormat::Json;
65 }
66 }
67 OutputFormat::Text
68}
69
70fn render_clap_message(err: &clap::Error) -> String {
71 err.to_string()
72 .lines()
73 .find(|line| !line.trim().is_empty())
74 .map(|line| {
75 let line = line.trim();
76 line.strip_prefix("error:")
77 .map(str::trim)
78 .unwrap_or(line)
79 .to_string()
80 })
81 .unwrap_or_else(|| "command-line parse failed".to_string())
82}