flagsmith_flag_engine/segments/
evaluator.rs

1use super::constants;
2use super::Segment;
3use super::SegmentCondition;
4use super::SegmentRule;
5use crate::environments;
6use crate::identities;
7
8use crate::utils::hashing::get_hashed_percentage_for_object_ids;
9
10pub fn get_identity_segments(
11    environment: &environments::Environment,
12    identity: &identities::Identity,
13    override_traits: Option<&Vec<identities::Trait>>,
14) -> Vec<Segment> {
15    environment
16        .project
17        .segments
18        .clone()
19        .into_iter()
20        .filter(|segment| evaluate_identity_in_segment(&identity, &segment, override_traits))
21        .collect()
22}
23
24pub fn evaluate_identity_in_segment(
25    identity: &identities::Identity,
26    segment: &Segment,
27    override_traits: Option<&Vec<identities::Trait>>,
28) -> bool {
29    let traits = override_traits.unwrap_or(&identity.identity_traits);
30    let identity_id = match identity.django_id {
31        Some(django_id) => django_id.to_string(),
32        None => identity.composite_key(),
33    };
34    segment.rules.len() > 0
35        && segment
36            .rules
37            .iter()
38            .map(|rule| {
39                traits_match_segment_rule(traits, rule, &segment.id.to_string(), &identity_id)
40            })
41            .all(|result| result)
42}
43
44fn traits_match_segment_rule(
45    identity_traits: &Vec<identities::Trait>,
46    rule: &SegmentRule,
47    segment_id: &str,
48    identity_id: &str,
49) -> bool {
50    let mut rules_iterator = rule.conditions.iter().map(|condition| {
51        traits_match_segment_condition(&identity_traits, condition, segment_id, identity_id)
52    });
53    let matches_condtion = match rule.segment_rule_type.as_str() {
54        constants::ANY_RULE => rules_iterator.any(|result| result == true),
55        constants::ALL_RULE => rules_iterator.all(|result| result == true),
56        constants::NONE_RULE => true,
57        _ => false,
58    };
59    return matches_condtion
60        && rule
61            .rules
62            .iter()
63            .map(|rule| {
64                traits_match_segment_rule(&identity_traits, rule.as_ref(), segment_id, identity_id)
65            })
66            .all(|result| result == true);
67}
68
69fn traits_match_segment_condition(
70    identity_traits: &Vec<identities::Trait>,
71    condition: &SegmentCondition,
72    segment_id: &str,
73    identity_id: &str,
74) -> bool {
75    if condition.operator == constants::PERCENTAGE_SPLIT {
76        let float_value: f32 = condition.value.as_ref().unwrap().parse().unwrap();
77        return get_hashed_percentage_for_object_ids(vec![segment_id, identity_id], 1)
78            <= float_value;
79    }
80    match condition.property.clone() {
81        Some(property) => {
82            let identity_trait = identity_traits
83                .iter()
84                .filter(|identity_trait| identity_trait.trait_key == property)
85                .next();
86            if condition.operator == constants::IS_SET {
87                return identity_trait.is_some();
88            }
89
90            if condition.operator == constants::IS_NOT_SET {
91                return identity_trait.is_none();
92            }
93
94            if identity_trait.is_some() {
95                return condition.matches_trait_value(&identity_trait.unwrap().trait_value);
96            }
97
98            return false;
99        }
100        None => false,
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use crate::types::FlagsmithValue;
107
108    use super::*;
109    use rstest::*;
110
111    #[rstest]
112    #[case(constants::IS_SET, "foo", "foo", true)]
113    #[case(constants::IS_SET, "foo", "bar", false)]
114    #[case(constants::IS_NOT_SET, "foo", "foo", false)]
115    #[case(constants::IS_NOT_SET, "foo", "bar", true)]
116    fn trait_matches_segmnt_condition_is_and_is_not(
117        #[case] operator: &str,
118        #[case] property: &str,
119        #[case] trait_key: &str,
120        #[case] expected_result: bool,
121    ) {
122        let condition = SegmentCondition {
123            property: Some(property.to_string()),
124            operator: operator.to_string(),
125            value: None,
126        };
127        let traits = vec![identities::Trait {
128            trait_key: trait_key.to_string(),
129            trait_value: FlagsmithValue {
130                value: "".to_string(),
131                value_type: crate::types::FlagsmithValueType::None,
132            },
133        }];
134        let result = traits_match_segment_condition(&traits, &condition, "1", "1");
135        assert_eq!(result, expected_result);
136    }
137}