use std::path::PathBuf;
use crate::cli::Cli;
use crate::error::Error;
#[derive(Debug, Clone)]
pub struct CompatArgs {
pub(crate) compat_root: PathBuf,
pub(crate) compat_report: PathBuf,
pub(crate) compat_cargo_test_argv: Vec<String>,
pub(crate) compat_manifest: Option<PathBuf>,
pub(crate) compat_commit: Option<String>,
pub(crate) compat_filter: Vec<String>,
pub(crate) compat_trybuild_macro: Vec<String>,
pub(crate) inner_cli: Cli,
}
impl CompatArgs {
pub fn from_cli(cli: Cli) -> Result<Self, Error> {
debug_assert!(
cli.compat,
"CompatArgs::from_cli called outside compat mode; validate_mode_consistency \
must have been bypassed"
);
let compat_root = cli
.compat_root
.clone()
.expect("validate_mode_consistency ensures compat_root is set");
let compat_report = cli
.compat_report
.clone()
.expect("validate_mode_consistency ensures compat_report is set");
let compat_cargo_test_argv = parse_argv_json(
cli.compat_cargo_test_argv
.as_deref()
.unwrap_or(DEFAULT_CARGO_TEST_ARGV_JSON),
)?;
let compat_manifest = cli.compat_manifest.clone();
let compat_commit = cli.compat_commit.clone();
let compat_filter = cli.compat_filter.clone();
let compat_trybuild_macro = cli.compat_trybuild_macro.clone();
Ok(Self {
compat_root,
compat_report,
compat_cargo_test_argv,
compat_manifest,
compat_commit,
compat_filter,
compat_trybuild_macro,
inner_cli: cli,
})
}
}
const DEFAULT_CARGO_TEST_ARGV_JSON: &str = r#"["cargo","test"]"#;
fn parse_argv_json(s: &str) -> Result<Vec<String>, Error> {
let value: serde_json::Value = serde_json::from_str(s).map_err(|e| Error::Cli {
clap_exit_code: 2,
message: format!(
"error: `--compat-cargo-test-argv` must be a JSON array of strings \
(e.g. `[\"cargo\",\"test\"]`); failed to parse as JSON: {e}"
),
})?;
let arr = match value {
serde_json::Value::Array(a) => a,
other => {
return Err(Error::Cli {
clap_exit_code: 2,
message: format!(
"error: `--compat-cargo-test-argv` must be a JSON array of strings \
(e.g. `[\"cargo\",\"test\"]`); got a JSON {}",
json_value_kind(&other),
),
});
}
};
let mut argv = Vec::with_capacity(arr.len());
for (idx, elem) in arr.into_iter().enumerate() {
match elem {
serde_json::Value::String(s) => argv.push(s),
other => {
return Err(Error::Cli {
clap_exit_code: 2,
message: format!(
"error: `--compat-cargo-test-argv` element at index {idx} is a JSON {} \
but every element must be a JSON string",
json_value_kind(&other),
),
});
}
}
}
Ok(argv)
}
fn json_value_kind(v: &serde_json::Value) -> &'static str {
match v {
serde_json::Value::Null => "null",
serde_json::Value::Bool(_) => "boolean",
serde_json::Value::Number(_) => "number",
serde_json::Value::String(_) => "string",
serde_json::Value::Array(_) => "array",
serde_json::Value::Object(_) => "object",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_argv_parses_to_cargo_test() {
let argv = parse_argv_json(DEFAULT_CARGO_TEST_ARGV_JSON).expect("default must parse");
assert_eq!(argv, vec!["cargo".to_string(), "test".to_string()]);
}
#[test]
fn parse_argv_json_rejects_object() {
let err = parse_argv_json(r#"{"cargo":"test"}"#).expect_err("object must be rejected");
match err {
Error::Cli { message, .. } => assert!(
message.contains("JSON object"),
"diagnostic must name the JSON kind: {message}"
),
other => panic!("expected Cli error, got {other:?}"),
}
}
#[test]
fn parse_argv_json_rejects_string() {
let err = parse_argv_json(r#""cargo test""#).expect_err("string must be rejected");
match err {
Error::Cli { message, .. } => assert!(
message.contains("JSON string"),
"diagnostic must name the JSON kind: {message}"
),
other => panic!("expected Cli error, got {other:?}"),
}
}
#[test]
fn parse_argv_json_rejects_non_string_element() {
let err = parse_argv_json(r#"["cargo", 42]"#).expect_err("number element must be rejected");
match err {
Error::Cli { message, .. } => {
assert!(
message.contains("index 1"),
"diagnostic must name the failing index: {message}"
);
assert!(
message.contains("JSON number"),
"diagnostic must name the JSON kind: {message}"
);
}
other => panic!("expected Cli error, got {other:?}"),
}
}
#[test]
fn parse_argv_json_rejects_malformed_json() {
let err =
parse_argv_json(r#"["cargo","test"#).expect_err("malformed JSON must be rejected");
match err {
Error::Cli { message, .. } => assert!(
message.contains("failed to parse as JSON"),
"diagnostic must surface the parse failure: {message}"
),
other => panic!("expected Cli error, got {other:?}"),
}
}
#[test]
fn parse_argv_json_accepts_extended_argv() {
let argv = parse_argv_json(r#"["cargo","+nightly","test","--","--ignored"]"#)
.expect("extended argv must parse");
assert_eq!(
argv,
vec![
"cargo".to_string(),
"+nightly".to_string(),
"test".to_string(),
"--".to_string(),
"--ignored".to_string(),
]
);
}
}