aqc_file_engine_core/
engine.rs1use crate::finding::Finding;
5use crate::merge::ConflictEntry;
6use crate::requirement::EngineRequirement;
7use crate::types::{EngineOutput, Provenance};
8
9#[expect(
17 clippy::module_name_repetitions,
18 reason = "FileEngine is the canonical trait name; renaming it loses the connection to the file-engines abstraction in plans and call sites."
19)]
20pub trait FileEngine<ResolvedRequirements> {
21 fn reconcile(
24 current_bytes: Option<&[u8]>,
25 resolved_requirements: &ResolvedRequirements,
26 ) -> EngineOutput;
27}
28
29pub trait Engine {
34 fn id(&self) -> &'static str;
36 #[allow(
39 clippy::type_complexity,
40 reason = "`&[(Provenance, Box<dyn EngineRequirement>)]` is the erased multi-requirement input the registry dispatches; a type alias would hide the contract."
41 )]
42 fn reconcile(
43 &self,
44 current_bytes: Option<&[u8]>,
45 reqs: &[(Provenance, Box<dyn EngineRequirement>)],
46 ) -> EngineOutput;
47}
48
49#[allow(
55 clippy::type_complexity,
56 reason = "The erased multi-requirement input and merge/reconcile closures are the public dispatch shape."
57)]
58pub fn merged_reconcile<Requirements, ResolvedRequirements, Merge, Reconcile>(
59 current_bytes: Option<&[u8]>,
60 reqs: &[(Provenance, Box<dyn EngineRequirement>)],
61 merge: Merge,
62 reconcile_one: Reconcile,
63) -> EngineOutput
64where
65 Requirements: EngineRequirement + Clone,
66 Merge: Fn(Vec<(Provenance, Requirements)>) -> (ResolvedRequirements, Vec<ConflictEntry>),
67 Reconcile: Fn(Option<&[u8]>, &ResolvedRequirements) -> EngineOutput,
68{
69 let typed: Vec<(Provenance, Requirements)> = reqs
70 .iter()
71 .filter_map(|(prov, r)| {
72 r.as_any()
73 .downcast_ref::<Requirements>()
74 .map(|req| (prov.clone(), req.clone()))
75 })
76 .collect();
77 if typed.is_empty() {
78 return EngineOutput {
79 expected_bytes: current_bytes.map(<[u8]>::to_vec).unwrap_or_default(),
80 findings: Vec::new(),
81 };
82 }
83 let (merged, conflicts) = merge(typed);
84 let mut out = reconcile_one(current_bytes, &merged);
85 for entry in conflicts {
86 let finding = Finding::ConflictingRequirements {
87 key: entry.key,
88 contributors: entry
89 .contributors
90 .into_iter()
91 .map(|(prov, value)| (prov.policy, value))
92 .collect(),
93 reason: entry.reason,
94 };
95 out.findings.push(finding);
96 }
97 out
98}