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 {
samples: Vec<JsonValue>,
synthesized: BTreeMap<String, Vec<JsonValue>>,
}
struct Conditions {
variables: BTreeMap<String, Vec<Expression>>,
}
impl ContextFactory {
pub(super) fn new(report: &PackageInspectReport) -> Self {
let conditions = Conditions::new(report);
let samples = report
.evaluation_contexts
.iter()
.flat_map(|evaluation_context| {
evaluation_context
.samples
.iter()
.map(|sample| sample.value.clone())
})
.collect();
let synthesized = report
.variables
.iter()
.map(|variable| {
let mut contexts = Vec::new();
push_variable_contexts(&conditions, variable, &mut contexts);
(variable.id.clone(), contexts)
})
.collect();
Self {
samples,
synthesized,
}
}
pub(super) fn candidates_for(&self, variable_id: &str) -> impl Iterator<Item = &JsonValue> {
self.synthesized
.get(variable_id)
.map(Vec::as_slice)
.unwrap_or_default()
.iter()
.chain(self.samples.iter())
}
}
impl Conditions {
fn new(report: &PackageInspectReport) -> Self {
let variables: BTreeMap<String, Vec<Expression>> = report
.variables
.iter()
.filter_map(|variable| {
if variable.resolve.default_value != Some(JsonValue::Bool(false)) {
return None;
}
let rules: Option<Vec<Expression>> = variable
.resolve
.rules
.iter()
.map(|rule| {
(rule.value == Some(JsonValue::Bool(true)))
.then_some(rule.when.as_deref())
.flatten()
.and_then(|source| Expression::parse(source).ok())
})
.collect();
Some((variable.id.clone(), rules?))
})
.collect();
Self { variables }
}
fn synthesize(&self, id: &str, want: bool, stack: &mut BTreeSet<String>) -> Option<JsonValue> {
if !stack.insert(id.to_owned()) {
return None;
}
let result = self.synthesize_condition_variable(id, want, stack);
stack.remove(id);
result
}
fn synthesize_condition_variable(
&self,
id: &str,
want: bool,
stack: &mut BTreeSet<String>,
) -> Option<JsonValue> {
let rules = self.variables.get(id)?;
let mut synthesize = |rule: &Expression, want: bool| -> Option<JsonValue> {
rule.synthesize_context(
want,
&mut |nested, nested_want| self.synthesize(nested, nested_want, stack),
&mut |_| None,
)
};
if want {
rules.iter().find_map(|rule| synthesize(rule, true))
} else {
let mut merged = empty_context();
for rule in rules {
merge_context(&mut merged, synthesize(rule, false)?)?;
}
Some(merged)
}
}
}
fn push_variable_contexts(
conditions: &Conditions,
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(allocation) = &variable.resolve.allocation
&& let Some(eligibility) = allocation
.eligibility
.as_deref()
.and_then(|source| Expression::parse(source).ok())
{
let eligibility = Some(eligibility);
for want in [true, false] {
if let Some(context) = synth(conditions, &eligibility, want) {
out.push(context);
}
}
}
if let Some(context) = merge_all(rules.iter().map(|rule| synth(conditions, rule, false))) {
out.push(context);
}
for index in 0..rules.len() {
if let Some(context) = synth(conditions, &rules[index], true) {
out.push(context);
}
let earlier = rules[..index]
.iter()
.map(|rule| synth(conditions, rule, false));
let this = std::iter::once(synth(conditions, &rules[index], true));
if let Some(context) = merge_all(earlier.chain(this)) {
out.push(context);
}
}
}
fn synth(conditions: &Conditions, rule: &Option<Expression>, want: bool) -> Option<JsonValue> {
rule.as_ref()?.synthesize_context(
want,
&mut |id, want| conditions.synthesize(id, want, &mut BTreeSet::new()),
&mut |_| None,
)
}
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)
}