use std::collections::HashMap;
use std::sync::Arc;
use crate::condition::eval_context::SavedGroups;
use crate::dto::{GrowthBookFeature, GrowthBookFeatureRuleKind, GrowthBookFeatureRuleParentData};
use crate::filter::use_case::Filter;
use crate::model_public::{FeatureResult, GrowthBookAttribute};
use crate::sticky_bucket::StickyBucketService;
enum ParentOutcome {
Continue,
SkipRule,
ShortCircuit(Box<FeatureResult>),
}
impl GrowthBookFeature {
#[allow(clippy::too_many_arguments)]
pub fn get_value(
&self,
feature_name: &str,
feature_name_decorate: Vec<String>,
user_attributes: &Vec<GrowthBookAttribute>,
forced_variations: &Option<HashMap<String, i64>>,
all_features: &HashMap<String, GrowthBookFeature>,
sticky_bucket_service: &Option<Arc<dyn StickyBucketService>>,
saved_groups: &SavedGroups,
) -> FeatureResult {
if let Some(rules) = &self.rules {
for rule in rules {
if let Some(parents) = &rule.parent_conditions {
match evaluate_parent_conditions(
parents,
feature_name,
&feature_name_decorate,
user_attributes,
forced_variations,
all_features,
sticky_bucket_service,
saved_groups,
) {
ParentOutcome::ShortCircuit(result) => return *result,
ParentOutcome::SkipRule => continue,
ParentOutcome::Continue => {},
}
}
if let Some(filters) = rule.filters() {
if Filter::is_filtered_out(filters, "id", user_attributes) {
continue;
}
}
match &rule.kind {
GrowthBookFeatureRuleKind::Force(it) => {
if let Some(feature) = it.get_match_value(feature_name, user_attributes, saved_groups) {
return feature;
}
},
GrowthBookFeatureRuleKind::Rollout(it) => {
if let Some(feature) = it.get_match_value(feature_name, user_attributes, saved_groups) {
return feature;
}
},
GrowthBookFeatureRuleKind::Experiment(it) => {
if let Some(feature) = it.get_match_value(feature_name, user_attributes, forced_variations, sticky_bucket_service, saved_groups) {
return feature;
}
},
GrowthBookFeatureRuleKind::Empty => {},
}
}
}
FeatureResult::from_default_value(self.default_value.clone())
}
}
#[allow(clippy::too_many_arguments)]
fn evaluate_parent_conditions(
parents: &[GrowthBookFeatureRuleParentData],
feature_name: &str,
feature_name_decorate: &[String],
user_attributes: &Vec<GrowthBookAttribute>,
forced_variations: &Option<HashMap<String, i64>>,
all_features: &HashMap<String, GrowthBookFeature>,
sticky_bucket_service: &Option<Arc<dyn StickyBucketService>>,
saved_groups: &SavedGroups,
) -> ParentOutcome {
for parent in parents {
let parent_feature_name = &parent.id;
if feature_name_decorate.contains(parent_feature_name) {
return ParentOutcome::ShortCircuit(Box::new(FeatureResult::cyclic_prerequisite()));
}
let mut updated_decorate = feature_name_decorate.to_vec();
updated_decorate.push(String::from(feature_name));
let parent_response = if let Some(parent_feature) = all_features.get(parent_feature_name) {
parent_feature.get_value(
parent_feature_name,
updated_decorate,
user_attributes,
forced_variations,
all_features,
sticky_bucket_service,
saved_groups,
)
} else {
FeatureResult::unknown_feature()
};
if parent_response.source == "cyclicPrerequisite" {
return ParentOutcome::ShortCircuit(Box::new(FeatureResult::cyclic_prerequisite()));
}
if !parent.is_met(parent_response, saved_groups) {
if parent.gate {
return ParentOutcome::ShortCircuit(Box::new(FeatureResult::prerequisite()));
}
return ParentOutcome::SkipRule;
}
}
ParentOutcome::Continue
}