use crate::{Baseline, CheckArgs, RiskScore, ScanResult, Summary};
use colored::Colorize;
use rustc_hash::FxHashMap;
use std::collections::HashSet;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::ExitCode;
pub fn handle_baseline(paths: &[PathBuf]) -> ExitCode {
for path in paths {
match Baseline::from_directory(path) {
Ok(baseline) => {
if let Err(e) = baseline.save(path) {
eprintln!("Failed to save baseline for {}: {}", path.display(), e);
return ExitCode::from(2);
}
println!(
"Baseline created for {} ({} files)",
path.display(),
baseline.file_count
);
}
Err(e) => {
eprintln!("Failed to create baseline for {}: {}", path.display(), e);
return ExitCode::from(2);
}
}
}
println!("\nBaseline saved. Use --check-drift to detect changes.");
ExitCode::SUCCESS
}
pub fn handle_save_baseline(paths: &[PathBuf], baseline_path: &Path) -> ExitCode {
let single_path = paths.len() == 1;
let mut combined_hashes = FxHashMap::default();
for path in paths {
match Baseline::from_directory(path) {
Ok(baseline) => {
for (file_path, hash) in baseline.file_hashes {
let key = if single_path {
file_path
} else {
format!("{}:{}", path.display(), file_path)
};
combined_hashes.insert(key, hash);
}
}
Err(e) => {
eprintln!("Failed to create baseline for {}: {}", path.display(), e);
return ExitCode::from(2);
}
}
}
let baseline = Baseline {
version: env!("CARGO_PKG_VERSION").to_string(),
created_at: chrono::Utc::now().to_rfc3339(),
file_count: combined_hashes.len(),
file_hashes: combined_hashes,
};
if let Err(e) = baseline.save_to_file(baseline_path) {
eprintln!(
"Failed to save baseline to {}: {}",
baseline_path.display(),
e
);
return ExitCode::from(2);
}
println!(
"Baseline saved to {} ({} files)",
baseline_path.display(),
baseline.file_count
);
ExitCode::SUCCESS
}
pub fn handle_check_drift(args: &CheckArgs) -> ExitCode {
let mut has_any_drift = false;
if let Some(ref baseline_file) = args.baseline_file {
let baseline = match Baseline::load_from_file(baseline_file) {
Ok(b) => b,
Err(e) => {
eprintln!(
"Failed to load baseline from {}: {}",
baseline_file.display(),
e
);
return ExitCode::from(2);
}
};
for path in &args.paths {
match baseline.check_drift(path) {
Ok(report) => {
println!("Checking drift for: {}\n", path.display());
println!("{}", report.format_terminal());
if report.has_drift {
has_any_drift = true;
}
}
Err(e) => {
eprintln!("Failed to check drift for {}: {}", path.display(), e);
return ExitCode::from(2);
}
}
}
} else {
for path in &args.paths {
match Baseline::load(path) {
Ok(baseline) => match baseline.check_drift(path) {
Ok(report) => {
println!("Checking drift for: {}\n", path.display());
println!("{}", report.format_terminal());
if report.has_drift {
has_any_drift = true;
}
}
Err(e) => {
eprintln!("Failed to check drift for {}: {}", path.display(), e);
return ExitCode::from(2);
}
},
Err(e) => {
eprintln!(
"No baseline found for {}. Run with --baseline first.\nError: {}",
path.display(),
e
);
return ExitCode::from(2);
}
}
}
}
if has_any_drift {
ExitCode::from(1)
} else {
ExitCode::SUCCESS
}
}
pub fn filter_against_baseline(mut result: ScanResult, baseline_path: &Path) -> ScanResult {
let baseline_result = match fs::read_to_string(baseline_path) {
Ok(content) => match serde_json::from_str::<ScanResult>(&content) {
Ok(r) => r,
Err(e) => {
eprintln!(
"{} Failed to parse baseline file: {}",
"Warning:".yellow(),
e
);
return result;
}
},
Err(e) => {
eprintln!(
"{} Failed to read baseline file: {}",
"Warning:".yellow(),
e
);
return result;
}
};
let baseline_signatures: HashSet<String> = baseline_result
.findings
.iter()
.map(|f| format!("{}:{}:{}", f.id, f.location.file, f.location.line))
.collect();
let original_count = result.findings.len();
result.findings.retain(|f| {
let sig = format!("{}:{}:{}", f.id, f.location.file, f.location.line);
!baseline_signatures.contains(&sig)
});
let filtered_count = original_count - result.findings.len();
if filtered_count > 0 {
eprintln!(
"{} {} findings filtered (already in baseline)",
"Info:".cyan(),
filtered_count
);
}
result.summary = Summary::from_findings_with_rule_severity(&result.findings);
if let Some(ref mut risk_score) = result.risk_score {
*risk_score = RiskScore::from_findings(&result.findings);
}
result
}