use std::path::PathBuf;
use std::process::ExitCode;
use clap::{Parser, ValueEnum};
use fhir_diff::{Filter, Pool, Severity, diff, load, report};
#[derive(Parser)]
#[command(name = "fhir-diff", version)]
struct Cli {
old: String,
new: String,
#[arg(long = "profile", value_name = "URL|NAME")]
profiles: Vec<String>,
#[arg(long, value_enum, default_value_t = Format::Text)]
format: Format,
#[arg(long, short)]
output: Option<PathBuf>,
#[arg(long, value_enum, default_value_t = Level::Info)]
min_severity: Level,
#[arg(long, value_enum, default_value_t = FailOn::Never)]
fail_on: FailOn,
}
#[derive(Clone, Copy, ValueEnum)]
enum Format {
Text,
Markdown,
Json,
Sarif,
}
#[derive(Clone, Copy, ValueEnum)]
enum Level {
Info,
Warning,
Breaking,
}
impl From<Level> for Severity {
fn from(level: Level) -> Self {
match level {
Level::Info => Severity::Info,
Level::Warning => Severity::Warning,
Level::Breaking => Severity::Breaking,
}
}
}
#[derive(Clone, Copy, ValueEnum)]
enum FailOn {
Never,
Warning,
Breaking,
}
fn main() -> ExitCode {
let cli = Cli::parse();
match run(&cli) {
Ok(code) => code,
Err(error) => {
eprintln!("error: {error}");
ExitCode::from(2)
}
}
}
fn run(cli: &Cli) -> Result<ExitCode, String> {
let old = load(&cli.old)?;
let new = load(&cli.new)?;
let (old_pool, new_pool) = Pool::pair(&old, &new);
let filter = Filter {
profiles: cli.profiles.clone(),
};
let report = diff(
&old.package.id(),
&old_pool,
&new.package.id(),
&new_pool,
&filter,
);
let min = Severity::from(cli.min_severity);
let text = match cli.format {
Format::Text => report::text(&report, min),
Format::Markdown => report::markdown(&report, min),
Format::Json => report::json(&report),
Format::Sarif => report::sarif(&report, min),
};
match &cli.output {
Some(path) => std::fs::write(path, text).map_err(|e| format!("{}: {e}", path.display()))?,
None => println!("{text}"),
}
let threshold = match cli.fail_on {
FailOn::Never => None,
FailOn::Warning => Some(Severity::Warning),
FailOn::Breaking => Some(Severity::Breaking),
};
Ok(match (threshold, report.worst()) {
(Some(threshold), Some(worst)) if worst >= threshold => ExitCode::from(1),
_ => ExitCode::SUCCESS,
})
}