1use super::features;
2use super::types::{FlagsmithValue, FlagsmithValueType};
3use regex::Regex;
4use semver::Version;
5use serde::{Deserialize, Serialize};
6pub mod constants;
7pub mod evaluator;
8
9#[derive(Clone, Serialize, Deserialize, Debug)]
10pub struct SegmentCondition {
11 pub operator: String,
12 pub value: Option<String>,
13 #[serde(rename = "property_")]
14 pub property: Option<String>,
15}
16
17impl SegmentCondition {
18 pub fn matches_trait_value(&self, trait_value: &FlagsmithValue) -> bool {
19 if self.operator.as_str() == constants::MODULO {
20 return self.modulo_operations(trait_value, &self.value.as_ref().unwrap());
21 }
22 if self.operator.as_str() == constants::IN {
23 return match trait_value.value_type {
24 FlagsmithValueType::String => {
25 self.in_operations(&trait_value.value, &self.value.as_ref().unwrap())
26 }
27 FlagsmithValueType::Integer => {
28 let trait_value: String = trait_value.value.to_string();
29 self.in_operations(&trait_value, &self.value.as_ref().unwrap())
30 }
31 _ => false,
32 };
33 }
34 return match trait_value.value_type {
35 FlagsmithValueType::Integer => {
36 let trait_value: i64 = trait_value.value.parse().unwrap();
37 let segment_condition_value: i64 = self.value.as_ref().unwrap().parse().unwrap();
38
39 self.number_operations(trait_value, segment_condition_value)
40 }
41 FlagsmithValueType::Float => {
42 let trait_value: f64 = trait_value.value.parse().unwrap();
43 let segment_condition_value: f64 = self.value.as_ref().unwrap().parse().unwrap();
44 self.number_operations(trait_value, segment_condition_value)
45 }
46 FlagsmithValueType::String if self.value.as_ref().unwrap().ends_with(":semver") => {
47 let trait_value = Version::parse(&trait_value.value).unwrap();
48 let segment_condition_value = Version::parse(
49 &self.value.as_ref().unwrap()[..self.value.as_ref().unwrap().len() - 7],
50 )
51 .unwrap();
52 self.semver_operations(trait_value, segment_condition_value)
53 }
54 FlagsmithValueType::String => {
55 self.string_operations(&trait_value.value, &self.value.as_ref().unwrap())
56 }
57 FlagsmithValueType::Bool => {
58 let trait_value: bool = trait_value.value.parse().unwrap();
59 let segment_condition_value: bool = self.value.clone().unwrap().parse().unwrap();
60 self.bool_operations(trait_value, segment_condition_value)
61 }
62 _ => false,
63 };
64 }
65 fn string_operations(&self, trait_value: &str, segment_value: &str) -> bool {
66 match self.operator.as_str() {
67 constants::EQUAL => trait_value == segment_value,
68 constants::NOT_EQUAL => trait_value != segment_value,
69 constants::CONTAINS => trait_value.contains(segment_value),
70 constants::NOT_CONTAINS => !trait_value.contains(segment_value),
71 constants::REGEX => {
72 let re = Regex::new(segment_value).unwrap();
73 re.is_match(&trait_value)
74 }
75 _ => false,
76 }
77 }
78 fn modulo_operations(&self, trait_value: &FlagsmithValue, segment_value: &str) -> bool {
79 let values: Vec<&str> = segment_value.split("|").collect();
80 if values.len() != 2 {
81 return false;
82 }
83 let divisor: f64 = match values[0].parse() {
84 Ok(v) => v,
85 Err(_) => return false,
86 };
87 let remainder: f64 = match values[1].parse() {
88 Ok(v) => v,
89 Err(_) => return false,
90 };
91
92 let trait_value: f64 = match trait_value.value.parse() {
93 Ok(v) => v,
94 Err(_) => return false,
95 };
96 return (trait_value % divisor) == remainder;
97 }
98 fn semver_operations(&self, trait_value: Version, segment_value: Version) -> bool {
99 match self.operator.as_str() {
100 constants::EQUAL => trait_value == segment_value,
101 constants::NOT_EQUAL => trait_value != segment_value,
102 constants::GREATER_THAN => trait_value > segment_value,
103 constants::GREATER_THAN_INCLUSIVE => trait_value >= segment_value,
104 constants::LESS_THAN => trait_value < segment_value,
105 constants::LESS_THAN_INCLUSIVE => trait_value <= segment_value,
106 _ => false,
107 }
108 }
109 fn bool_operations(&self, trait_value: bool, segment_value: bool) -> bool {
110 match self.operator.as_str() {
111 constants::EQUAL => trait_value == segment_value,
112 constants::NOT_EQUAL => trait_value != segment_value,
113 _ => false,
114 }
115 }
116 fn number_operations<T: PartialOrd + PartialEq>(
117 &self,
118 trait_value: T,
119 segment_value: T,
120 ) -> bool {
121 match self.operator.as_str() {
122 constants::EQUAL => trait_value == segment_value,
123 constants::NOT_EQUAL => trait_value != segment_value,
124 constants::GREATER_THAN => trait_value > segment_value,
125 constants::GREATER_THAN_INCLUSIVE => trait_value >= segment_value,
126 constants::LESS_THAN => trait_value < segment_value,
127 constants::LESS_THAN_INCLUSIVE => trait_value <= segment_value,
128 _ => false,
129 }
130 }
131 fn in_operations(&self, trait_value: &str, segment_value: &str) -> bool {
132 segment_value.split(',').any(|x| x == trait_value)
133 }
134}
135
136#[derive(Clone, Serialize, Deserialize, Debug)]
137pub struct SegmentRule {
138 #[serde(rename = "type")]
139 pub segment_rule_type: String,
140 pub rules: Vec<Box<SegmentRule>>,
141 pub conditions: Vec<SegmentCondition>,
142}
143
144#[derive(Clone, Serialize, Deserialize, Debug)]
145pub struct Segment {
146 pub id: u32,
147 pub name: String,
148 pub rules: Vec<SegmentRule>,
149
150 #[serde(default)]
151 pub feature_states: Vec<features::FeatureState>,
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use rstest::*;
158
159 #[rstest]
160 #[case(constants::EQUAL, "bar", FlagsmithValueType::String, "bar", true)]
161 #[case(constants::EQUAL, "bar", FlagsmithValueType::String, "baz", false)]
162 #[case(constants::EQUAL, "1", FlagsmithValueType::Integer, "1", true)]
163 #[case(constants::EQUAL, "1", FlagsmithValueType::Integer, "2", false)]
164 #[case(constants::EQUAL, "true", FlagsmithValueType::Bool, "true", true)]
165 #[case(constants::EQUAL, "false", FlagsmithValueType::Bool, "false", true)]
166 #[case(constants::EQUAL, "true", FlagsmithValueType::Bool, "false", false)]
167 #[case(constants::EQUAL, "false", FlagsmithValueType::Bool, "true", false)]
168 #[case(constants::EQUAL, "1.23", FlagsmithValueType::Float, "1.23", true)]
169 #[case(constants::EQUAL, "1.23", FlagsmithValueType::Float, "1.25", false)]
170 #[case(constants::GREATER_THAN, "2", FlagsmithValueType::Integer, "1", true)]
171 #[case(constants::GREATER_THAN, "1", FlagsmithValueType::Integer, "2", false)]
172 #[case(constants::GREATER_THAN, "1", FlagsmithValueType::Integer, "1", false)]
173 #[case(constants::GREATER_THAN, "0", FlagsmithValueType::Integer, "1", false)]
174 #[case(constants::GREATER_THAN, "2.1", FlagsmithValueType::Float, "2.0", true)]
175 #[case(
176 constants::GREATER_THAN,
177 "2.1",
178 FlagsmithValueType::Float,
179 "2.2",
180 false
181 )]
182 #[case(
183 constants::GREATER_THAN,
184 "2.0",
185 FlagsmithValueType::Float,
186 "2.1",
187 false
188 )]
189 #[case(
190 constants::GREATER_THAN,
191 "2.0",
192 FlagsmithValueType::Float,
193 "2.1",
194 false
195 )]
196 #[case(
197 constants::GREATER_THAN,
198 "2.0",
199 FlagsmithValueType::Float,
200 "2.0",
201 false
202 )]
203 #[case(
204 constants::GREATER_THAN_INCLUSIVE,
205 "1",
206 FlagsmithValueType::Integer,
207 "1",
208 true
209 )]
210 #[case(
211 constants::GREATER_THAN_INCLUSIVE,
212 "2",
213 FlagsmithValueType::Integer,
214 "1",
215 true
216 )]
217 #[case(
218 constants::GREATER_THAN_INCLUSIVE,
219 "0",
220 FlagsmithValueType::Integer,
221 "1",
222 false
223 )]
224 #[case(
225 constants::GREATER_THAN_INCLUSIVE,
226 "2.0",
227 FlagsmithValueType::Float,
228 "2.0",
229 true
230 )]
231 #[case(
232 constants::GREATER_THAN_INCLUSIVE,
233 "2.1",
234 FlagsmithValueType::Float,
235 "2.0",
236 true
237 )]
238 #[case(
239 constants::GREATER_THAN_INCLUSIVE,
240 "2.1",
241 FlagsmithValueType::Float,
242 "2.2",
243 false
244 )]
245 #[case(constants::LESS_THAN, "2", FlagsmithValueType::Integer, "1", false)]
246 #[case(constants::LESS_THAN, "1", FlagsmithValueType::Integer, "2", true)]
247 #[case(constants::LESS_THAN, "1", FlagsmithValueType::Integer, "1", false)]
248 #[case(constants::LESS_THAN, "0", FlagsmithValueType::Integer, "1", true)]
249 #[case(constants::LESS_THAN, "2.1", FlagsmithValueType::Float, "2.0", false)]
250 #[case(constants::LESS_THAN, "2.1", FlagsmithValueType::Float, "2.2", true)]
251 #[case(constants::LESS_THAN, "2.0", FlagsmithValueType::Float, "2.1", true)]
252 #[case(constants::LESS_THAN, "2.0", FlagsmithValueType::Float, "2.1", true)]
253 #[case(constants::LESS_THAN, "2.0", FlagsmithValueType::Float, "2.0", false)]
254 #[case(
255 constants::LESS_THAN_INCLUSIVE,
256 "1",
257 FlagsmithValueType::Integer,
258 "1",
259 true
260 )]
261 #[case(
262 constants::LESS_THAN_INCLUSIVE,
263 "2",
264 FlagsmithValueType::Integer,
265 "1",
266 false
267 )]
268 #[case(
269 constants::LESS_THAN_INCLUSIVE,
270 "1",
271 FlagsmithValueType::Integer,
272 "2",
273 true
274 )]
275 #[case(
276 constants::LESS_THAN_INCLUSIVE,
277 "2.0",
278 FlagsmithValueType::Float,
279 "2.0",
280 true
281 )]
282 #[case(
283 constants::LESS_THAN_INCLUSIVE,
284 "2.1",
285 FlagsmithValueType::Float,
286 "2.0",
287 false
288 )]
289 #[case(
290 constants::LESS_THAN_INCLUSIVE,
291 "2.2",
292 FlagsmithValueType::Float,
293 "2.3",
294 true
295 )]
296 #[case(constants::NOT_EQUAL, "bar", FlagsmithValueType::String, "bar", false)]
297 #[case(constants::NOT_EQUAL, "bar", FlagsmithValueType::String, "baz", true)]
298 #[case(constants::NOT_EQUAL, "1", FlagsmithValueType::Integer, "1", false)]
299 #[case(constants::NOT_EQUAL, "1", FlagsmithValueType::Integer, "2", true)]
300 #[case(constants::NOT_EQUAL, "true", FlagsmithValueType::Bool, "true", false)]
301 #[case(
302 constants::NOT_EQUAL,
303 "false",
304 FlagsmithValueType::Bool,
305 "false",
306 false
307 )]
308 #[case(constants::NOT_EQUAL, "true", FlagsmithValueType::Bool, "false", true)]
309 #[case(constants::NOT_EQUAL, "false", FlagsmithValueType::Bool, "true", true)]
310 #[case(constants::NOT_EQUAL, "1.23", FlagsmithValueType::Float, "1.23", false)]
311 #[case(constants::NOT_EQUAL, "1.23", FlagsmithValueType::Float, "1.25", true)]
312 #[case(constants::CONTAINS, "bar", FlagsmithValueType::String, "b", true)]
313 #[case(constants::CONTAINS, "bar", FlagsmithValueType::String, "bar", true)]
314 #[case(constants::CONTAINS, "bar", FlagsmithValueType::String, "baz", false)]
315 #[case(constants::NOT_CONTAINS, "bar", FlagsmithValueType::String, "b", false)]
316 #[case(
317 constants::NOT_CONTAINS,
318 "bar",
319 FlagsmithValueType::String,
320 "bar",
321 false
322 )]
323 #[case(
324 constants::NOT_CONTAINS,
325 "bar",
326 FlagsmithValueType::String,
327 "baz",
328 true
329 )]
330 #[case(constants::REGEX, "foo", FlagsmithValueType::String, r"[a-z]+", true)]
331 #[case(constants::IN, "foo", FlagsmithValueType::String, "", false)]
332 #[case(constants::IN, "foo", FlagsmithValueType::String, "foo,bar", true)]
333 #[case(constants::IN, "bar", FlagsmithValueType::String, "foo,bar", true)]
334 #[case(constants::IN, "ba", FlagsmithValueType::String, "foo,bar", false)]
335 #[case(constants::IN, "foo", FlagsmithValueType::String, "foo", true)]
336 #[case(constants::IN, "1", FlagsmithValueType::Integer, "1,2,3,4", true)]
337 #[case(constants::IN, "1", FlagsmithValueType::Integer, "", false)]
338 #[case(constants::IN, "1", FlagsmithValueType::Integer, "1", true)]
339 #[case(constants::IN, "1.5", FlagsmithValueType::Float, "1.5", false)]
342 #[case(constants::IN, "false", FlagsmithValueType::Bool, "false", false)]
343 fn segemnt_condition_matches_trait_value(
344 #[case] operator: &str,
345 #[case] trait_value: &str,
346 #[case] trait_value_type: FlagsmithValueType,
347 #[case] value: &str,
348 #[case] result: bool,
349 ) {
350 let trait_value = FlagsmithValue {
351 value: trait_value.to_string(),
352 value_type: trait_value_type,
353 };
354 let segment_condition = SegmentCondition {
355 operator: operator.to_string(),
356 value: Some(value.to_string()),
357 property: Some("foo".to_string()),
358 };
359 assert_eq!(segment_condition.matches_trait_value(&trait_value), result)
360 }
361
362 #[rstest]
363 #[case(
364 constants::EQUAL,
365 "1.0.0",
366 FlagsmithValueType::String,
367 "1.0.0:semver",
368 true
369 )]
370 #[case(
371 constants::EQUAL,
372 "1.0.0",
373 FlagsmithValueType::String,
374 "1.0.1:semver",
375 false
376 )]
377 #[case(
378 constants::NOT_EQUAL,
379 "1.0.0",
380 FlagsmithValueType::String,
381 "1.0.1:semver",
382 true
383 )]
384 #[case(
385 constants::NOT_EQUAL,
386 "1.0.0",
387 FlagsmithValueType::String,
388 "1.0.0:semver",
389 false
390 )]
391 #[case(
392 constants::GREATER_THAN,
393 "1.0.1",
394 FlagsmithValueType::String,
395 "1.0.0:semver",
396 true
397 )]
398 #[case(
399 constants::GREATER_THAN,
400 "1.0.1",
401 FlagsmithValueType::String,
402 "1.0.1:semver",
403 false
404 )]
405 #[case(
406 constants::GREATER_THAN,
407 "1.0.0",
408 FlagsmithValueType::String,
409 "1.0.0-beta:semver",
410 true
411 )]
412 #[case(
413 constants::GREATER_THAN,
414 "1.0.1",
415 FlagsmithValueType::String,
416 "1.0.2-beta:semver",
417 false
418 )]
419 #[case(
420 constants::GREATER_THAN,
421 "1.2.3",
422 FlagsmithValueType::String,
423 "1.2.3-pre.2+build.4:semver",
424 true
425 )]
426 #[case(
427 constants::LESS_THAN,
428 "1.0.1",
429 FlagsmithValueType::String,
430 "1.0.2:semver",
431 true
432 )]
433 #[case(
434 constants::LESS_THAN,
435 "1.0.1",
436 FlagsmithValueType::String,
437 "1.0.1:semver",
438 false
439 )]
440 #[case(
441 constants::LESS_THAN,
442 "1.0.2",
443 FlagsmithValueType::String,
444 "1.0.1:semver",
445 false
446 )]
447 #[case(
448 constants::LESS_THAN,
449 "1.0.0-rc.2",
450 FlagsmithValueType::String,
451 "1.0.0-rc.3:semver",
452 true
453 )]
454 #[case(
455 constants::GREATER_THAN_INCLUSIVE,
456 "1.0.1",
457 FlagsmithValueType::String,
458 "1.0.0:semver",
459 true
460 )]
461 #[case(
462 constants::GREATER_THAN_INCLUSIVE,
463 "1.0.1",
464 FlagsmithValueType::String,
465 "1.0.1:semver",
466 true
467 )]
468 #[case(
469 constants::GREATER_THAN_INCLUSIVE,
470 "1.0.1",
471 FlagsmithValueType::String,
472 "1.0.2-beta:semver",
473 false
474 )]
475 #[case(
476 constants::GREATER_THAN_INCLUSIVE,
477 "1.2.3",
478 FlagsmithValueType::String,
479 "1.2.3-pre.2+build.4:semver",
480 true
481 )]
482 #[case(
483 constants::LESS_THAN_INCLUSIVE,
484 "1.0.1",
485 FlagsmithValueType::String,
486 "1.0.2:semver",
487 true
488 )]
489 #[case(
490 constants::LESS_THAN_INCLUSIVE,
491 "1.0.1",
492 FlagsmithValueType::String,
493 "1.0.1:semver",
494 true
495 )]
496 #[case(
497 constants::LESS_THAN_INCLUSIVE,
498 "1.0.2",
499 FlagsmithValueType::String,
500 "1.0.1:semver",
501 false
502 )]
503 #[case(
504 constants::LESS_THAN_INCLUSIVE,
505 "1.0.0-rc.2",
506 FlagsmithValueType::String,
507 "1.0.0-rc.3:semver",
508 true
509 )]
510
511 fn segemnt_condition_matches_trait_value_semver(
512 #[case] operator: &str,
513 #[case] trait_value: &str,
514 #[case] trait_value_type: FlagsmithValueType,
515 #[case] value: &str,
516 #[case] result: bool,
517 ) {
518 let trait_value = FlagsmithValue {
519 value: trait_value.to_string(),
520 value_type: trait_value_type,
521 };
522 let segment_condition = SegmentCondition {
523 operator: operator.to_string(),
524 value: Some(value.to_string()),
525 property: Some("foo".to_string()),
526 };
527 assert_eq!(segment_condition.matches_trait_value(&trait_value), result)
528 }
529
530 #[rstest]
531 #[case("1", FlagsmithValueType::Integer, "2|0", false)]
532 #[case("1.1", FlagsmithValueType::Float, "2.1|1.1", true)]
533 #[case("2", FlagsmithValueType::Integer, "2|0", true)]
534 #[case("3", FlagsmithValueType::Integer, "2|0", false)]
535 #[case("34.2", FlagsmithValueType::Float, "4|3", false)]
536 #[case("35.0", FlagsmithValueType::Float, "4|3", true)]
537 #[case("bar", FlagsmithValueType::String, "3|0", false)]
538 #[case("1.0.0", FlagsmithValueType::String, "3|0", false)]
539 #[case("false", FlagsmithValueType::Bool, "1|3", false)]
540 fn segment_condition_matches_trait_value_modulo(
541 #[case] trait_value: &str,
542 #[case] trait_value_type: FlagsmithValueType,
543 #[case] value: &str,
544 #[case] result: bool,
545 ) {
546 let trait_value = FlagsmithValue {
547 value: trait_value.to_string(),
548 value_type: trait_value_type,
549 };
550 let segment_condition = SegmentCondition {
551 operator: constants::MODULO.to_string(),
552 value: Some(value.to_string()),
553 property: Some("foo".to_string()),
554 };
555 assert_eq!(segment_condition.matches_trait_value(&trait_value), result)
556 }
557}