unsafe-review-cli 0.3.1

CLI adapter for unsafe-review
Documentation
use super::*;
use crate::command::{ReceiptTemplateOptions, SavedOutputReceiptOptions};

pub(super) fn parse_receipt(args: Vec<String>) -> Result<Command, String> {
    let mut rest = args;
    let Some(subcommand) = rest.first() else {
        return Err(
            "missing receipt subcommand `import-miri`, `import-careful`, `import-sanitizer`, `import-concurrency`, `import-proof`, `template`, `validate`, or `audit`"
                .to_string(),
        );
    };
    let subcommand = subcommand.clone();
    rest.remove(0);
    match subcommand.as_str() {
        "import-careful" | "import-cargo-careful" => {
            parse_saved_output_receipt(rest, "import-careful", false)
                .map(Command::ReceiptImportCareful)
        }
        "import-miri" => {
            parse_saved_output_receipt(rest, "import-miri", false).map(Command::ReceiptImportMiri)
        }
        "import-sanitizer" => parse_saved_output_receipt(rest, "import-sanitizer", true)
            .map(Command::ReceiptImportSanitizer),
        "import-concurrency" => parse_saved_output_receipt(rest, "import-concurrency", true)
            .map(Command::ReceiptImportConcurrency),
        "import-proof" => {
            parse_saved_output_receipt(rest, "import-proof", true).map(Command::ReceiptImportProof)
        }
        "template" => parse_receipt_template(rest).map(Command::ReceiptTemplate),
        "validate" => parse_receipt_validate(rest),
        "audit" => parse_receipt_audit(rest).map(Command::ReceiptAudit),
        other => Err(format!("unknown receipt subcommand `{other}`")),
    }
}

pub(super) fn parse_receipt_template(args: Vec<String>) -> Result<ReceiptTemplateOptions, String> {
    let mut options = ReceiptTemplateOptions::default();
    let mut id: Option<String> = None;
    let mut idx = 0usize;
    while idx < args.len() {
        match args[idx].as_str() {
            "--tool" => {
                idx += 1;
                options.tool = value(&args, idx, "--tool")?.to_string();
            }
            arg if arg.starts_with("--tool=") => {
                options.tool = inline_value(arg, "--tool")?.to_string();
            }
            "--strength" => {
                idx += 1;
                options.strength = value(&args, idx, "--strength")?.to_string();
            }
            arg if arg.starts_with("--strength=") => {
                options.strength = inline_value(arg, "--strength")?.to_string();
            }
            "--author" => {
                idx += 1;
                options.author = value(&args, idx, "--author")?.to_string();
            }
            arg if arg.starts_with("--author=") => {
                options.author = inline_value(arg, "--author")?.to_string();
            }
            "--recorded-at" => {
                idx += 1;
                options.recorded_at = value(&args, idx, "--recorded-at")?.to_string();
            }
            arg if arg.starts_with("--recorded-at=") => {
                options.recorded_at = inline_value(arg, "--recorded-at")?.to_string();
            }
            "--expires-at" => {
                idx += 1;
                options.expires_at = value(&args, idx, "--expires-at")?.to_string();
            }
            arg if arg.starts_with("--expires-at=") => {
                options.expires_at = inline_value(arg, "--expires-at")?.to_string();
            }
            "--summary" => {
                idx += 1;
                options.summary = Some(value(&args, idx, "--summary")?.to_string());
            }
            arg if arg.starts_with("--summary=") => {
                options.summary = Some(inline_value(arg, "--summary")?.to_string());
            }
            "--command" => {
                idx += 1;
                options.command = Some(value(&args, idx, "--command")?.to_string());
            }
            arg if arg.starts_with("--command=") => {
                options.command = Some(inline_value(arg, "--command")?.to_string());
            }
            "--limitation" => {
                idx += 1;
                options
                    .limitations
                    .push(value(&args, idx, "--limitation")?.to_string());
            }
            arg if arg.starts_with("--limitation=") => {
                options
                    .limitations
                    .push(inline_value(arg, "--limitation")?.to_string());
            }
            "--out" => {
                idx += 1;
                options.out = Some(PathBuf::from(value(&args, idx, "--out")?));
            }
            arg if arg.starts_with("--out=") => {
                options.out = Some(PathBuf::from(inline_value(arg, "--out")?));
            }
            value if value.starts_with('-') => {
                return Err(format!("unknown receipt template argument `{value}`"));
            }
            value => set_card_id(&mut id, value)?,
        }
        idx += 1;
    }
    options.card_id = id.ok_or_else(|| "missing card id".to_string())?;
    validate_required_cli_value(&options.tool, "--tool")?;
    validate_required_cli_value(&options.strength, "--strength")?;
    validate_required_cli_value(&options.author, "--author")?;
    validate_required_cli_value(&options.recorded_at, "--recorded-at")?;
    validate_required_cli_value(&options.expires_at, "--expires-at")?;
    Ok(options)
}

fn parse_receipt_audit(args: Vec<String>) -> Result<CheckOptions, String> {
    let mut options = parse_check(args)?;
    options.format = normalize_report_format(options.format, parse_receipt_audit_format)?;
    require_advisory_policy(&options, "receipt audit is advisory-only")?;
    Ok(options)
}

fn parse_receipt_validate(args: Vec<String>) -> Result<Command, String> {
    let mut root = PathBuf::from(".");
    let mut idx = 0usize;
    while idx < args.len() {
        match args[idx].as_str() {
            "--root" => {
                idx += 1;
                root = parse_path_value(&args, idx, "--root")?;
            }
            arg if arg.starts_with("--root=") => {
                root = parse_inline_path_value(arg, "--root")?;
            }
            other => return Err(format!("unknown receipt validate argument `{other}`")),
        }
        idx += 1;
    }
    Ok(Command::ReceiptValidate { root })
}

fn parse_saved_output_receipt(
    args: Vec<String>,
    command_name: &str,
    allow_tool: bool,
) -> Result<SavedOutputReceiptOptions, String> {
    let mut options = SavedOutputReceiptOptions::default();
    let mut id: Option<String> = None;
    let mut idx = 0usize;
    while idx < args.len() {
        let arg = args[idx].as_str();
        if parse_saved_output_receipt_tool(&args, &mut idx, arg, &mut options, allow_tool)? {
            idx += 1;
            continue;
        }
        if parse_saved_output_receipt_common(&args, &mut idx, arg, &mut options)? {
            idx += 1;
            continue;
        }
        match arg {
            value if value.starts_with('-') => {
                return Err(format!("unknown receipt {command_name} argument `{value}`"));
            }
            value => set_card_id(&mut id, value)?,
        }
        idx += 1;
    }
    options.card_id = id.ok_or_else(|| "missing card id".to_string())?;
    if options.log.as_os_str().is_empty() {
        return Err("missing value for --log".to_string());
    }
    validate_required_cli_value(&options.author, "--author")?;
    validate_required_cli_value(&options.recorded_at, "--recorded-at")?;
    validate_required_cli_value(&options.expires_at, "--expires-at")?;
    validate_required_cli_value(&options.command, "--command")?;
    if allow_tool && options.tool.as_deref().unwrap_or("").trim().is_empty() {
        return Err("missing value for --tool".to_string());
    }
    Ok(options)
}

fn parse_saved_output_receipt_tool(
    args: &[String],
    idx: &mut usize,
    arg: &str,
    options: &mut SavedOutputReceiptOptions,
    allow_tool: bool,
) -> Result<bool, String> {
    match arg {
        "--tool" if allow_tool => {
            *idx += 1;
            options.tool = Some(value(args, *idx, "--tool")?.to_string());
            Ok(true)
        }
        _ if allow_tool && arg.starts_with("--tool=") => {
            options.tool = Some(inline_value(arg, "--tool")?.to_string());
            Ok(true)
        }
        _ => Ok(false),
    }
}

fn parse_saved_output_receipt_common(
    args: &[String],
    idx: &mut usize,
    arg: &str,
    options: &mut SavedOutputReceiptOptions,
) -> Result<bool, String> {
    match arg {
        "--log" => {
            *idx += 1;
            options.log = PathBuf::from(value(args, *idx, "--log")?);
            Ok(true)
        }
        _ if arg.starts_with("--log=") => {
            options.log = PathBuf::from(inline_value(arg, "--log")?);
            Ok(true)
        }
        "--author" => {
            *idx += 1;
            options.author = value(args, *idx, "--author")?.to_string();
            Ok(true)
        }
        _ if arg.starts_with("--author=") => {
            options.author = inline_value(arg, "--author")?.to_string();
            Ok(true)
        }
        "--recorded-at" => {
            *idx += 1;
            options.recorded_at = value(args, *idx, "--recorded-at")?.to_string();
            Ok(true)
        }
        _ if arg.starts_with("--recorded-at=") => {
            options.recorded_at = inline_value(arg, "--recorded-at")?.to_string();
            Ok(true)
        }
        "--expires-at" => {
            *idx += 1;
            options.expires_at = value(args, *idx, "--expires-at")?.to_string();
            Ok(true)
        }
        _ if arg.starts_with("--expires-at=") => {
            options.expires_at = inline_value(arg, "--expires-at")?.to_string();
            Ok(true)
        }
        "--command" => {
            *idx += 1;
            options.command = value(args, *idx, "--command")?.to_string();
            Ok(true)
        }
        _ if arg.starts_with("--command=") => {
            options.command = inline_value(arg, "--command")?.to_string();
            Ok(true)
        }
        "--limitation" => {
            *idx += 1;
            options
                .limitations
                .push(value(args, *idx, "--limitation")?.to_string());
            Ok(true)
        }
        _ if arg.starts_with("--limitation=") => {
            options
                .limitations
                .push(inline_value(arg, "--limitation")?.to_string());
            Ok(true)
        }
        "--out" => {
            *idx += 1;
            options.out = Some(PathBuf::from(value(args, *idx, "--out")?));
            Ok(true)
        }
        _ if arg.starts_with("--out=") => {
            options.out = Some(PathBuf::from(inline_value(arg, "--out")?));
            Ok(true)
        }
        _ => Ok(false),
    }
}