1use super::context::{
2 Condition, ConditionOperator, EngineEvaluationContext, SegmentContext, SegmentRule,
3 SegmentRuleType,
4};
5use crate::types::FlagsmithValue;
6use crate::utils::hashing;
7use regex::Regex;
8use semver::Version;
9use serde_json_path::JsonPath;
10
11pub fn is_context_in_segment(ec: &EngineEvaluationContext, segment: &SegmentContext) -> bool {
13 if segment.rules.is_empty() {
14 return false;
15 }
16
17 for rule in &segment.rules {
19 if !context_matches_segment_rule(ec, rule, &segment.key) {
20 return false;
21 }
22 }
23
24 true
25}
26
27fn context_matches_segment_rule(
29 ec: &EngineEvaluationContext,
30 rule: &SegmentRule,
31 segment_key: &str,
32) -> bool {
33 if !rule.conditions.is_empty()
35 && !matches_conditions_by_rule_type(ec, &rule.conditions, &rule.rule_type, segment_key)
36 {
37 return false;
38 }
39
40 for nested_rule in &rule.rules {
42 if !context_matches_segment_rule(ec, nested_rule, segment_key) {
43 return false;
44 }
45 }
46
47 true
48}
49
50fn matches_conditions_by_rule_type(
52 ec: &EngineEvaluationContext,
53 conditions: &[Condition],
54 rule_type: &SegmentRuleType,
55 segment_key: &str,
56) -> bool {
57 for condition in conditions {
58 let condition_matches = context_matches_condition(ec, condition, segment_key);
59
60 match rule_type {
61 SegmentRuleType::All => {
62 if !condition_matches {
63 return false; }
65 }
66 SegmentRuleType::None => {
67 if condition_matches {
68 return false; }
70 }
71 SegmentRuleType::Any => {
72 if condition_matches {
73 return true; }
75 }
76 }
77 }
78
79 *rule_type != SegmentRuleType::Any
81}
82
83fn context_matches_condition(
85 ec: &EngineEvaluationContext,
86 condition: &Condition,
87 segment_key: &str,
88) -> bool {
89 let context_value = if !condition.property.is_empty() {
90 get_context_value(ec, &condition.property)
91 } else {
92 None
93 };
94
95 match condition.operator {
96 ConditionOperator::PercentageSplit => {
97 match_percentage_split(ec, condition, segment_key, context_value.as_ref())
98 }
99 ConditionOperator::In => match_in_operator(condition, context_value.as_ref()),
100 ConditionOperator::IsNotSet => context_value.is_none(),
101 ConditionOperator::IsSet => context_value.is_some(),
102 _ => {
103 if let Some(ref ctx_val) = context_value {
104 parse_and_match(&condition.operator, ctx_val, &condition.value.as_string())
105 } else {
106 false
107 }
108 }
109 }
110}
111
112fn get_context_value(ec: &EngineEvaluationContext, property: &str) -> Option<FlagsmithValue> {
114 if property.starts_with("$.") {
116 if let Some(value) = get_value_from_jsonpath(ec, property) {
117 return Some(value);
118 }
119 }
121
122 if let Some(ref identity) = ec.identity {
124 if let Some(trait_value) = identity.traits.get(property) {
125 return Some(trait_value.clone());
126 }
127 }
128
129 None
130}
131
132fn get_value_from_jsonpath(ec: &EngineEvaluationContext, path: &str) -> Option<FlagsmithValue> {
134 let json_path = match JsonPath::parse(path) {
136 Ok(p) => p,
137 Err(_) => return None,
138 };
139
140 let context_json = match serde_json::to_value(ec) {
142 Ok(v) => v,
143 Err(_) => return None,
144 };
145
146 let result = json_path.query(&context_json);
148
149 let node_list = result.all();
151 if node_list.is_empty() {
152 return None;
153 }
154
155 let value = node_list[0];
157
158 match value {
160 serde_json::Value::String(s) => Some(FlagsmithValue {
161 value: s.clone(),
162 value_type: crate::types::FlagsmithValueType::String,
163 }),
164 serde_json::Value::Number(n) => {
165 if n.is_f64() {
166 Some(FlagsmithValue {
167 value: n.to_string(),
168 value_type: crate::types::FlagsmithValueType::Float,
169 })
170 } else {
171 Some(FlagsmithValue {
172 value: n.to_string(),
173 value_type: crate::types::FlagsmithValueType::Integer,
174 })
175 }
176 }
177 serde_json::Value::Bool(b) => Some(FlagsmithValue {
178 value: b.to_string(),
179 value_type: crate::types::FlagsmithValueType::Bool,
180 }),
181 _ => None,
182 }
183}
184
185fn match_percentage_split(
186 ec: &EngineEvaluationContext,
187 condition: &Condition,
188 segment_key: &str,
189 context_value: Option<&FlagsmithValue>,
190) -> bool {
191 let float_value = match condition.value.as_string().parse::<f64>() {
192 Ok(v) => v,
193 Err(_) => return false,
194 };
195
196 let split_key: Option<String> = if condition.property.is_empty() {
197 ec.identity.as_ref().map(|id| id.key.clone())
198 } else {
199 context_value.map(|v| v.value.clone())
200 };
201
202 let split_key = match split_key {
203 Some(key) => key,
204 None => return false,
205 };
206
207 let object_ids: Vec<&str> = vec![segment_key, &split_key];
208 let hash_percentage = hashing::get_hashed_percentage_for_object_ids(object_ids, 1);
209 (hash_percentage as f64) <= float_value
210}
211
212fn match_in_operator(condition: &Condition, context_value: Option<&FlagsmithValue>) -> bool {
214 if context_value.is_none() {
215 return false;
216 }
217
218 let ctx_value = context_value.unwrap();
219
220 use crate::types::FlagsmithValueType;
222 if ctx_value.value_type == FlagsmithValueType::Bool {
223 return false;
224 }
225
226 let trait_value = &ctx_value.value;
227
228 condition.value.contains_string(trait_value)
230}
231
232fn parse_and_match(
234 operator: &ConditionOperator,
235 trait_value: &FlagsmithValue,
236 condition_value: &str,
237) -> bool {
238 use crate::types::FlagsmithValueType;
239
240 match operator {
242 ConditionOperator::Modulo => return evaluate_modulo(&trait_value.value, condition_value),
243 ConditionOperator::Regex => return evaluate_regex(&trait_value.value, condition_value),
244 ConditionOperator::Contains => return trait_value.value.contains(condition_value),
245 ConditionOperator::NotContains => return !trait_value.value.contains(condition_value),
246 _ => {}
247 }
248
249 match trait_value.value_type {
251 FlagsmithValueType::Bool => compare_bool(operator, &trait_value.value, condition_value),
252 FlagsmithValueType::Integer => {
253 compare_integer(operator, &trait_value.value, condition_value)
254 }
255 FlagsmithValueType::Float => compare_float(operator, &trait_value.value, condition_value),
256 FlagsmithValueType::String => compare_string(operator, &trait_value.value, condition_value),
257 _ => false,
258 }
259}
260
261fn parse_bool(s: &str, allow_int_conversion: bool) -> Option<bool> {
264 match s.to_lowercase().as_str() {
265 "true" => Some(true),
266 "1" if allow_int_conversion => Some(true),
267 "false" => Some(false),
268 _ => None,
269 }
270}
271
272fn compare_bool(operator: &ConditionOperator, trait_value: &str, condition_value: &str) -> bool {
274 if let (Some(b1), Some(b2)) = (
275 parse_bool(trait_value, true),
276 parse_bool(condition_value, true),
277 ) {
278 match operator {
279 ConditionOperator::Equal => b1 == b2,
280 ConditionOperator::NotEqual => b1 != b2,
281 _ => false,
282 }
283 } else {
284 false
285 }
286}
287
288fn compare_integer(operator: &ConditionOperator, trait_value: &str, condition_value: &str) -> bool {
290 if let (Ok(i1), Ok(i2)) = (trait_value.parse::<i64>(), condition_value.parse::<i64>()) {
291 dispatch_operator(operator, i1, i2)
292 } else {
293 false
294 }
295}
296
297fn compare_float(operator: &ConditionOperator, trait_value: &str, condition_value: &str) -> bool {
299 if let (Ok(f1), Ok(f2)) = (trait_value.parse::<f64>(), condition_value.parse::<f64>()) {
300 dispatch_operator(operator, f1, f2)
301 } else {
302 false
303 }
304}
305
306fn compare_string(operator: &ConditionOperator, trait_value: &str, condition_value: &str) -> bool {
308 if let Some(version_str) = condition_value.strip_suffix(":semver") {
310 if let Ok(condition_version) = Version::parse(version_str) {
311 return evaluate_semver(operator, trait_value, &condition_version);
312 }
313 return false;
314 }
315
316 if let (Some(b1), Some(b2)) = (
318 parse_bool(trait_value, false),
319 parse_bool(condition_value, false),
320 ) {
321 return match operator {
322 ConditionOperator::Equal => b1 == b2,
323 ConditionOperator::NotEqual => b1 != b2,
324 _ => false,
325 };
326 }
327
328 if let (Ok(i1), Ok(i2)) = (trait_value.parse::<i64>(), condition_value.parse::<i64>()) {
330 return dispatch_operator(operator, i1, i2);
331 }
332
333 if let (Ok(f1), Ok(f2)) = (trait_value.parse::<f64>(), condition_value.parse::<f64>()) {
335 return dispatch_operator(operator, f1, f2);
336 }
337
338 dispatch_operator(operator, trait_value, condition_value)
340}
341
342fn dispatch_operator<T: PartialOrd + PartialEq>(
344 operator: &ConditionOperator,
345 v1: T,
346 v2: T,
347) -> bool {
348 match operator {
349 ConditionOperator::Equal => v1 == v2,
350 ConditionOperator::NotEqual => v1 != v2,
351 ConditionOperator::GreaterThan => v1 > v2,
352 ConditionOperator::LessThan => v1 < v2,
353 ConditionOperator::GreaterThanInclusive => v1 >= v2,
354 ConditionOperator::LessThanInclusive => v1 <= v2,
355 _ => false,
356 }
357}
358
359fn evaluate_regex(trait_value: &str, condition_value: &str) -> bool {
361 if let Ok(re) = Regex::new(condition_value) {
362 return re.is_match(trait_value);
363 }
364 false
365}
366
367fn evaluate_modulo(trait_value: &str, condition_value: &str) -> bool {
369 let values: Vec<&str> = condition_value.split('|').collect();
370 if values.len() != 2 {
371 return false;
372 }
373
374 let divisor = match values[0].parse::<f64>() {
375 Ok(v) => v,
376 Err(_) => return false,
377 };
378
379 let remainder = match values[1].parse::<f64>() {
380 Ok(v) => v,
381 Err(_) => return false,
382 };
383
384 let trait_value_float = match trait_value.parse::<f64>() {
385 Ok(v) => v,
386 Err(_) => return false,
387 };
388
389 const EPSILON: f64 = 1e-10;
391 ((trait_value_float % divisor) - remainder).abs() < EPSILON
392}
393
394fn evaluate_semver(
396 operator: &ConditionOperator,
397 trait_value: &str,
398 condition_version: &Version,
399) -> bool {
400 let trait_version = match Version::parse(trait_value) {
401 Ok(v) => v,
402 Err(_) => return false,
403 };
404
405 match operator {
406 ConditionOperator::Equal => trait_version == *condition_version,
407 ConditionOperator::NotEqual => trait_version != *condition_version,
408 ConditionOperator::GreaterThan => trait_version > *condition_version,
409 ConditionOperator::LessThan => trait_version < *condition_version,
410 ConditionOperator::GreaterThanInclusive => trait_version >= *condition_version,
411 ConditionOperator::LessThanInclusive => trait_version <= *condition_version,
412 _ => false,
413 }
414}
415
416#[cfg(test)]
417mod tests {
418 use super::*;
419
420 #[test]
421 fn test_dispatch_operator_integers() {
422 assert!(dispatch_operator(&ConditionOperator::Equal, 5, 5));
423 assert!(!dispatch_operator(&ConditionOperator::Equal, 5, 6));
424 assert!(dispatch_operator(&ConditionOperator::GreaterThan, 6, 5));
425 assert!(!dispatch_operator(&ConditionOperator::GreaterThan, 5, 6));
426 }
427
428 #[test]
429 fn test_evaluate_modulo() {
430 assert!(evaluate_modulo("2", "2|0"));
431 assert!(!evaluate_modulo("3", "2|0"));
432 assert!(evaluate_modulo("35.0", "4|3"));
433 }
434}