flagsmith_flag_engine/segments/
evaluator.rs1use 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}