Skip to content

Commit

Permalink
feat: Search contexts by dimension values (#264)
Browse files Browse the repository at this point in the history
  • Loading branch information
ayushjain17 authored Oct 22, 2024
1 parent 24e9a9e commit 12743af
Show file tree
Hide file tree
Showing 38 changed files with 862 additions and 737 deletions.
6 changes: 4 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/cac_client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ jsonlogic = { workspace = true }
log = { workspace = true }
once_cell = { workspace = true }
reqwest = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
strum = { workspace = true }
strum_macros = { workspace = true }
superposition_types = { path = "../superposition_types" }
tokio = { version = "1.29.1", features = ["full"] }

[lib]
Expand Down
26 changes: 17 additions & 9 deletions crates/cac_client/src/eval.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
//NOTE this code is copied over from sdk-config-server with small changes for compatiblity
//TODO refactor, make eval MJOS agnostic

use std::collections::HashMap;

use crate::{utils::core::MapError, Context, MergeStrategy};
use serde_json::{json, Map, Value};
use superposition_types::Overrides;

pub fn merge(doc: &mut Value, patch: &Value) {
if !patch.is_object() {
Expand Down Expand Up @@ -41,7 +44,7 @@ fn replace_top_level(
fn get_overrides(
query_data: &Map<String, Value>,
contexts: &[Context],
overrides: &Map<String, Value>,
overrides: &HashMap<String, Overrides>,
merge_strategy: &MergeStrategy,
mut on_override_select: Option<&mut dyn FnMut(Context)>,
) -> serde_json::Result<Value> {
Expand All @@ -52,22 +55,27 @@ fn get_overrides(
}
};

let query_data = Value::Object(query_data.clone());

for context in contexts {
// TODO :: Add semantic version comparator in Lib
if let Ok(Value::Bool(true)) =
jsonlogic::apply(&context.condition, &json!(query_data))
{
if let Ok(Value::Bool(true)) = jsonlogic::apply(
&Value::Object(context.condition.clone().into()),
&query_data,
) {
for override_key in &context.override_with_keys {
if let Some(overriden_value) = overrides.get(override_key) {
match merge_strategy {
MergeStrategy::REPLACE => replace_top_level(
required_overrides.as_object_mut().unwrap(),
overriden_value,
&Value::Object(overriden_value.clone().into()),
|| on_override_select(context.clone()),
override_key,
),
MergeStrategy::MERGE => {
merge(&mut required_overrides, overriden_value);
merge(
&mut required_overrides,
&Value::Object(overriden_value.clone().into()),
);
on_override_select(context.clone())
}
}
Expand Down Expand Up @@ -101,7 +109,7 @@ fn merge_overrides_on_default_config(
pub fn eval_cac(
mut default_config: Map<String, Value>,
contexts: &[Context],
overrides: &Map<String, Value>,
overrides: &HashMap<String, Overrides>,
query_data: &Map<String, Value>,
merge_strategy: MergeStrategy,
) -> Result<Map<String, Value>, String> {
Expand All @@ -123,7 +131,7 @@ pub fn eval_cac(
pub fn eval_cac_with_reasoning(
mut default_config: Map<String, Value>,
contexts: &[Context],
overrides: &Map<String, Value>,
overrides: &HashMap<String, Overrides>,
query_data: &Map<String, Value>,
merge_strategy: MergeStrategy,
) -> Result<Map<String, Value>, String> {
Expand Down
150 changes: 17 additions & 133 deletions crates/cac_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,11 @@ use actix_web::{rt::time::interval, web::Data};
use chrono::{DateTime, Utc};
use derive_more::{Deref, DerefMut};
use reqwest::{RequestBuilder, Response, StatusCode};
use serde::{Deserialize, Serialize};
use serde_json::{json, Map, Value};
use serde_json::{Map, Value};
use superposition_types::{Config, Context};
use tokio::sync::RwLock;
use utils::core::MapError;

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Context {
pub condition: Value,
pub override_with_keys: [String; 1],
}

#[repr(C)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Config {
pub contexts: Vec<Context>,
pub overrides: Map<String, Value>,
pub default_configs: Map<String, Value>,
}

#[derive(strum_macros::EnumString)]
#[strum(serialize_all = "snake_case")]
pub enum MergeStrategy {
Expand All @@ -49,9 +35,9 @@ impl Default for MergeStrategy {
impl From<String> for MergeStrategy {
fn from(value: String) -> Self {
match value.to_lowercase().as_str() {
"replace" => MergeStrategy::REPLACE,
"merge" => MergeStrategy::MERGE,
_ => MergeStrategy::default(),
"replace" => Self::REPLACE,
"merge" => Self::MERGE,
_ => Self::default(),
}
}
}
Expand Down Expand Up @@ -165,12 +151,12 @@ impl Client {
let cac = self.config.read().await;
let mut config = cac.to_owned();
if let Some(prefix_list) = prefix {
config = filter_config_by_prefix(&config, prefix_list)?;
config = config.filter_by_prefix(&HashSet::from_iter(prefix_list));
}

let dimension_filtered_config = query_data
.filter(|query_map| !query_map.is_empty())
.map(|query_map| filter_config_by_dimensions(&config, &query_map));
.map(|query_map| config.filter_by_dimensions(&query_map));

if let Some(filtered_config) = dimension_filtered_config {
config = filtered_config;
Expand All @@ -183,42 +169,34 @@ impl Client {
self.last_modified.read().await.clone()
}

pub async fn eval(
pub async fn get_resolved_config(
&self,
query_data: Map<String, Value>,
filter_keys: Option<Vec<String>>,
merge_strategy: MergeStrategy,
) -> Result<Map<String, Value>, String> {
let cac = self.config.read().await;
let mut config = cac.to_owned();
if let Some(keys) = filter_keys {
config = config.filter_by_prefix(&HashSet::from_iter(keys));
}
eval::eval_cac(
cac.default_configs.to_owned(),
&cac.contexts,
&cac.overrides,
config.default_configs.to_owned(),
&config.contexts,
&config.overrides,
&query_data,
merge_strategy,
)
}

pub async fn get_resolved_config(
&self,
query_data: Map<String, Value>,
filter_keys: Option<Vec<String>>,
merge_strategy: MergeStrategy,
) -> Result<Map<String, Value>, String> {
let mut cac = self.eval(query_data, merge_strategy).await?;
if let Some(keys) = filter_keys {
cac = filter_keys_by_prefix(cac, keys);
}
Ok(cac)
}

pub async fn get_default_config(
&self,
filter_keys: Option<Vec<String>>,
) -> Result<Map<String, Value>, String> {
let configs = self.config.read().await;
let mut default_configs = configs.default_configs.clone();
if let Some(keys) = filter_keys {
default_configs = filter_keys_by_prefix(default_configs, keys);
default_configs = configs.filter_default_by_prefix(&HashSet::from_iter(keys));
}
Ok(default_configs)
}
Expand Down Expand Up @@ -261,97 +239,3 @@ pub static CLIENT_FACTORY: Lazy<ClientFactory> =
pub use eval::eval_cac;
pub use eval::eval_cac_with_reasoning;
pub use eval::merge;

pub fn filter_keys_by_prefix(
keys: Map<String, Value>,
prefix_list: Vec<String>,
) -> Map<String, Value> {
let prefix_list: HashSet<String> = HashSet::from_iter(prefix_list);
keys.into_iter()
.filter(|(key, _)| {
prefix_list
.iter()
.any(|prefix_str| key.starts_with(prefix_str))
})
.collect()
}

pub fn filter_config_by_prefix(
config: &Config,
prefix_list: Vec<String>,
) -> Result<Config, String> {
let mut filtered_overrides: Map<String, Value> = Map::new();

let filtered_default_config: Map<String, Value> =
filter_keys_by_prefix(config.default_configs.clone(), prefix_list);

for (key, overrides) in &config.overrides {
let overrides_map = overrides
.as_object()
.ok_or_else(|| {
log::error!("failed to decode overrides.");
String::from("failed to decode overrides.")
})?
.clone();

let filtered_overrides_map: Map<String, Value> = overrides_map
.into_iter()
.filter(|(key, _)| filtered_default_config.contains_key(key))
.collect();

if !filtered_overrides_map.is_empty() {
filtered_overrides.insert(key.clone(), Value::Object(filtered_overrides_map));
}
}

let filtered_context: Vec<Context> = config
.contexts
.clone()
.into_iter()
.filter(|context| filtered_overrides.contains_key(&context.override_with_keys[0]))
.collect();

let filtered_config = Config {
contexts: filtered_context,
overrides: filtered_overrides,
default_configs: filtered_default_config,
};

Ok(filtered_config)
}

pub fn filter_config_by_dimensions(
config: &Config,
dimension_data: &Map<String, Value>,
) -> Config {
let filtered_context = config
.contexts
.iter()
.filter_map(|context| {
match jsonlogic::partial_apply(&context.condition, &json!(dimension_data)) {
Ok(jsonlogic::PartialApplyOutcome::Resolved(Value::Bool(true)))
| Ok(jsonlogic::PartialApplyOutcome::Ambiguous) => Some(context.clone()),
_ => None,
}
})
.collect::<Vec<Context>>();

let filtered_overrides: Map<String, Value> = filtered_context
.iter()
.flat_map(|ele| {
let override_with_key = &ele.override_with_keys[0];
config
.overrides
.get(override_with_key)
.map(|value| (override_with_key.to_string(), value.clone()))
})
.collect();

let filtered_config = Config {
contexts: filtered_context,
overrides: filtered_overrides,
default_configs: config.default_configs.clone(),
};

filtered_config
}
7 changes: 5 additions & 2 deletions crates/context_aware_config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@ derive_more = { workspace = true }
diesel = { workspace = true }
futures-util = "0.3.28"
itertools = "0.10.5"
jsonlogic = { workspace = true }
jsonschema = { workspace = true }
log = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
service_utils = { path = "../service_utils" }
strum_macros = { workspace = true }
superposition_macros = { path = "../superposition_macros" }
superposition_types = { path = "../superposition_types", features = ["result"] }
superposition_types = { path = "../superposition_types", features = [
"result",
"diesel_derives",
"server",
] }
uuid = { workspace = true }
fred = { workspace = true, optional = true, features = ["metrics"]}
cfg-if = { workspace = true }
Expand Down
1 change: 0 additions & 1 deletion crates/context_aware_config/src/api/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
mod handlers;
pub mod types;
pub use handlers::endpoints;
mod helpers;
Loading

0 comments on commit 12743af

Please sign in to comment.