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, ItemRequirements, Provenance, Resolve, ResolvedItemRequirements,
5    ResolvedRequirement, ScalarAssertion, resolve_items,
6};
7
8use super::{ResolvedTextFileRequirements, TextFileRequirements, TextSnippet};
9
10/// Provenance-tagged text requirements routed to one text-engine target.
11type TextRequirementInput = Vec<(Provenance, TextFileRequirements)>;
12/// Resolved text requirements plus merge conflicts found during resolution.
13type TextMergeOutput = (ResolvedTextFileRequirements, Vec<ConflictEntry>);
14
15impl TextFileRequirements {
16    #[must_use]
17    #[allow(
18        clippy::needless_pass_by_value,
19        reason = "merged_reconcile passes owned typed requirements to every engine merge function."
20    )]
21    pub fn merge(reqs: TextRequirementInput) -> TextMergeOutput {
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 required_snippets = resolve_snippets(
30            "required_snippets",
31            &reqs,
32            |req| req.required_snippets.clone(),
33            &mut conflicts,
34        );
35        (
36            ResolvedTextFileRequirements {
37                exact_contents,
38                required_snippets,
39            },
40            conflicts,
41        )
42    }
43}
44
45/// Resolve optional scalar text assertions while preserving provenance.
46fn resolve_optional_scalar<T>(
47    key: &str,
48    items: &[(Provenance, TextFileRequirements)],
49    get: impl Fn(&TextFileRequirements) -> Option<ScalarAssertion<T>>,
50    conflicts: &mut Vec<ConflictEntry>,
51) -> Option<ResolvedRequirement<ScalarAssertion<T>, ScalarAssertion<T>>>
52where
53    T: aqc_file_engine_core::ScalarValue,
54{
55    let assertions = items
56        .iter()
57        .filter_map(|(prov, req)| get(req).map(|assertion| (prov.clone(), assertion)))
58        .collect::<Vec<_>>();
59    if assertions.is_empty() {
60        None
61    } else {
62        ScalarAssertion::<T>::resolve(key, assertions, conflicts)
63    }
64}
65
66/// Resolve required text snippets through the shared item merge primitive.
67fn resolve_snippets(
68    key: &str,
69    items: &[(Provenance, TextFileRequirements)],
70    get: impl Fn(&TextFileRequirements) -> ItemRequirements<TextSnippet>,
71    conflicts: &mut Vec<ConflictEntry>,
72) -> ResolvedItemRequirements<TextSnippet> {
73    resolve_items(
74        key,
75        items
76            .iter()
77            .map(|(prov, req)| (prov.clone(), get(req)))
78            .collect(),
79        conflicts,
80    )
81}