use super::*;
pub(crate) fn write_baseline(path: &Path, findings: &[Finding]) -> Result<(), String> {
let entries: Vec<BaselineEntry> = findings
.iter()
.map(|finding| BaselineEntry {
fingerprint: finding.fingerprint.clone(),
rule_id: finding.rule_id.clone(),
file_path: finding.file_path.clone(),
line: finding.line,
symbol: finding.symbol.clone(),
message: finding.message.clone(),
})
.collect();
let value = json!({
"schemaVersion": "gruff.baseline.v1",
"generatedAt": Utc::now().to_rfc3339(),
"entries": entries,
});
fs::write(
path,
serde_json::to_string_pretty(&value).expect("baseline serializes"),
)
.map_err(|error| format!("unable to write baseline {}: {error}", path.display()))
}
pub(crate) fn apply_baseline(path: &Path, findings: &mut Vec<Finding>) -> Result<(), String> {
let raw = fs::read_to_string(path)
.map_err(|error| format!("unable to read baseline {}: {error}", path.display()))?;
let data: BaselineData = serde_json::from_str(&raw)
.map_err(|error| format!("invalid baseline {}: {error}", path.display()))?;
if data.schema_version.as_deref() != Some("gruff.baseline.v1") {
return Err(format!("unsupported baseline schema in {}", path.display()));
}
let keys: BTreeSet<(String, String, String)> = data
.entries
.into_iter()
.map(|entry| (entry.fingerprint, entry.rule_id, entry.file_path))
.collect();
findings.retain(|finding| {
!keys.iter().any(|(fingerprint, rule_id, file_path)| {
fingerprint == &finding.fingerprint
&& rule_id == &finding.rule_id
&& file_path == &finding.file_path
})
});
Ok(())
}
pub(crate) fn record_history(
project_root: &Path,
history_file: &Path,
findings: &[Finding],
diagnostics: &mut Vec<RunDiagnostic>,
) {
let path = absolutize(project_root, history_file);
let mut entries = fs::read_to_string(&path)
.ok()
.and_then(|raw| serde_json::from_str::<Vec<Value>>(&raw).ok())
.unwrap_or_default();
entries.push(json!({
"recordedAt": Utc::now().to_rfc3339(),
"findings": findings.len(),
"score": score_report(findings).composite,
}));
if entries.len() > 100 {
entries = entries.split_off(entries.len() - 100);
}
if let Err(error) = fs::write(
&path,
serde_json::to_string_pretty(&entries).expect("history serializes"),
) {
diagnostics.push(RunDiagnostic {
diagnostic_type: "history-error".to_string(),
message: format!("Unable to write history file: {error}"),
file_path: Some(display_path(project_root, &path)),
line: None,
});
}
}