1use crate::attribute_value::AttributeValue;
2use crate::contexts::attribute_reference::AttributeName;
3use crate::contexts::context::Kind;
4use crate::store::Store;
5use crate::variation::VariationOrRollout;
6use crate::{util, Context, EvaluationStack, Reference};
7use chrono::{self, Utc};
8use log::{error, warn};
9use regex::Regex;
10use serde::{Deserialize, Serialize};
11use serde_with::skip_serializing_none;
12use util::is_false;
13
14#[skip_serializing_none]
21#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
22#[serde(rename_all = "camelCase", from = "IntermediateClause")]
23pub struct Clause {
24 context_kind: Kind,
26 attribute: Reference,
30 #[serde(skip_serializing_if = "is_false")]
33 negate: bool,
34 op: Op,
36 values: Vec<AttributeValue>,
38}
39
40#[derive(Debug, Deserialize, PartialEq)]
41#[serde(rename_all = "camelCase")]
42struct ClauseWithKind {
43 context_kind: Kind,
44 attribute: Reference,
45 #[serde(default)]
46 negate: bool,
47 op: Op,
48 values: Vec<AttributeValue>,
49}
50
51#[derive(Debug, Deserialize, PartialEq)]
52#[serde(rename_all = "camelCase")]
53struct ClauseWithoutKind {
54 attribute: AttributeName,
55 #[serde(default)]
56 negate: bool,
57 op: Op,
58 values: Vec<AttributeValue>,
59}
60
61#[derive(Debug, Deserialize, PartialEq)]
62#[serde(untagged)]
63enum IntermediateClause {
64 ContextAware(ClauseWithKind),
67 ContextOblivious(ClauseWithoutKind),
68}
69
70impl From<IntermediateClause> for Clause {
71 fn from(ic: IntermediateClause) -> Self {
72 match ic {
73 IntermediateClause::ContextAware(fields) => Self {
74 context_kind: fields.context_kind,
75 attribute: fields.attribute,
76 negate: fields.negate,
77 op: fields.op,
78 values: fields.values,
79 },
80 IntermediateClause::ContextOblivious(fields) => Self {
81 context_kind: Kind::default(),
82 attribute: Reference::from(fields.attribute),
83 negate: fields.negate,
84 op: fields.op,
85 values: fields.values,
86 },
87 }
88 }
89}
90
91#[cfg(test)]
92pub(crate) mod proptest_generators {
93 use super::Clause;
94 use crate::contexts::attribute_reference::proptest_generators::*;
95 use crate::contexts::context::proptest_generators::*;
96 use crate::rule::Op;
97 use crate::AttributeValue;
98 use proptest::{collection::vec, prelude::*};
99
100 prop_compose! {
101 pub(crate) fn any_clause()(
104 kind in any_kind(),
105 reference in any_ref(),
108 negate in any::<bool>(),
109 values in vec(any::<bool>(), 0..5),
110 op in any::<Op>()
111 ) -> Clause {
112 Clause {
113 context_kind: kind,
114 attribute: reference,
115 negate,
116 op,
117 values: values.iter().map(|&b| AttributeValue::from(b)).collect()
118 }
119 }
120 }
121}
122
123#[derive(Clone, Debug, Serialize, Deserialize)]
128#[serde(rename_all = "camelCase")]
129pub struct FlagRule {
130 #[serde(default)]
134 pub id: String,
135 clauses: Vec<Clause>,
136
137 #[serde(flatten)]
139 pub variation_or_rollout: VariationOrRollout,
140
141 pub track_events: bool,
150}
151
152#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
153#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
154#[serde(rename_all = "camelCase")]
155enum Op {
156 In,
157 StartsWith,
158 EndsWith,
159 Contains,
160 Matches,
161 LessThan,
162 LessThanOrEqual,
163 GreaterThan,
164 GreaterThanOrEqual,
165 Before,
166 After,
167 SegmentMatch,
168 SemVerEqual,
169 SemVerGreaterThan,
170 SemVerLessThan,
171 #[serde(other)]
172 Unknown,
173}
174
175impl Clause {
176 pub(crate) fn matches(
177 &self,
178 context: &Context,
179 store: &dyn Store,
180 evaluation_stack: &mut EvaluationStack,
181 ) -> Result<bool, String> {
182 if let Op::SegmentMatch = self.op {
183 self.matches_segment(context, store, evaluation_stack)
184 } else {
185 self.matches_non_segment(context)
186 }
187 }
188
189 fn maybe_negate(&self, v: bool) -> bool {
190 if self.negate {
191 !v
192 } else {
193 v
194 }
195 }
196
197 pub(crate) fn matches_segment(
198 &self,
199 context: &Context,
200 store: &dyn Store,
201 evaluation_stack: &mut EvaluationStack,
202 ) -> Result<bool, String> {
203 for value in self.values.iter() {
204 if let Some(segment_key) = value.as_str() {
205 if let Some(segment) = store.segment(segment_key) {
206 let matches = segment.contains(context, store, evaluation_stack)?;
207 if matches {
208 return Ok(self.maybe_negate(true));
209 }
210 }
211 }
212 }
213
214 Ok(self.maybe_negate(false))
215 }
216
217 pub(crate) fn matches_non_segment(&self, context: &Context) -> Result<bool, String> {
218 if !self.attribute.is_valid() {
219 return Err(self.attribute.error());
220 }
221
222 if self.attribute.is_kind() {
223 for clause_value in &self.values {
224 for kind in context.kinds().iter() {
225 if self
226 .op
227 .matches(&AttributeValue::String(kind.to_string()), clause_value)
228 {
229 return Ok(self.maybe_negate(true));
230 }
231 }
232 }
233 return Ok(self.maybe_negate(false));
234 }
235
236 if let Some(actual_context) = context.as_kind(&self.context_kind) {
237 return match actual_context.get_value(&self.attribute) {
238 None | Some(AttributeValue::Null) => Ok(false),
239 Some(AttributeValue::Array(context_values)) => {
240 for clause_value in &self.values {
241 for context_value in context_values.iter() {
242 if self.op.matches(context_value, clause_value) {
243 return Ok(self.maybe_negate(true));
244 }
245 }
246 }
247
248 Ok(self.maybe_negate(false))
249 }
250 Some(context_value) => {
251 if self
252 .values
253 .iter()
254 .any(|clause_value| self.op.matches(&context_value, clause_value))
255 {
256 return Ok(self.maybe_negate(true));
257 }
258 Ok(self.maybe_negate(false))
259 }
260 };
261 }
262
263 Ok(false)
264 }
265
266 #[cfg(test)]
267 pub(crate) fn new_match(reference: Reference, value: AttributeValue, kind: Kind) -> Self {
269 Self {
270 attribute: reference,
271 negate: false,
272 op: Op::Matches,
273 values: vec![value],
274 context_kind: kind,
275 }
276 }
277
278 #[cfg(test)]
279 pub(crate) fn new_context_oblivious_match(reference: Reference, value: AttributeValue) -> Self {
281 Self {
282 attribute: reference,
283 negate: false,
284 op: Op::Matches,
285 values: vec![value],
286 context_kind: Kind::default(),
287 }
288 }
289}
290
291impl FlagRule {
292 pub(crate) fn matches(
296 &self,
297 context: &Context,
298 store: &dyn Store,
299 evaluation_stack: &mut EvaluationStack,
300 ) -> Result<bool, String> {
301 for clause in &self.clauses {
303 let result = clause.matches(context, store, evaluation_stack)?;
304 if !result {
305 return Ok(false);
306 }
307 }
308
309 Ok(true)
310 }
311
312 #[cfg(test)]
313 pub(crate) fn new_segment_match(segment_keys: Vec<&str>, kind: Kind) -> Self {
314 Self {
315 id: "rule".to_string(),
316 clauses: vec![Clause {
317 attribute: Reference::new("key"),
318 negate: false,
319 op: Op::SegmentMatch,
320 values: segment_keys
321 .iter()
322 .map(|key| AttributeValue::String(key.to_string()))
323 .collect(),
324 context_kind: kind,
325 }],
326 variation_or_rollout: VariationOrRollout::Variation { variation: 1 },
327 track_events: false,
328 }
329 }
330}
331
332impl Op {
333 fn matches(&self, lhs: &AttributeValue, rhs: &AttributeValue) -> bool {
334 match self {
335 Op::In => lhs == rhs,
336
337 Op::StartsWith => string_op(lhs, rhs, |l, r| l.starts_with(r)),
339 Op::EndsWith => string_op(lhs, rhs, |l, r| l.ends_with(r)),
340 Op::Contains => string_op(lhs, rhs, |l, r| l.contains(r)),
341 Op::Matches => string_op(lhs, rhs, |l, r| match Regex::new(r) {
342 Ok(re) => re.is_match(l),
343 Err(e) => {
344 warn!("Invalid regex for 'matches' operator ({}): {}", e, l);
345 false
346 }
347 }),
348
349 Op::LessThan => numeric_op(lhs, rhs, |l, r| l < r),
351 Op::LessThanOrEqual => numeric_op(lhs, rhs, |l, r| l <= r),
352 Op::GreaterThan => numeric_op(lhs, rhs, |l, r| l > r),
353 Op::GreaterThanOrEqual => numeric_op(lhs, rhs, |l, r| l >= r),
354
355 Op::Before => time_op(lhs, rhs, |l, r| l < r),
356 Op::After => time_op(lhs, rhs, |l, r| l > r),
357
358 Op::SegmentMatch => {
359 error!("segmentMatch operator should be special-cased, shouldn't get here");
360 false
361 }
362
363 Op::SemVerEqual => semver_op(lhs, rhs, |l, r| l == r),
364 Op::SemVerLessThan => semver_op(lhs, rhs, |l, r| l < r),
365 Op::SemVerGreaterThan => semver_op(lhs, rhs, |l, r| l > r),
366 Op::Unknown => false,
367 }
368 }
369}
370
371fn string_op<F: Fn(&str, &str) -> bool>(lhs: &AttributeValue, rhs: &AttributeValue, f: F) -> bool {
372 match (lhs.as_str(), rhs.as_str()) {
373 (Some(l), Some(r)) => f(l, r),
374 _ => false,
375 }
376}
377
378fn numeric_op<F: Fn(f64, f64) -> bool>(lhs: &AttributeValue, rhs: &AttributeValue, f: F) -> bool {
379 match (lhs.to_f64(), rhs.to_f64()) {
380 (Some(l), Some(r)) => f(l, r),
381 _ => false,
382 }
383}
384
385fn time_op<F: Fn(chrono::DateTime<Utc>, chrono::DateTime<Utc>) -> bool>(
386 lhs: &AttributeValue,
387 rhs: &AttributeValue,
388 f: F,
389) -> bool {
390 match (lhs.to_datetime(), rhs.to_datetime()) {
391 (Some(l), Some(r)) => f(l, r),
392 _ => false,
393 }
394}
395
396fn semver_op<F: Fn(semver::Version, semver::Version) -> bool>(
397 lhs: &AttributeValue,
398 rhs: &AttributeValue,
399 f: F,
400) -> bool {
401 match (lhs.as_semver(), rhs.as_semver()) {
402 (Some(l), Some(r)) => f(l, r),
403 _ => false,
404 }
405}
406
407#[cfg(test)]
408mod tests {
409 use super::*;
410 use crate::{flag::Flag, ContextBuilder, Segment};
411 use assert_json_diff::assert_json_eq;
412 use maplit::hashmap;
413 use proptest::prelude::*;
414 use serde_json::json;
415 use std::collections::HashMap;
416 use std::time::SystemTime;
417 struct TestStore;
418 use crate::proptest_generators::*;
419
420 impl Store for TestStore {
421 fn flag(&self, _flag_key: &str) -> Option<Flag> {
422 None
423 }
424 fn segment(&self, _segment_key: &str) -> Option<Segment> {
425 None
426 }
427 }
428
429 fn astring(s: &str) -> AttributeValue {
430 AttributeValue::String(s.into())
431 }
432 fn anum(f: f64) -> AttributeValue {
433 AttributeValue::Number(f)
434 }
435
436 #[test]
437 fn test_op_in() {
438 assert!(Op::In.matches(&astring("foo"), &astring("foo")));
440
441 assert!(!Op::In.matches(&astring("foo"), &astring("bar")));
442 assert!(
443 !Op::In.matches(&astring("Foo"), &astring("foo")),
444 "case sensitive"
445 );
446
447 assert!(Op::In.matches(&anum(42.0), &anum(42.0)));
449 assert!(!Op::In.matches(&anum(42.0), &anum(3.0)));
450 assert!(Op::In.matches(&anum(0.0), &anum(-0.0)));
451
452 assert!(Op::In.matches(&vec![0.0].into(), &vec![0.0].into()));
454 assert!(!Op::In.matches(&vec![0.0, 1.0].into(), &vec![0.0].into()));
455 assert!(!Op::In.matches(&vec![0.0].into(), &vec![0.0, 1.0].into()));
456 assert!(!Op::In.matches(&anum(0.0), &vec![0.0].into()));
457 assert!(!Op::In.matches(&vec![0.0].into(), &anum(0.0)));
458
459 assert!(Op::In.matches(&hashmap! {"x" => 0.0}.into(), &hashmap! {"x" => 0.0}.into()));
461 assert!(!Op::In.matches(
462 &hashmap! {"x" => 0.0, "y" => 1.0}.into(),
463 &hashmap! {"x" => 0.0}.into()
464 ));
465 assert!(!Op::In.matches(
466 &hashmap! {"x" => 0.0}.into(),
467 &hashmap! {"x" => 0.0, "y" => 1.0}.into()
468 ));
469 assert!(!Op::In.matches(&anum(0.0), &hashmap! {"x" => 0.0}.into()));
470 assert!(!Op::In.matches(&hashmap! {"x" => 0.0}.into(), &anum(0.0)));
471 }
472
473 #[test]
474 fn test_op_starts_with() {
475 assert!(Op::StartsWith.matches(&astring(""), &astring("")));
477 assert!(Op::StartsWith.matches(&astring("a"), &astring("")));
478 assert!(Op::StartsWith.matches(&astring("a"), &astring("a")));
479
480 assert!(Op::StartsWith.matches(&astring("food"), &astring("foo")));
482 assert!(!Op::StartsWith.matches(&astring("foo"), &astring("food")));
483
484 assert!(
485 !Op::StartsWith.matches(&astring("Food"), &astring("foo")),
486 "case sensitive"
487 );
488 }
489
490 #[test]
491 fn test_op_ends_with() {
492 assert!(Op::EndsWith.matches(&astring(""), &astring("")));
494 assert!(Op::EndsWith.matches(&astring("a"), &astring("")));
495 assert!(Op::EndsWith.matches(&astring("a"), &astring("a")));
496
497 assert!(Op::EndsWith.matches(&astring("food"), &astring("ood")));
499 assert!(!Op::EndsWith.matches(&astring("ood"), &astring("food")));
500
501 assert!(
502 !Op::EndsWith.matches(&astring("FOOD"), &astring("ood")),
503 "case sensitive"
504 );
505 }
506
507 #[test]
508 fn test_op_contains() {
509 assert!(Op::Contains.matches(&astring(""), &astring("")));
511 assert!(Op::Contains.matches(&astring("a"), &astring("")));
512 assert!(Op::Contains.matches(&astring("a"), &astring("a")));
513
514 assert!(Op::Contains.matches(&astring("food"), &astring("oo")));
516 assert!(!Op::Contains.matches(&astring("oo"), &astring("food")));
517
518 assert!(
519 !Op::Contains.matches(&astring("FOOD"), &astring("oo")),
520 "case sensitive"
521 );
522 }
523
524 #[test]
525 fn test_op_matches() {
526 fn should_match(text: &str, pattern: &str) {
527 assert!(
528 Op::Matches.matches(&astring(text), &astring(pattern)),
529 "`{}` should match `{}`",
530 text,
531 pattern
532 );
533 }
534
535 fn should_not_match(text: &str, pattern: &str) {
536 assert!(
537 !Op::Matches.matches(&astring(text), &astring(pattern)),
538 "`{}` should not match `{}`",
539 text,
540 pattern
541 );
542 }
543
544 should_match("", "");
545 should_match("a", "");
546 should_match("a", "a");
547 should_match("a", ".");
548 should_match("hello world", "hello.*rld");
549 should_match("hello world", "hello.*orl");
550 should_match("hello world", "l+");
551 should_match("hello world", "(world|planet)");
552
553 should_not_match("", ".");
554 should_not_match("", r"\");
555 should_not_match("hello world", "aloha");
556 should_not_match("hello world", "***bad regex");
557 }
558
559 #[test]
560 fn test_ops_numeric() {
561 assert!(Op::LessThan.matches(&anum(0.0), &anum(1.0)));
563 assert!(!Op::LessThan.matches(&anum(0.0), &anum(0.0)));
564 assert!(!Op::LessThan.matches(&anum(1.0), &anum(0.0)));
565
566 assert!(Op::GreaterThan.matches(&anum(1.0), &anum(0.0)));
567 assert!(!Op::GreaterThan.matches(&anum(0.0), &anum(0.0)));
568 assert!(!Op::GreaterThan.matches(&anum(0.0), &anum(1.0)));
569
570 assert!(Op::LessThanOrEqual.matches(&anum(0.0), &anum(1.0)));
571 assert!(Op::LessThanOrEqual.matches(&anum(0.0), &anum(0.0)));
572 assert!(!Op::LessThanOrEqual.matches(&anum(1.0), &anum(0.0)));
573
574 assert!(Op::GreaterThanOrEqual.matches(&anum(1.0), &anum(0.0)));
575 assert!(Op::GreaterThanOrEqual.matches(&anum(0.0), &anum(0.0)));
576 assert!(!Op::GreaterThanOrEqual.matches(&anum(0.0), &anum(1.0)));
577
578 assert!(!Op::LessThan.matches(&astring("0"), &anum(1.0)));
580 assert!(!Op::LessThan.matches(&anum(0.0), &astring("1")));
581 }
582
583 #[test]
584 fn test_ops_time() {
585 let today = SystemTime::now();
586 let today_millis = today
587 .duration_since(SystemTime::UNIX_EPOCH)
588 .unwrap()
589 .as_millis() as f64;
590 let yesterday_millis = today_millis - 86_400_000_f64;
591
592 assert!(Op::Before.matches(&anum(yesterday_millis), &anum(today_millis)));
594 assert!(!Op::Before.matches(&anum(today_millis), &anum(yesterday_millis)));
595 assert!(!Op::Before.matches(&anum(today_millis), &anum(today_millis)));
596
597 assert!(Op::After.matches(&anum(today_millis), &anum(yesterday_millis)));
598 assert!(!Op::After.matches(&anum(yesterday_millis), &anum(today_millis)));
599 assert!(!Op::After.matches(&anum(today_millis), &anum(today_millis)));
600
601 assert!(!Op::Before.matches(&astring(&yesterday_millis.to_string()), &anum(today_millis)));
603 assert!(!Op::After.matches(&anum(today_millis), &astring(&yesterday_millis.to_string())));
604
605 assert!(Op::Before.matches(
607 &astring("2019-11-19T17:29:00.000000-07:00"),
608 &anum(today_millis)
609 ));
610 assert!(
611 Op::Before.matches(&astring("2019-11-19T17:29:00-07:00"), &anum(today_millis)),
612 "fractional seconds part is optional"
613 );
614
615 assert!(Op::After.matches(
616 &anum(today_millis),
617 &astring("2019-11-19T17:29:00.000000-07:00")
618 ));
619
620 assert!(!Op::Before.matches(&astring("fish"), &anum(today_millis)));
622 assert!(!Op::After.matches(&anum(today_millis), &astring("fish")));
623 }
624
625 #[test]
626 fn test_semver_ops() {
627 assert!(Op::SemVerEqual.matches(&astring("2.0.0"), &astring("2.0.0")));
628
629 assert!(
630 Op::SemVerEqual.matches(&astring("2.0"), &astring("2.0.0")),
631 "we allow missing components (filled in with zeroes)"
632 );
633 assert!(
634 Op::SemVerEqual.matches(&astring("2"), &astring("2.0.0")),
635 "we allow missing components (filled in with zeroes)"
636 );
637
638 assert!(!Op::SemVerEqual.matches(&astring("2.0.0"), &astring("3.0.0")));
639 assert!(!Op::SemVerEqual.matches(&astring("2.0.0"), &astring("2.1.0")));
640 assert!(!Op::SemVerEqual.matches(&astring("2.0.0"), &astring("2.0.1")));
641
642 assert!(Op::SemVerGreaterThan.matches(&astring("3.0.0"), &astring("2.0.0")));
643 assert!(Op::SemVerGreaterThan.matches(&astring("2.1.0"), &astring("2.0.0")));
644 assert!(Op::SemVerGreaterThan.matches(&astring("2.0.1"), &astring("2.0.0")));
645 assert!(Op::SemVerGreaterThan
646 .matches(&astring("2.0.0-rc.10.green"), &astring("2.0.0-rc.2.green")));
647 assert!(
648 Op::SemVerGreaterThan.matches(&astring("2.0.0-rc.2.red"), &astring("2.0.0-rc.2.green")),
649 "red > green"
650 );
651 assert!(
652 Op::SemVerGreaterThan
653 .matches(&astring("2.0.0-rc.2.green.1"), &astring("2.0.0-rc.2.green")),
654 "adding more version components makes it greater"
655 );
656
657 assert!(!Op::SemVerGreaterThan.matches(&astring("2.0.0"), &astring("2.0.0")));
658 assert!(!Op::SemVerGreaterThan.matches(&astring("1.9.0"), &astring("2.0.0")));
659 assert!(
660 !Op::SemVerGreaterThan.matches(&astring("2.0.0-rc"), &astring("2.0.0")),
661 "prerelease version < released version"
662 );
663 assert!(
664 !Op::SemVerGreaterThan.matches(&astring("2.0.0+build"), &astring("2.0.0")),
665 "build metadata is ignored, these versions are equal"
666 );
667
668 assert!(!Op::SemVerEqual.matches(&astring("2.0.0"), &astring("200")));
669
670 assert!(!Op::SemVerEqual.matches(&astring("2.0.0"), &anum(2.0)));
672 }
673
674 #[test]
675 fn test_clause_matches() {
676 let one_val_clause = Clause {
677 attribute: Reference::new("a"),
678 negate: false,
679 op: Op::In,
680 values: vec!["foo".into()],
681 context_kind: Kind::default(),
682 };
683 let many_val_clause = Clause {
684 attribute: Reference::new("a"),
685 negate: false,
686 op: Op::In,
687 values: vec!["foo".into(), "bar".into()],
688 context_kind: Kind::default(),
689 };
690 let negated_clause = Clause {
691 attribute: Reference::new("a"),
692 negate: true,
693 op: Op::In,
694 values: vec!["foo".into()],
695 context_kind: Kind::default(),
696 };
697 let negated_many_val_clause = Clause {
698 attribute: Reference::new("a"),
699 negate: true,
700 op: Op::In,
701 values: vec!["foo".into(), "bar".into()],
702 context_kind: Kind::default(),
703 };
704 let key_clause = Clause {
705 attribute: Reference::new("key"),
706 negate: false,
707 op: Op::In,
708 values: vec!["matching".into()],
709 context_kind: Kind::default(),
710 };
711
712 let mut context_builder = ContextBuilder::new("without");
713 let context_without_attribute = context_builder.build().expect("Failed to build context");
714
715 context_builder
716 .key("matching")
717 .set_value("a", AttributeValue::String("foo".to_string()));
718 let matching_context = context_builder.build().expect("Failed to build context");
719
720 context_builder
721 .key("non-matching")
722 .set_value("a", AttributeValue::String("lol".to_string()));
723 let non_matching_context = context_builder.build().expect("Failed to build context");
724
725 let mut evaluation_stack = EvaluationStack::default();
726
727 assert!(one_val_clause
728 .matches(&matching_context, &TestStore {}, &mut evaluation_stack)
729 .unwrap());
730 assert!(!one_val_clause
731 .matches(&non_matching_context, &TestStore {}, &mut evaluation_stack)
732 .unwrap());
733 assert!(!one_val_clause
734 .matches(
735 &context_without_attribute,
736 &TestStore {},
737 &mut evaluation_stack
738 )
739 .unwrap());
740
741 assert!(!negated_clause
742 .matches(&matching_context, &TestStore {}, &mut evaluation_stack)
743 .unwrap());
744 assert!(negated_clause
745 .matches(&non_matching_context, &TestStore {}, &mut evaluation_stack)
746 .unwrap());
747
748 assert!(
749 !negated_clause
750 .matches(
751 &context_without_attribute,
752 &TestStore {},
753 &mut evaluation_stack
754 )
755 .unwrap(),
756 "targeting missing attribute does not match even when negated"
757 );
758
759 assert!(
760 many_val_clause
761 .matches(&matching_context, &TestStore {}, &mut evaluation_stack)
762 .unwrap(),
763 "requires only one of the values"
764 );
765 assert!(!many_val_clause
766 .matches(&non_matching_context, &TestStore {}, &mut evaluation_stack)
767 .unwrap());
768 assert!(!many_val_clause
769 .matches(
770 &context_without_attribute,
771 &TestStore {},
772 &mut evaluation_stack
773 )
774 .unwrap());
775
776 assert!(
777 !negated_many_val_clause
778 .matches(&matching_context, &TestStore {}, &mut evaluation_stack)
779 .unwrap(),
780 "requires all values are missing"
781 );
782 assert!(negated_many_val_clause
783 .matches(&non_matching_context, &TestStore {}, &mut evaluation_stack)
784 .unwrap());
785
786 assert!(
787 !negated_many_val_clause
788 .matches(
789 &context_without_attribute,
790 &TestStore {},
791 &mut evaluation_stack
792 )
793 .unwrap(),
794 "targeting missing attribute does not match even when negated"
795 );
796
797 assert!(
798 key_clause
799 .matches(&matching_context, &TestStore {}, &mut evaluation_stack)
800 .unwrap(),
801 "should match key"
802 );
803 assert!(
804 !key_clause
805 .matches(&non_matching_context, &TestStore {}, &mut evaluation_stack)
806 .unwrap(),
807 "should not match non-matching key"
808 );
809
810 context_builder.key("with-many").set_value(
811 "a",
812 AttributeValue::Array(vec![
813 AttributeValue::String("foo".to_string()),
814 AttributeValue::String("bar".to_string()),
815 AttributeValue::String("lol".to_string()),
816 ]),
817 );
818 let context_with_many = context_builder.build().expect("Failed to build context");
819
820 assert!(one_val_clause
821 .matches(&context_with_many, &TestStore {}, &mut evaluation_stack)
822 .unwrap());
823 assert!(many_val_clause
824 .matches(&context_with_many, &TestStore {}, &mut evaluation_stack)
825 .unwrap());
826
827 assert!(!negated_clause
828 .matches(&context_with_many, &TestStore {}, &mut evaluation_stack)
829 .unwrap());
830 assert!(!negated_many_val_clause
831 .matches(&context_with_many, &TestStore {}, &mut evaluation_stack)
832 .unwrap());
833 }
834
835 struct AttributeTestCase {
836 matching_context: Context,
837 non_matching_context: Context,
838 context_without_attribute: Option<Context>,
839 }
840
841 #[test]
842 fn test_clause_matches_attributes() {
843 let tests: HashMap<&str, AttributeTestCase> = hashmap! {
844 "key" => AttributeTestCase {
845 matching_context: ContextBuilder::new("match").build().unwrap(),
846 non_matching_context: ContextBuilder::new("nope").build().unwrap(),
847 context_without_attribute: None,
848 },
849 "name" => AttributeTestCase {
850 matching_context: ContextBuilder::new("matching").name("match").build().unwrap(),
851 non_matching_context: ContextBuilder::new("non-matching").name("nope").build().unwrap(),
852 context_without_attribute: Some(ContextBuilder::new("without-attribute").build().unwrap()),
853 },
854 };
855
856 let mut evaluation_stack = EvaluationStack::default();
857
858 for (attr, test_case) in tests {
859 let clause = Clause {
860 attribute: Reference::new(attr),
861 negate: false,
862 op: Op::In,
863 values: vec!["match".into()],
864 context_kind: Kind::default(),
865 };
866
867 assert!(
868 clause
869 .matches(
870 &test_case.matching_context,
871 &TestStore {},
872 &mut evaluation_stack
873 )
874 .unwrap(),
875 "should match {}",
876 attr
877 );
878 assert!(
879 !clause
880 .matches(
881 &test_case.non_matching_context,
882 &TestStore {},
883 &mut evaluation_stack
884 )
885 .unwrap(),
886 "should not match non-matching {}",
887 attr
888 );
889 if let Some(context_without_attribute) = test_case.context_without_attribute {
890 assert!(
891 !clause
892 .matches(
893 &context_without_attribute,
894 &TestStore {},
895 &mut evaluation_stack
896 )
897 .unwrap(),
898 "should not match user with null {}",
899 attr
900 );
901 }
902 }
903 }
904
905 #[test]
906 fn test_clause_matches_anonymous_attribute() {
907 let clause = Clause {
908 attribute: Reference::new("anonymous"),
909 negate: false,
910 op: Op::In,
911 values: vec![true.into()],
912 context_kind: Kind::default(),
913 };
914
915 let anon_context = ContextBuilder::new("anon").anonymous(true).build().unwrap();
916 let non_anon_context = ContextBuilder::new("nonanon")
917 .anonymous(false)
918 .build()
919 .unwrap();
920 let implicitly_non_anon_context = ContextBuilder::new("implicit").build().unwrap();
921
922 let mut evaluation_stack = EvaluationStack::default();
923 assert!(clause
924 .matches(&anon_context, &TestStore {}, &mut evaluation_stack)
925 .unwrap());
926 assert!(!clause
927 .matches(&non_anon_context, &TestStore {}, &mut evaluation_stack)
928 .unwrap());
929 assert!(!clause
930 .matches(
931 &implicitly_non_anon_context,
932 &TestStore {},
933 &mut evaluation_stack
934 )
935 .unwrap());
936 }
937
938 #[test]
939 fn test_clause_matches_custom_attributes() {
940 for attr in &["custom", "custom1"] {
942 let clause = Clause {
943 attribute: Reference::new(attr),
944 negate: false,
945 op: Op::In,
946 values: vec!["match".into()],
947 context_kind: Kind::default(),
948 };
949
950 let matching_context = ContextBuilder::new("matching")
951 .set_value(attr, AttributeValue::String("match".into()))
952 .build()
953 .unwrap();
954 let non_matching_context = ContextBuilder::new("non-matching")
955 .set_value(attr, AttributeValue::String("nope".into()))
956 .build()
957 .unwrap();
958 let context_without_attribute = ContextBuilder::new("without_attribute")
959 .set_value(attr, AttributeValue::Null)
960 .build()
961 .unwrap();
962
963 let mut evaluation_stack = EvaluationStack::default();
964 assert!(
965 clause
966 .matches(&matching_context, &TestStore {}, &mut evaluation_stack)
967 .unwrap(),
968 "should match {}",
969 attr
970 );
971 assert!(
972 !clause
973 .matches(&non_matching_context, &TestStore {}, &mut evaluation_stack)
974 .unwrap(),
975 "should not match non-matching {}",
976 attr
977 );
978 assert!(
979 !clause
980 .matches(
981 &context_without_attribute,
982 &TestStore {},
983 &mut evaluation_stack
984 )
985 .unwrap(),
986 "should not match user with null {}",
987 attr
988 );
989 }
990 }
991
992 #[test]
993 fn test_null_attribute() {
994 let context_null_attr = ContextBuilder::new("key")
995 .set_value("attr", AttributeValue::Null)
996 .build()
997 .unwrap();
998
999 let context_missing_attr = ContextBuilder::new("key").build().unwrap();
1000
1001 let clause_values = vec![
1002 AttributeValue::Bool(true),
1003 AttributeValue::Bool(false),
1004 AttributeValue::Number(1.5),
1005 AttributeValue::Number(1.0),
1006 AttributeValue::Null,
1007 AttributeValue::String("abc".to_string()),
1008 AttributeValue::Array(vec![
1009 AttributeValue::String("def".to_string()),
1010 AttributeValue::Null,
1011 ]),
1012 ];
1013
1014 for op in &[
1015 Op::In,
1016 Op::StartsWith,
1017 Op::EndsWith,
1018 Op::Contains,
1019 Op::Matches,
1020 Op::LessThan,
1021 Op::LessThanOrEqual,
1022 Op::GreaterThan,
1023 Op::GreaterThanOrEqual,
1024 Op::Before,
1025 Op::After,
1026 Op::SemVerEqual,
1027 Op::SemVerGreaterThan,
1028 Op::SemVerLessThan,
1029 ] {
1030 for neg in &[true, false] {
1031 let clause = Clause {
1032 attribute: Reference::new("attr"),
1033 negate: *neg,
1034 op: *op,
1035 values: clause_values.clone(),
1036 context_kind: Kind::default(),
1037 };
1038 let mut evaluation_stack = EvaluationStack::default();
1039 assert!(
1040 !clause
1041 .matches(&context_null_attr, &TestStore {}, &mut evaluation_stack)
1042 .unwrap(),
1043 "Null attribute matches operator {:?} when {}negated",
1044 clause.op,
1045 if *neg { "" } else { "not " },
1046 );
1047 assert!(
1048 !clause
1049 .matches(&context_missing_attr, &TestStore {}, &mut evaluation_stack)
1050 .unwrap(),
1051 "Missing attribute matches operator {:?} when {}negated",
1052 clause.op,
1053 if *neg { "" } else { "not " },
1054 );
1055 }
1056 }
1057 }
1058
1059 fn clause_test_case<S, T>(op: Op, context_value: S, clause_value: T, expected: bool)
1062 where
1063 AttributeValue: From<S>,
1064 AttributeValue: From<T>,
1065 S: Clone,
1066 T: Clone,
1067 {
1068 let clause = Clause {
1069 attribute: Reference::new("attr"),
1070 negate: false,
1071 op,
1072 values: match clause_value.into() {
1073 AttributeValue::Array(vec) => vec,
1074 other => vec![other],
1075 },
1076 context_kind: Kind::default(),
1077 };
1078
1079 let context = ContextBuilder::new("key")
1080 .set_value("attr", context_value.into())
1081 .build()
1082 .unwrap();
1083
1084 let mut evaluation_stack = EvaluationStack::default();
1085 assert_eq!(
1086 clause
1087 .matches(&context, &TestStore {}, &mut evaluation_stack)
1088 .unwrap(),
1089 expected,
1090 "{:?} {:?} {:?} should be {}",
1091 context.get_value(&Reference::new("attr")).unwrap(),
1092 clause.op,
1093 clause.values,
1094 &expected
1095 );
1096 }
1097
1098 #[test]
1099 fn match_is_false_on_invalid_reference() {
1100 let clause = Clause {
1101 attribute: Reference::new("/"),
1102 negate: false,
1103 op: Op::In,
1104 values: vec![],
1105 context_kind: Kind::default(),
1106 };
1107
1108 let context = ContextBuilder::new("key")
1109 .set_value("attr", true.into())
1110 .build()
1111 .unwrap();
1112 let mut evaluation_stack = EvaluationStack::default();
1113 assert!(clause
1114 .matches(&context, &TestStore {}, &mut evaluation_stack)
1115 .is_err());
1116 }
1117
1118 #[test]
1119 fn match_is_false_no_context_matches() {
1120 let clause = Clause {
1121 attribute: Reference::new("attr"),
1122 negate: false,
1123 op: Op::In,
1124 values: vec![true.into()],
1125 context_kind: Kind::default(),
1126 };
1127
1128 let context = ContextBuilder::new("key")
1129 .kind("org")
1130 .set_value("attr", true.into())
1131 .build()
1132 .unwrap();
1133 let mut evaluation_stack = EvaluationStack::default();
1134 assert!(!clause
1135 .matches(&context, &TestStore {}, &mut evaluation_stack)
1136 .unwrap());
1137 }
1138
1139 #[test]
1140 fn test_numeric_clauses() {
1141 clause_test_case(Op::In, 99, 99, true);
1142 clause_test_case(Op::In, 99.0, 99, true);
1143 clause_test_case(Op::In, 99, 99.0, true);
1144 clause_test_case(Op::In, 99, vec![99, 98, 97, 96], true);
1145 clause_test_case(Op::In, 99.0001, 99.0001, true);
1146 clause_test_case(Op::In, 99.0001, vec![99.0001, 98.0, 97.0, 96.0], true);
1147 clause_test_case(Op::LessThan, 1, 1.99999, true);
1148 clause_test_case(Op::LessThan, 1.99999, 1, false);
1149 clause_test_case(Op::LessThan, 1, 2, true);
1150 clause_test_case(Op::LessThanOrEqual, 1, 1.0, true);
1151 clause_test_case(Op::GreaterThan, 2, 1.99999, true);
1152 clause_test_case(Op::GreaterThan, 1.99999, 2, false);
1153 clause_test_case(Op::GreaterThan, 2, 1, true);
1154 clause_test_case(Op::GreaterThanOrEqual, 1, 1.0, true);
1155 }
1156
1157 #[test]
1158 fn test_string_clauses() {
1159 clause_test_case(Op::In, "x", "x", true);
1160 clause_test_case(Op::In, "x", vec!["x", "a", "b", "c"], true);
1161 clause_test_case(Op::In, "x", "xyz", false);
1162 clause_test_case(Op::StartsWith, "xyz", "x", true);
1163 clause_test_case(Op::StartsWith, "x", "xyz", false);
1164 clause_test_case(Op::EndsWith, "xyz", "z", true);
1165 clause_test_case(Op::EndsWith, "z", "xyz", false);
1166 clause_test_case(Op::Contains, "xyz", "y", true);
1167 clause_test_case(Op::Contains, "y", "xyz", false);
1168 }
1169
1170 #[test]
1171 fn test_mixed_string_and_numbers() {
1172 clause_test_case(Op::In, "99", 99, false);
1173 clause_test_case(Op::In, 99, "99", false);
1174 clause_test_case(Op::Contains, "99", 99, false);
1175 clause_test_case(Op::StartsWith, "99", 99, false);
1176 clause_test_case(Op::EndsWith, "99", 99, false);
1177 clause_test_case(Op::LessThanOrEqual, "99", 99, false);
1178 clause_test_case(Op::LessThanOrEqual, 99, "99", false);
1179 clause_test_case(Op::GreaterThanOrEqual, "99", 99, false);
1180 clause_test_case(Op::GreaterThanOrEqual, 99, "99", false);
1181 }
1182
1183 #[test]
1184 fn test_boolean_equality() {
1185 clause_test_case(Op::In, true, true, true);
1186 clause_test_case(Op::In, false, false, true);
1187 clause_test_case(Op::In, true, false, false);
1188 clause_test_case(Op::In, false, true, false);
1189 clause_test_case(Op::In, true, vec![false, true], true);
1190 }
1191
1192 #[test]
1193 fn test_array_equality() {
1194 clause_test_case(Op::In, vec![vec!["x"]], vec![vec!["x"]], true);
1197 clause_test_case(Op::In, vec![vec!["x"]], vec!["x"], false);
1198 clause_test_case(
1199 Op::In,
1200 vec![vec!["x"]],
1201 vec![vec!["x"], vec!["a"], vec!["b"]],
1202 true,
1203 );
1204 }
1205
1206 #[test]
1207 fn test_object_equality() {
1208 clause_test_case(Op::In, hashmap! {"x" => "1"}, hashmap! {"x" => "1"}, true);
1209 clause_test_case(
1210 Op::In,
1211 hashmap! {"x" => "1"},
1212 vec![
1213 hashmap! {"x" => "1"},
1214 hashmap! {"a" => "2"},
1215 hashmap! {"b" => "3"},
1216 ],
1217 true,
1218 );
1219 }
1220
1221 #[test]
1222 fn test_regex_match() {
1223 clause_test_case(Op::Matches, "hello world", "hello.*rld", true);
1224 clause_test_case(Op::Matches, "hello world", "hello.*orl", true);
1225 clause_test_case(Op::Matches, "hello world", "l+", true);
1226 clause_test_case(Op::Matches, "hello world", "(world|planet)", true);
1227 clause_test_case(Op::Matches, "hello world", "aloha", false);
1228 clause_test_case(Op::Matches, "hello world", "***bad regex", false);
1229 }
1230
1231 #[test]
1232 fn test_date_clauses() {
1233 const DATE_STR1: &str = "2017-12-06T00:00:00.000-07:00";
1234 const DATE_STR2: &str = "2017-12-06T00:01:01.000-07:00";
1235 const DATE_MS1: i64 = 10000000;
1236 const DATE_MS2: i64 = 10000001;
1237 const INVALID_DATE: &str = "hey what's this?";
1238
1239 clause_test_case(Op::Before, DATE_STR1, DATE_STR2, true);
1240 clause_test_case(Op::Before, DATE_MS1, DATE_MS2, true);
1241 clause_test_case(Op::Before, DATE_STR2, DATE_STR1, false);
1242 clause_test_case(Op::Before, DATE_MS2, DATE_MS1, false);
1243 clause_test_case(Op::Before, DATE_STR1, DATE_STR1, false);
1244 clause_test_case(Op::Before, DATE_MS1, DATE_MS1, false);
1245 clause_test_case(Op::Before, AttributeValue::Null, DATE_STR1, false);
1246 clause_test_case(Op::Before, DATE_STR1, INVALID_DATE, false);
1247 clause_test_case(Op::After, DATE_STR2, DATE_STR1, true);
1248 clause_test_case(Op::After, DATE_MS2, DATE_MS1, true);
1249 clause_test_case(Op::After, DATE_STR1, DATE_STR2, false);
1250 clause_test_case(Op::After, DATE_MS1, DATE_MS2, false);
1251 clause_test_case(Op::After, DATE_STR1, DATE_STR1, false);
1252 clause_test_case(Op::After, DATE_MS1, DATE_MS1, false);
1253 clause_test_case(Op::After, AttributeValue::Null, DATE_STR1, false);
1254 clause_test_case(Op::After, DATE_STR1, INVALID_DATE, false);
1255 }
1256
1257 #[test]
1258 fn test_semver_clauses() {
1259 clause_test_case(Op::SemVerEqual, "2.0.0", "2.0.0", true);
1260 clause_test_case(Op::SemVerEqual, "2.0", "2.0.0", true);
1261 clause_test_case(Op::SemVerEqual, "2-rc1", "2.0.0-rc1", true);
1262 clause_test_case(Op::SemVerEqual, "2+build2", "2.0.0+build2", true);
1263 clause_test_case(Op::SemVerEqual, "2.0.0", "2.0.1", false);
1264 clause_test_case(Op::SemVerLessThan, "2.0.0", "2.0.1", true);
1265 clause_test_case(Op::SemVerLessThan, "2.0", "2.0.1", true);
1266 clause_test_case(Op::SemVerLessThan, "2.0.1", "2.0.0", false);
1267 clause_test_case(Op::SemVerLessThan, "2.0.1", "2.0", false);
1268 clause_test_case(Op::SemVerLessThan, "2.0.1", "xbad%ver", false);
1269 clause_test_case(Op::SemVerLessThan, "2.0.0-rc", "2.0.0-rc.beta", true);
1270 clause_test_case(Op::SemVerGreaterThan, "2.0.1", "2.0", true);
1271 clause_test_case(Op::SemVerGreaterThan, "10.0.1", "2.0", true);
1272 clause_test_case(Op::SemVerGreaterThan, "2.0.0", "2.0.1", false);
1273 clause_test_case(Op::SemVerGreaterThan, "2.0", "2.0.1", false);
1274 clause_test_case(Op::SemVerGreaterThan, "2.0.1", "xbad%ver", false);
1275 clause_test_case(Op::SemVerGreaterThan, "2.0.0-rc.1", "2.0.0-rc.0", true);
1276 }
1277
1278 #[test]
1279 fn clause_deserialize_with_attribute_missing_causes_error() {
1280 let attribute_missing = json!({
1281 "op" : "in",
1282 "values" : [],
1283 });
1284 assert!(serde_json::from_value::<IntermediateClause>(attribute_missing).is_err());
1285 }
1286
1287 #[test]
1288 fn clause_deserialize_with_op_missing_causes_error() {
1289 let op_missing = json!({
1290 "values" : [],
1291 "attribute" : "",
1292 });
1293 assert!(serde_json::from_value::<IntermediateClause>(op_missing).is_err());
1294 }
1295
1296 #[test]
1297 fn clause_deserialize_with_values_missing_causes_error() {
1298 let values_missing = json!({
1299 "op" : "in",
1300 "values" : [],
1301 });
1302 assert!(serde_json::from_value::<IntermediateClause>(values_missing).is_err());
1303 }
1304
1305 #[test]
1306 fn clause_deserialize_with_required_fields_parses_successfully() {
1307 let all_required_fields_present = json!({
1308 "attribute" : "",
1309 "op" : "in",
1310 "values" : [],
1311 });
1312
1313 assert_eq!(
1314 serde_json::from_value::<IntermediateClause>(all_required_fields_present).unwrap(),
1315 IntermediateClause::ContextOblivious(ClauseWithoutKind {
1316 attribute: AttributeName::default(),
1317 negate: false,
1318 op: Op::In,
1319 values: vec![]
1320 })
1321 );
1322 }
1323
1324 proptest! {
1325 #[test]
1326 fn arbitrary_clause_serialization_rountrip(clause in any_clause()) {
1327 let json = serde_json::to_value(clause).expect("a clause should serialize");
1328 let parsed: Clause = serde_json::from_value(json.clone()).expect("a clause should parse");
1329 assert_json_eq!(json, parsed);
1330 }
1331 }
1332
1333 #[test]
1334 fn clause_with_negate_omitted_defaults_to_false() {
1335 let negate_omitted = json!({
1336 "attribute" : "",
1337 "op" : "in",
1338 "values" : [],
1339 });
1340
1341 assert!(
1342 !serde_json::from_value::<Clause>(negate_omitted)
1343 .unwrap()
1344 .negate
1345 )
1346 }
1347
1348 #[test]
1349 fn clause_with_empty_attribute_defaults_to_invalid_attribute() {
1350 let empty_attribute = json!({
1351 "attribute" : "",
1352 "op" : "in",
1353 "values" : [],
1354 });
1355
1356 let attr = serde_json::from_value::<Clause>(empty_attribute)
1357 .unwrap()
1358 .attribute;
1359 assert_eq!(Reference::default(), attr);
1360 }
1361
1362 proptest! {
1363 #[test]
1364 fn clause_with_context_kind_implies_attribute_references(arbitrary_attribute in any::<String>()) {
1365 let with_context_kind = json!({
1366 "attribute" : arbitrary_attribute,
1367 "op" : "in",
1368 "values" : [],
1369 "contextKind" : "user",
1370 });
1371
1372 prop_assert_eq!(
1373 Reference::new(arbitrary_attribute),
1374 serde_json::from_value::<Clause>(with_context_kind)
1375 .unwrap()
1376 .attribute
1377 )
1378 }
1379 }
1380
1381 proptest! {
1382 #[test]
1383 fn clause_without_context_kind_implies_literal_attribute_name(arbitrary_attribute in any_valid_ref_string()) {
1384 let without_context_kind = json!({
1385 "attribute" : arbitrary_attribute,
1386 "op" : "in",
1387 "values" : [],
1388 });
1389
1390 prop_assert_eq!(
1391 Reference::from(AttributeName::new(arbitrary_attribute)),
1392 serde_json::from_value::<Clause>(without_context_kind)
1393 .unwrap()
1394 .attribute
1395 );
1396 }
1397 }
1398}