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(
187 ec: &EngineEvaluationContext,
188 condition: &Condition,
189 segment_key: &str,
190 context_value: Option<&FlagsmithValue>,
191) -> bool {
192 let float_value = match condition.value.as_string().parse::<f64>() {
193 Ok(v) => v,
194 Err(_) => return false,
195 };
196
197 let context_str = context_value.map(|v| v.value.clone());
199 let object_ids: Vec<&str> = if let Some(ref ctx_str) = context_str {
200 vec![segment_key, ctx_str.as_str()]
201 } else if let Some(ref identity) = ec.identity {
202 vec![segment_key, &identity.key]
203 } else {
204 return false;
205 };
206
207 let hash_percentage = hashing::get_hashed_percentage_for_object_ids(object_ids, 1);
208 (hash_percentage as f64) <= float_value
209}
210
211fn match_in_operator(condition: &Condition, context_value: Option<&FlagsmithValue>) -> bool {
213 if context_value.is_none() {
214 return false;
215 }
216
217 let ctx_value = context_value.unwrap();
218
219 use crate::types::FlagsmithValueType;
221 if ctx_value.value_type == FlagsmithValueType::Bool {
222 return false;
223 }
224
225 let trait_value = &ctx_value.value;
226
227 condition.value.contains_string(trait_value)
229}
230
231fn parse_and_match(
233 operator: &ConditionOperator,
234 trait_value: &FlagsmithValue,
235 condition_value: &str,
236) -> bool {
237 use crate::types::FlagsmithValueType;
238
239 match operator {
241 ConditionOperator::Modulo => return evaluate_modulo(&trait_value.value, condition_value),
242 ConditionOperator::Regex => return evaluate_regex(&trait_value.value, condition_value),
243 ConditionOperator::Contains => return trait_value.value.contains(condition_value),
244 ConditionOperator::NotContains => return !trait_value.value.contains(condition_value),
245 _ => {}
246 }
247
248 match trait_value.value_type {
250 FlagsmithValueType::Bool => compare_bool(operator, &trait_value.value, condition_value),
251 FlagsmithValueType::Integer => {
252 compare_integer(operator, &trait_value.value, condition_value)
253 }
254 FlagsmithValueType::Float => compare_float(operator, &trait_value.value, condition_value),
255 FlagsmithValueType::String => compare_string(operator, &trait_value.value, condition_value),
256 _ => false,
257 }
258}
259
260fn parse_bool(s: &str, allow_int_conversion: bool) -> Option<bool> {
263 match s.to_lowercase().as_str() {
264 "true" => Some(true),
265 "1" if allow_int_conversion => Some(true),
266 "false" => Some(false),
267 _ => None,
268 }
269}
270
271fn compare_bool(operator: &ConditionOperator, trait_value: &str, condition_value: &str) -> bool {
273 if let (Some(b1), Some(b2)) = (
274 parse_bool(trait_value, true),
275 parse_bool(condition_value, true),
276 ) {
277 match operator {
278 ConditionOperator::Equal => b1 == b2,
279 ConditionOperator::NotEqual => b1 != b2,
280 _ => false,
281 }
282 } else {
283 false
284 }
285}
286
287fn compare_integer(operator: &ConditionOperator, trait_value: &str, condition_value: &str) -> bool {
289 if let (Ok(i1), Ok(i2)) = (trait_value.parse::<i64>(), condition_value.parse::<i64>()) {
290 dispatch_operator(operator, i1, i2)
291 } else {
292 false
293 }
294}
295
296fn compare_float(operator: &ConditionOperator, trait_value: &str, condition_value: &str) -> bool {
298 if let (Ok(f1), Ok(f2)) = (trait_value.parse::<f64>(), condition_value.parse::<f64>()) {
299 dispatch_operator(operator, f1, f2)
300 } else {
301 false
302 }
303}
304
305fn compare_string(operator: &ConditionOperator, trait_value: &str, condition_value: &str) -> bool {
307 if let Some(version_str) = condition_value.strip_suffix(":semver") {
309 if let Ok(condition_version) = Version::parse(version_str) {
310 return evaluate_semver(operator, trait_value, &condition_version);
311 }
312 return false;
313 }
314
315 if let (Some(b1), Some(b2)) = (
317 parse_bool(trait_value, false),
318 parse_bool(condition_value, false),
319 ) {
320 return match operator {
321 ConditionOperator::Equal => b1 == b2,
322 ConditionOperator::NotEqual => b1 != b2,
323 _ => false,
324 };
325 }
326
327 if let (Ok(i1), Ok(i2)) = (trait_value.parse::<i64>(), condition_value.parse::<i64>()) {
329 return dispatch_operator(operator, i1, i2);
330 }
331
332 if let (Ok(f1), Ok(f2)) = (trait_value.parse::<f64>(), condition_value.parse::<f64>()) {
334 return dispatch_operator(operator, f1, f2);
335 }
336
337 dispatch_operator(operator, trait_value, condition_value)
339}
340
341fn dispatch_operator<T: PartialOrd + PartialEq>(
343 operator: &ConditionOperator,
344 v1: T,
345 v2: T,
346) -> bool {
347 match operator {
348 ConditionOperator::Equal => v1 == v2,
349 ConditionOperator::NotEqual => v1 != v2,
350 ConditionOperator::GreaterThan => v1 > v2,
351 ConditionOperator::LessThan => v1 < v2,
352 ConditionOperator::GreaterThanInclusive => v1 >= v2,
353 ConditionOperator::LessThanInclusive => v1 <= v2,
354 _ => false,
355 }
356}
357
358fn evaluate_regex(trait_value: &str, condition_value: &str) -> bool {
360 if let Ok(re) = Regex::new(condition_value) {
361 return re.is_match(trait_value);
362 }
363 false
364}
365
366fn evaluate_modulo(trait_value: &str, condition_value: &str) -> bool {
368 let values: Vec<&str> = condition_value.split('|').collect();
369 if values.len() != 2 {
370 return false;
371 }
372
373 let divisor = match values[0].parse::<f64>() {
374 Ok(v) => v,
375 Err(_) => return false,
376 };
377
378 let remainder = match values[1].parse::<f64>() {
379 Ok(v) => v,
380 Err(_) => return false,
381 };
382
383 let trait_value_float = match trait_value.parse::<f64>() {
384 Ok(v) => v,
385 Err(_) => return false,
386 };
387
388 const EPSILON: f64 = 1e-10;
390 ((trait_value_float % divisor) - remainder).abs() < EPSILON
391}
392
393fn evaluate_semver(
395 operator: &ConditionOperator,
396 trait_value: &str,
397 condition_version: &Version,
398) -> bool {
399 let trait_version = match Version::parse(trait_value) {
400 Ok(v) => v,
401 Err(_) => return false,
402 };
403
404 match operator {
405 ConditionOperator::Equal => trait_version == *condition_version,
406 ConditionOperator::NotEqual => trait_version != *condition_version,
407 ConditionOperator::GreaterThan => trait_version > *condition_version,
408 ConditionOperator::LessThan => trait_version < *condition_version,
409 ConditionOperator::GreaterThanInclusive => trait_version >= *condition_version,
410 ConditionOperator::LessThanInclusive => trait_version <= *condition_version,
411 _ => false,
412 }
413}
414
415#[cfg(test)]
416mod tests {
417 use super::*;
418
419 #[test]
420 fn test_dispatch_operator_integers() {
421 assert!(dispatch_operator(&ConditionOperator::Equal, 5, 5));
422 assert!(!dispatch_operator(&ConditionOperator::Equal, 5, 6));
423 assert!(dispatch_operator(&ConditionOperator::GreaterThan, 6, 5));
424 assert!(!dispatch_operator(&ConditionOperator::GreaterThan, 5, 6));
425 }
426
427 #[test]
428 fn test_evaluate_modulo() {
429 assert!(evaluate_modulo("2", "2|0"));
430 assert!(!evaluate_modulo("3", "2|0"));
431 assert!(evaluate_modulo("35.0", "4|3"));
432 }
433}