#[cfg(test)]
mod test_passes;
use crate::{fix, json, lint, schema, string, warning, Versioned as _};
const MAX_PASSES: usize = 8;
pub(crate) fn tariff(
tariff: &crate::tariff::Versioned<'_>,
unexpected: UnexpectedFields,
) -> Result<Fixed, fix::Error> {
fix_until_no_edits(
tariff.as_json_str(),
tariff.version(),
unexpected,
MAX_PASSES,
)
}
#[derive(Debug)]
pub struct Fixed {
pub source: String,
pub edits: usize,
pub passes: usize,
pub outcome: Outcome,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Outcome {
AllFixesApplied,
FixLimit,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum UnexpectedFields {
Remove,
Keep,
}
fn fix_until_no_edits(
source: &str,
version: crate::Version,
unexpected: UnexpectedFields,
max_passes: usize,
) -> Result<Fixed, fix::Error> {
let mut source = source.to_owned();
let mut edits: usize = 0;
let mut passes: usize = 0;
let outcome = loop {
let len = string::ReasonableLen::new(&source).map_err(|_e| fix::Error::OutputTooLarge)?;
let doc = json::parse(len).map_err(fix::Error::Internal)?;
let (built, schema_warnings) = crate::tariff::from_json(doc, version).into_parts();
let edits_this_pass = pass_edits(&built, schema_warnings, unexpected)?;
if edits_this_pass.is_empty() {
break Outcome::AllFixesApplied;
}
if passes == max_passes {
break Outcome::FixLimit;
}
let next = fix::apply(built.as_doc(), &edits_this_pass)?;
edits = edits.saturating_add(edits_this_pass.len());
passes = passes.saturating_add(1);
source = next;
};
Ok(Fixed {
source,
edits,
passes,
outcome,
})
}
fn pass_edits(
tariff: &crate::tariff::Versioned<'_>,
mut schema_warnings: warning::Set<schema::Warning>,
unexpected: UnexpectedFields,
) -> Result<Vec<fix::Edit>, fix::Error> {
if unexpected == UnexpectedFields::Keep {
schema_warnings.remove_unexpected_fields();
}
let lint::tariff::Report {
warnings: lint_warnings,
} = lint::tariff(tariff);
let doc = tariff.as_doc();
let mut edits = fix::edits(doc, &schema_warnings)?;
let lint_edits = fix::edits(doc, &lint_warnings)?;
edits.extend(lint_edits);
Ok(edits)
}