use std::fs;
use std::io::IsTerminal;
use std::path::PathBuf;
use clap::{Parser, ValueEnum};
use fleetreach_core::FleetReport;
use fleetreach_report as report;
use crate::cli::{fail, SeverityArg};
#[derive(Parser)]
pub struct DiffArgs {
baseline: PathBuf,
current: PathBuf,
#[arg(short, long, value_enum, default_value_t = DiffFormat::Table)]
format: DiffFormat,
#[arg(
long,
value_enum,
default_value_t = SeverityArg::Low,
help = "fail if any NEW vuln is at/above this severity (Unknown always counts)"
)]
fail_on: SeverityArg,
#[arg(long, help = "also fail if a NEW supply-chain warning appeared")]
fail_on_warnings: bool,
#[arg(long, help = "always exit 0 (report-only; never gate on new findings)")]
exit_zero: bool,
}
#[derive(Clone, Copy, ValueEnum)]
enum DiffFormat {
Table,
Json,
}
pub fn run_diff(args: DiffArgs) -> u8 {
let baseline = match read_report(&args.baseline) {
Ok(r) => r,
Err(e) => return fail(&e),
};
let current = match read_report(&args.current) {
Ok(r) => r,
Err(e) => return fail(&e),
};
let diff = report::diff_reports(&baseline, ¤t);
match args.format {
DiffFormat::Json => match report::to_diff_json(&diff) {
Ok(json) => println!("{json}"),
Err(e) => return fail(&format!("serializing diff: {e}")),
},
DiffFormat::Table => {
println!(
"{}",
report::to_diff_table(&diff, std::io::stdout().is_terminal())
);
}
}
if args.exit_zero {
return 0;
}
u8::from(diff.regressions(args.fail_on.into(), args.fail_on_warnings) > 0)
}
fn read_report(path: &PathBuf) -> Result<FleetReport, String> {
let text = fs::read_to_string(path)
.map_err(|e| format!("reading report `{}`: {e}", path.display()))?;
serde_json::from_str(&text).map_err(|e| format!("parsing report `{}`: {e}", path.display()))
}