Skip to main content

aqc_text_engine_core/requirement/
merge.rs

1//! Text byte-stream requirement merge logic.
2
3use aqc_file_engine_core::{
4    ConflictEntry, Provenance, Resolve, ResolvedRequirement, ScalarAssertion, resolve_items,
5};
6
7use super::{ResolvedTextFileRequirements, TextFileRequirements};
8
9impl TextFileRequirements {
10    /// Merges all text-file requirements into one resolved requirement set.
11    ///
12    /// # Errors
13    ///
14    /// Returns every conflict when the input requirements cannot be composed.
15    #[allow(
16        clippy::needless_pass_by_value,
17        reason = "merged_reconcile passes owned typed requirements to every engine merge function."
18    )]
19    pub fn merge(
20        reqs: Vec<(Provenance, Self)>,
21    ) -> Result<ResolvedTextFileRequirements, Vec<ConflictEntry>> {
22        let mut conflicts = Vec::new();
23        let exact_contents = resolve_optional_scalar(
24            "exact_contents",
25            &reqs,
26            |req| req.exact_contents.clone(),
27            &mut conflicts,
28        );
29        let contents = resolve_items(
30            "contents",
31            reqs.iter()
32                .map(|(prov, req)| (prov.clone(), req.contents.clone()))
33                .collect(),
34            &mut conflicts,
35        );
36        let resolved = ResolvedTextFileRequirements {
37            exact_contents,
38            contents,
39        };
40        if conflicts.is_empty() {
41            Ok(resolved)
42        } else {
43            Err(conflicts)
44        }
45    }
46}
47
48/// Resolve one optional scalar field while preserving source provenance.
49fn resolve_optional_scalar<T>(
50    key: &str,
51    items: &[(Provenance, TextFileRequirements)],
52    get: impl Fn(&TextFileRequirements) -> Option<ScalarAssertion<T>>,
53    conflicts: &mut Vec<ConflictEntry>,
54) -> Option<ResolvedRequirement<ScalarAssertion<T>, ScalarAssertion<T>>>
55where
56    T: aqc_file_engine_core::ScalarValue,
57{
58    let assertions = items
59        .iter()
60        .filter_map(|(prov, req)| get(req).map(|assertion| (prov.clone(), assertion)))
61        .collect::<Vec<_>>();
62    if assertions.is_empty() {
63        None
64    } else {
65        ScalarAssertion::<T>::resolve(key, assertions, conflicts)
66    }
67}