use std::collections::{HashMap, HashSet};
use serde_json::{json, Map, Value};
pub use superposition_types::api::config::MergeStrategy;
use superposition_types::{
logic::evaluate_local_cohorts, Config, Context, DimensionInfo, Overrides,
};
pub fn eval_config(
default_config: Map<String, Value>,
contexts: &[Context],
overrides: &HashMap<String, Overrides>,
dimensions: &HashMap<String, DimensionInfo>,
query_data: &Map<String, Value>,
merge_strategy: MergeStrategy,
filter_prefixes: Option<Vec<String>>,
) -> Result<Map<String, Value>, String> {
let mut config = Config {
default_configs: default_config.into(),
contexts: contexts.to_vec(),
overrides: overrides.clone(),
dimensions: dimensions.clone(),
};
if let Some(prefixes) = filter_prefixes.filter(|p| !p.is_empty()) {
config = config.filter_by_prefix(&HashSet::from_iter(prefixes.iter().cloned()));
}
let modified_query_data = evaluate_local_cohorts(&config.dimensions, query_data);
let overrides_map: Map<String, Value> = get_overrides(
&modified_query_data,
&config.contexts,
&config.overrides,
&merge_strategy,
None,
)?;
let mut result_config = config.default_configs;
merge_overrides_on_default_config(&mut result_config, overrides_map, &merge_strategy);
Ok(result_config.into_inner())
}
pub fn eval_config_with_reasoning(
default_config: Map<String, Value>,
contexts: &[Context],
overrides: &HashMap<String, Overrides>,
dimensions: &HashMap<String, DimensionInfo>,
query_data: &Map<String, Value>,
merge_strategy: MergeStrategy,
filter_prefixes: Option<Vec<String>>, ) -> Result<Map<String, Value>, String> {
let mut reasoning: Vec<Value> = vec![];
let mut config = Config {
default_configs: default_config.into(),
contexts: contexts.to_vec(),
overrides: overrides.clone(),
dimensions: dimensions.clone(),
};
if let Some(prefixes) = filter_prefixes.filter(|p| !p.is_empty()) {
config = config.filter_by_prefix(&HashSet::from_iter(prefixes.iter().cloned()));
}
let mut reasoning_collector = |context: Context| {
reasoning.push(json!({
"context": context.condition,
"override": context.override_with_keys
}));
};
let modified_query_data = evaluate_local_cohorts(&config.dimensions, query_data);
let overrides_map = get_overrides(
&modified_query_data,
&config.contexts,
&config.overrides,
&merge_strategy,
Some(&mut reasoning_collector),
)?;
let mut result_config = config.default_configs;
merge_overrides_on_default_config(&mut result_config, overrides_map, &merge_strategy);
result_config.insert("metadata".into(), json!(reasoning));
Ok(result_config.into_inner())
}
pub fn merge(doc: &mut Value, patch: &Value) {
if !patch.is_object() {
*doc = patch.clone();
return;
}
if !doc.is_object() {
*doc = Value::Object(Map::new());
}
let map = doc.as_object_mut().unwrap();
for (key, value) in patch.as_object().unwrap() {
merge(map.entry(key.as_str()).or_insert(Value::Null), value);
}
}
fn replace_top_level(
doc: &mut Map<String, Value>,
patch: &Value,
mut on_override: impl FnMut(),
override_key: &String,
) {
match patch.as_object() {
Some(patch_map) => {
for (key, value) in patch_map {
doc.insert(key.clone(), value.clone());
}
on_override();
}
None => {
log::error!(
"Config: found non-object override key: {override_key} in overrides"
);
}
}
}
fn get_overrides(
query_data: &Map<String, Value>,
contexts: &[Context],
overrides: &HashMap<String, Overrides>,
merge_strategy: &MergeStrategy,
mut on_override_select: Option<&mut dyn FnMut(Context)>,
) -> Result<Map<String, Value>, String> {
let mut required_overrides: Value = json!({});
let mut on_override_select = |context: Context| {
if let Some(ref mut func) = on_override_select {
func(context)
}
};
for context in contexts {
let valid_context = superposition_types::apply(&context.condition, query_data);
if valid_context {
let override_key = context.override_with_keys.get_key();
if let Some(overriden_value) = overrides.get(override_key) {
match merge_strategy {
MergeStrategy::REPLACE => replace_top_level(
required_overrides.as_object_mut().unwrap(),
&Value::Object(overriden_value.clone().into()),
|| on_override_select(context.clone()),
override_key,
),
MergeStrategy::MERGE => {
merge(
&mut required_overrides,
&Value::Object(overriden_value.clone().into()),
);
on_override_select(context.clone())
}
}
}
}
}
match required_overrides {
Value::Object(map) => Ok(map),
_ => Err("Failed to create overrides map".to_string()),
}
}
fn merge_overrides_on_default_config(
default_config: &mut Map<String, Value>,
overrides: Map<String, Value>,
merge_strategy: &MergeStrategy,
) {
overrides.into_iter().for_each(|(key, val)| {
if let Some(og_val) = default_config.get_mut(&key) {
match merge_strategy {
MergeStrategy::REPLACE => {
let _ = default_config.insert(key.clone(), val.clone());
}
MergeStrategy::MERGE => merge(og_val, &val),
}
} else {
log::error!("Config: found non-default_config key: {key} in overrides");
}
})
}