use crate::finding::Finding;
use crate::merge::ConflictEntry;
use crate::requirement::EngineRequirement;
use crate::types::{EngineOutput, Provenance};
#[expect(
clippy::module_name_repetitions,
reason = "FileEngine is the canonical trait name; renaming it loses the connection to the file-engines abstraction in plans and call sites."
)]
pub trait FileEngine<ResolvedRequirements> {
fn reconcile(
current_bytes: Option<&[u8]>,
resolved_requirements: &ResolvedRequirements,
) -> EngineOutput;
}
pub trait Engine {
fn id(&self) -> &'static str;
#[allow(
clippy::type_complexity,
reason = "`&[(Provenance, Box<dyn EngineRequirement>)]` is the erased multi-requirement input the registry dispatches; a type alias would hide the contract."
)]
fn reconcile(
&self,
current_bytes: Option<&[u8]>,
reqs: &[(Provenance, Box<dyn EngineRequirement>)],
) -> EngineOutput;
}
#[allow(
clippy::type_complexity,
reason = "The erased multi-requirement input and merge/reconcile closures are the public dispatch shape."
)]
pub fn merged_reconcile<Requirements, ResolvedRequirements, Merge, Reconcile>(
current_bytes: Option<&[u8]>,
reqs: &[(Provenance, Box<dyn EngineRequirement>)],
merge: Merge,
reconcile_one: Reconcile,
) -> EngineOutput
where
Requirements: EngineRequirement + Clone,
Merge: Fn(Vec<(Provenance, Requirements)>) -> (ResolvedRequirements, Vec<ConflictEntry>),
Reconcile: Fn(Option<&[u8]>, &ResolvedRequirements) -> EngineOutput,
{
let typed: Vec<(Provenance, Requirements)> = reqs
.iter()
.filter_map(|(prov, r)| {
r.as_any()
.downcast_ref::<Requirements>()
.map(|req| (prov.clone(), req.clone()))
})
.collect();
if typed.is_empty() {
return EngineOutput {
expected_bytes: current_bytes.map(<[u8]>::to_vec).unwrap_or_default(),
findings: Vec::new(),
};
}
let (merged, conflicts) = merge(typed);
if !conflicts.is_empty() {
let findings = conflicts
.into_iter()
.map(|entry| Finding::ConflictingRequirements {
key: entry.key,
contributors: entry
.contributors
.into_iter()
.map(|(prov, value)| (prov.policy, value))
.collect(),
reason: entry.reason,
})
.collect();
return EngineOutput {
expected_bytes: current_bytes.map(<[u8]>::to_vec).unwrap_or_default(),
findings,
};
}
reconcile_one(current_bytes, &merged)
}