use std::collections::{HashMap, HashSet};
use bigdecimal::{BigDecimal, Num, ToPrimitive};
use itertools::Itertools;
use num_bigint::BigUint;
use serde_json::{Map, Value};
use superposition_types::{
Condition, Context, DimensionInfo, OverrideWithKeys, Overrides,
};
pub fn calculate_weight_from_index(index: u32) -> Result<BigDecimal, String> {
let base = BigUint::from(2u32);
let result = base.pow(index);
let biguint_str = &result.to_str_radix(10);
BigDecimal::from_str_radix(biguint_str, 10).map_err(|err| {
log::error!("failed to parse bigdecimal with error: {}", err);
String::from("failed to parse bigdecimal with error")
})
}
pub fn calculate_context_weight(
context: &Map<String, Value>,
dimensions_info: &HashMap<String, DimensionInfo>,
) -> Result<BigDecimal, String> {
let dimensions: HashSet<String> = context.keys().cloned().collect();
let mut weight = BigDecimal::from(0);
for dimension in dimensions {
let position = dimensions_info
.get(&dimension)
.map(|x| x.position)
.ok_or_else(|| {
let msg =
format!("Dimension:{} not found in Dimension schema map", dimension);
log::error!("{}", msg);
msg
})?;
weight += calculate_weight_from_index(position as u32)?;
}
Ok(weight)
}
fn json_to_sorted_string(v: &Value) -> String {
match v {
Value::Object(m) => {
let mut new_str: String = String::from("");
for (i, val) in m.iter().sorted_by_key(|item| item.0) {
let p: String = json_to_sorted_string(val);
new_str.push_str(i);
new_str.push_str(&String::from(":"));
new_str.push_str(&p);
new_str.push_str(&String::from("$"));
}
new_str
}
Value::String(m) => m.to_string(),
Value::Number(m) => m.to_string(),
Value::Bool(m) => m.to_string(),
Value::Null => String::from("null"),
Value::Array(m) => {
let mut new_vec =
m.iter().map(json_to_sorted_string).collect::<Vec<String>>();
new_vec.sort();
new_vec.join(",")
}
}
}
pub fn hash(val: &Value) -> String {
let sorted_str: String = json_to_sorted_string(val);
blake3::hash(sorted_str.as_bytes()).to_string()
}
pub fn create_connections_with_dependents(
cohorted_dimension: &str,
dimension_name: &str,
dimensions: &mut HashMap<String, DimensionInfo>,
) {
for (dim, dim_info) in dimensions.iter_mut() {
if dim == cohorted_dimension
&& !dim_info.dependency_graph.contains_key(cohorted_dimension)
{
dim_info
.dependency_graph
.insert(cohorted_dimension.to_string(), vec![]);
}
if let Some(current_deps) = dim_info.dependency_graph.get_mut(cohorted_dimension)
{
current_deps.push(dimension_name.to_string());
dim_info
.dependency_graph
.insert(dimension_name.to_string(), vec![]);
}
}
}
pub fn build_context(
condition: Condition,
overrides: Overrides,
dimensions: &HashMap<String, DimensionInfo>,
) -> Result<(Context, String, Overrides), String> {
let override_hash = hash(&Value::Object(
overrides
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
));
let condition_hash = hash(&Value::Object(
condition
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
));
let priority = calculate_context_weight(&condition, dimensions)
.map_err(|e| e.to_string())?
.to_i32()
.ok_or_else(|| "Failed to convert context weight to i32".to_string())?;
let context = Context {
condition,
id: condition_hash,
priority,
override_with_keys: OverrideWithKeys::new(override_hash.clone()),
weight: 0,
};
Ok((context, override_hash, overrides))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calculate_weight_from_index() {
let number_2_100_str = "1267650600228229401496703205376";
let big_decimal = BigDecimal::from_str_radix(number_2_100_str, 10)
.expect("Invalid string format");
let number_2_200_str =
"1606938044258990275541962092341162602522202993782792835301376";
let big_decimal_200 = BigDecimal::from_str_radix(number_2_200_str, 10)
.expect("Invalid string format");
assert_eq!(Some(big_decimal), calculate_weight_from_index(100).ok());
assert_eq!(Some(big_decimal_200), calculate_weight_from_index(200).ok());
}
#[test]
fn test_calculate_weight_small_indices() {
assert_eq!(calculate_weight_from_index(0).unwrap().to_string(), "1");
assert_eq!(calculate_weight_from_index(1).unwrap().to_string(), "2");
assert_eq!(calculate_weight_from_index(2).unwrap().to_string(), "4");
assert_eq!(calculate_weight_from_index(3).unwrap().to_string(), "8");
}
}