mod actions;
mod format;
mod plan;
use std::path::{Path, PathBuf};
use serde::Serialize;
use crate::catalog::CatalogError;
use crate::verify::TetVerifyReport;
pub use format::{format_plan_json, format_plan_text, format_repair_json, format_repair_text};
pub use plan::{RepairAction, RepairPlan, repair_plan_from_verify};
#[derive(Debug, Clone, Default)]
pub struct RepairOptions {
pub dry_run: bool,
pub apply: Vec<String>,
pub plan_codes: Vec<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct RepairActionResult {
pub code: String,
pub applied: bool,
pub dry_run: bool,
pub message: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct TetRepairReport {
pub path: PathBuf,
pub dry_run: bool,
pub actions: Vec<RepairActionResult>,
pub verify_after_ok: Option<bool>,
}
#[must_use]
pub fn repair_command_line(path: &Path, code: &str, dry_run: bool) -> String {
let p = path.display();
if dry_run {
format!("tet repair {p} --apply {code} --dry-run")
} else {
format!("tet repair {p} --apply {code}")
}
}
#[must_use]
pub fn is_repairable_code(code: &str) -> bool {
matches!(code, "footer_invalid")
}
#[must_use]
pub fn repair_command_for_code(path: &Path, code: &str) -> Option<String> {
if is_repairable_code(code) {
Some(repair_command_line(path, code, true))
} else {
None
}
}
pub fn enrich_verify_recommendations(path: &Path, report: &mut TetVerifyReport) {
for rec in &mut report.recommendations {
let Some(cmd) = repair_command_for_code(path, &rec.code) else {
continue;
};
match &mut rec.fix {
Some(fix) => fix.command = Some(cmd),
None => {
rec.fix = Some(crate::verify::VerifyFixHint {
summary: "Run repair (dry-run first).".to_owned(),
command: Some(cmd),
});
}
}
}
}
#[must_use]
pub fn repair_plan(path: &Path, verify: &TetVerifyReport) -> RepairPlan {
repair_plan_from_verify(path, verify)
}
pub fn repair_tet_file(
path: &Path,
options: &RepairOptions,
) -> Result<TetRepairReport, CatalogError> {
let verify = crate::verify::verify_tet_file(path)?;
let plan = repair_plan_from_verify(path, &verify);
let mut actions = Vec::new();
let codes: Vec<String> = if options.apply.is_empty() {
if options.plan_codes.is_empty() {
plan.actions
.iter()
.filter(|a| a.repairable)
.map(|a| a.code.clone())
.collect()
} else {
options.plan_codes.clone()
}
} else {
options.apply.clone()
};
for code in codes {
let action = plan
.actions
.iter()
.find(|a| a.code == code)
.cloned()
.unwrap_or(RepairAction {
code: code.clone(),
repairable: is_repairable_code(&code),
summary: "unknown repair code".to_owned(),
});
let result = if action.repairable {
actions::apply_repair_code(path, &code, options.dry_run)?
} else {
RepairActionResult {
code: code.clone(),
applied: false,
dry_run: options.dry_run,
message: "no in-place repair for this code; re-convert or rewrite required"
.to_owned(),
}
};
actions.push(result);
}
let verify_after_ok = if options.dry_run {
None
} else {
Some(crate::verify::verify_tet_file(path)?.ok)
};
Ok(TetRepairReport {
path: path.to_path_buf(),
dry_run: options.dry_run,
actions,
verify_after_ok,
})
}
pub fn repair_from_verify_report(
path: &Path,
verify: &TetVerifyReport,
dry_run: bool,
) -> Result<TetRepairReport, CatalogError> {
let plan = repair_plan_from_verify(path, verify);
let mut actions = Vec::new();
for action in plan.actions.iter().filter(|a| a.repairable) {
actions.push(actions::apply_repair_code(path, &action.code, dry_run)?);
}
let verify_after_ok = if dry_run {
None
} else {
Some(crate::verify::verify_tet_file(path)?.ok)
};
Ok(TetRepairReport {
path: path.to_path_buf(),
dry_run,
actions,
verify_after_ok,
})
}