use std::collections::{BTreeMap, BTreeSet};
use serde_json::Value as JsonValue;
use crate::expression::{Expression, empty_context, merge_context};
use crate::model::{PackageInspectReport, VariableInspectReport};
pub(super) struct ContextFactory {
contexts: Vec<JsonValue>,
}
impl ContextFactory {
pub(super) fn new(report: &PackageInspectReport) -> Self {
let qualifiers: BTreeMap<String, Expression> = report
.qualifiers
.iter()
.filter_map(|qualifier| {
let source = qualifier.when.as_deref()?;
let expression = Expression::parse(source).ok()?;
Some((qualifier.id.clone(), expression))
})
.collect();
let mut contexts = Vec::new();
for evaluation_context in &report.evaluation_contexts {
for sample in &evaluation_context.samples {
contexts.push(sample.value.clone());
}
}
for qualifier in &report.qualifiers {
for want in [true, false] {
if let Some(context) = synthesize_qualifier(&qualifiers, &qualifier.id, want) {
contexts.push(context);
}
}
}
for variable in &report.variables {
push_variable_contexts(&qualifiers, variable, &mut contexts);
}
Self { contexts }
}
pub(super) fn candidate_contexts(&self) -> &[JsonValue] {
&self.contexts
}
}
fn push_variable_contexts(
qualifiers: &BTreeMap<String, Expression>,
variable: &VariableInspectReport,
out: &mut Vec<JsonValue>,
) {
let rules: Vec<Option<Expression>> = variable
.resolve
.rules
.iter()
.map(|rule| {
rule.when
.as_deref()
.and_then(|source| Expression::parse(source).ok())
})
.collect();
if let Some(context) = merge_all(rules.iter().map(|rule| synth(qualifiers, rule, false))) {
out.push(context);
}
for index in 0..rules.len() {
if let Some(context) = synth(qualifiers, &rules[index], true) {
out.push(context);
}
let earlier = rules[..index]
.iter()
.map(|rule| synth(qualifiers, rule, false));
let this = std::iter::once(synth(qualifiers, &rules[index], true));
if let Some(context) = merge_all(earlier.chain(this)) {
out.push(context);
}
}
}
fn synth(
qualifiers: &BTreeMap<String, Expression>,
rule: &Option<Expression>,
want: bool,
) -> Option<JsonValue> {
rule.as_ref()?.synthesize_context(want, &mut |id, want| {
synthesize_qualifier(qualifiers, id, want)
})
}
fn merge_all(contexts: impl IntoIterator<Item = Option<JsonValue>>) -> Option<JsonValue> {
let mut merged = empty_context();
for context in contexts {
merge_context(&mut merged, context?)?;
}
Some(merged)
}
fn synthesize_qualifier(
qualifiers: &BTreeMap<String, Expression>,
id: &str,
want: bool,
) -> Option<JsonValue> {
synthesize_qualifier_inner(qualifiers, id, want, &mut BTreeSet::new())
}
fn synthesize_qualifier_inner(
qualifiers: &BTreeMap<String, Expression>,
id: &str,
want: bool,
stack: &mut BTreeSet<String>,
) -> Option<JsonValue> {
if !stack.insert(id.to_owned()) {
return None;
}
let result = qualifiers.get(id).and_then(|expression| {
expression.synthesize_context(want, &mut |nested, nested_want| {
synthesize_qualifier_inner(qualifiers, nested, nested_want, stack)
})
});
stack.remove(id);
result
}