Skip to main content

launchdarkly_server_sdk_evaluation/
rule.rs

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/// Clause describes an individual clause within a [crate::FlagRule] or `SegmentRule`.
15// Clause is deserialized via a helper, IntermediateClause, because of semantic ambiguity
16// of the attribute Reference field.
17//
18// Clause implements Serialize directly without a helper because References can serialize
19// themselves without any ambiguity.
20#[skip_serializing_none]
21#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
22#[serde(rename_all = "camelCase", from = "IntermediateClause")]
23pub struct Clause {
24    // Kind associated with this clause.
25    context_kind: Kind,
26    // Which context attribute to test. If op does not require an attribute,
27    // then the input may be an empty string, which will construct an invalid
28    // reference.
29    attribute: Reference,
30    // True if the result of the test should be negated.
31    // Skip serializing if false because it is optional in the JSON model.
32    #[serde(skip_serializing_if = "is_false")]
33    negate: bool,
34    // The test operation.
35    op: Op,
36    // The values to test against.
37    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    // ClauseWithKind must be listed first in the enum because otherwise ClauseWithoutKind
65    // could match the input (by ignoring/discarding the context_kind field).
66    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        // Generate arbitrary clauses. The clauses op will always be fixed to Op::In,
102        // and the values array will contain between 0-5 AtrributeValue::Bool elements.
103        pub(crate) fn any_clause()(
104            kind in any_kind(),
105            // reference is any_ref(), rather than any_valid_ref(), because we also want
106            // coverage of invalid references.
107            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/// FlagRule describes a single rule within a feature flag.
124///
125/// A rule consists of a set of ANDed matching conditions ([Clause]) for a context, along with either a
126/// fixed variation or a set of rollout percentages to use if the context matches all of the clauses.
127#[derive(Clone, Debug, Serialize, Deserialize)]
128#[serde(rename_all = "camelCase")]
129pub struct FlagRule {
130    /// A randomized identifier assigned to each rule when it is created.
131    ///
132    /// This is used to populate the id property of [crate::Reason]
133    #[serde(default)]
134    pub id: String,
135    clauses: Vec<Clause>,
136
137    /// Defines what variation to return if the context matches this rule.
138    #[serde(flatten)]
139    pub variation_or_rollout: VariationOrRollout,
140
141    /// Used internally by the SDK analytics event system.
142    ///
143    /// This field is true if the current LaunchDarkly account has experimentation enabled, has
144    /// associated this flag with an experiment, and has enabled this rule for the experiment. This
145    /// tells the SDK to send full event data for any evaluation that matches this rule.
146    ///
147    /// The launchdarkly-server-sdk-evaluation package does not implement that behavior; it is only
148    /// in the data model for use by the SDK.
149    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    // Use when matching a clause that has an associated context kind.
268    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    // Use when matching a clause that isn't context-aware.
280    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    /// Determines if a context matches the provided flag rule.
293    ///
294    /// A context will match if all flag clauses match; otherwise, this method returns false.
295    pub(crate) fn matches(
296        &self,
297        context: &Context,
298        store: &dyn Store,
299        evaluation_stack: &mut EvaluationStack,
300    ) -> Result<bool, String> {
301        // rules match if _all_ of their clauses do
302        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            // string ops
338            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            // numeric ops
350            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        // strings
439        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        // numbers
448        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        // arrays
453        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        // objects
460        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        // degenerate cases
476        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        // test asymmetry
481        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        // degenerate cases
493        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        // test asymmetry
498        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        // degenerate cases
510        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        // test asymmetry
515        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                "`{text}` should match `{pattern}`"
530            );
531        }
532
533        fn should_not_match(text: &str, pattern: &str) {
534            assert!(
535                !Op::Matches.matches(&astring(text), &astring(pattern)),
536                "`{text}` should not match `{pattern}`"
537            );
538        }
539
540        should_match("", "");
541        should_match("a", "");
542        should_match("a", "a");
543        should_match("a", ".");
544        should_match("hello world", "hello.*rld");
545        should_match("hello world", "hello.*orl");
546        should_match("hello world", "l+");
547        should_match("hello world", "(world|planet)");
548
549        should_not_match("", ".");
550        should_not_match("", r"\");
551        should_not_match("hello world", "aloha");
552        should_not_match("hello world", "***bad regex");
553    }
554
555    #[test]
556    fn test_ops_numeric() {
557        // basic numeric comparisons
558        assert!(Op::LessThan.matches(&anum(0.0), &anum(1.0)));
559        assert!(!Op::LessThan.matches(&anum(0.0), &anum(0.0)));
560        assert!(!Op::LessThan.matches(&anum(1.0), &anum(0.0)));
561
562        assert!(Op::GreaterThan.matches(&anum(1.0), &anum(0.0)));
563        assert!(!Op::GreaterThan.matches(&anum(0.0), &anum(0.0)));
564        assert!(!Op::GreaterThan.matches(&anum(0.0), &anum(1.0)));
565
566        assert!(Op::LessThanOrEqual.matches(&anum(0.0), &anum(1.0)));
567        assert!(Op::LessThanOrEqual.matches(&anum(0.0), &anum(0.0)));
568        assert!(!Op::LessThanOrEqual.matches(&anum(1.0), &anum(0.0)));
569
570        assert!(Op::GreaterThanOrEqual.matches(&anum(1.0), &anum(0.0)));
571        assert!(Op::GreaterThanOrEqual.matches(&anum(0.0), &anum(0.0)));
572        assert!(!Op::GreaterThanOrEqual.matches(&anum(0.0), &anum(1.0)));
573
574        // no conversions
575        assert!(!Op::LessThan.matches(&astring("0"), &anum(1.0)));
576        assert!(!Op::LessThan.matches(&anum(0.0), &astring("1")));
577    }
578
579    #[test]
580    fn test_ops_time() {
581        let today = SystemTime::now();
582        let today_millis = today
583            .duration_since(SystemTime::UNIX_EPOCH)
584            .unwrap()
585            .as_millis() as f64;
586        let yesterday_millis = today_millis - 86_400_000_f64;
587
588        // basic UNIX timestamp comparisons
589        assert!(Op::Before.matches(&anum(yesterday_millis), &anum(today_millis)));
590        assert!(!Op::Before.matches(&anum(today_millis), &anum(yesterday_millis)));
591        assert!(!Op::Before.matches(&anum(today_millis), &anum(today_millis)));
592
593        assert!(Op::After.matches(&anum(today_millis), &anum(yesterday_millis)));
594        assert!(!Op::After.matches(&anum(yesterday_millis), &anum(today_millis)));
595        assert!(!Op::After.matches(&anum(today_millis), &anum(today_millis)));
596
597        // numeric strings don't get converted to millis
598        assert!(!Op::Before.matches(&astring(&yesterday_millis.to_string()), &anum(today_millis)));
599        assert!(!Op::After.matches(&anum(today_millis), &astring(&yesterday_millis.to_string())));
600
601        // date-formatted strings get parsed
602        assert!(Op::Before.matches(
603            &astring("2019-11-19T17:29:00.000000-07:00"),
604            &anum(today_millis)
605        ));
606        assert!(
607            Op::Before.matches(&astring("2019-11-19T17:29:00-07:00"), &anum(today_millis)),
608            "fractional seconds part is optional"
609        );
610
611        assert!(Op::After.matches(
612            &anum(today_millis),
613            &astring("2019-11-19T17:29:00.000000-07:00")
614        ));
615
616        // nonsense strings don't match
617        assert!(!Op::Before.matches(&astring("fish"), &anum(today_millis)));
618        assert!(!Op::After.matches(&anum(today_millis), &astring("fish")));
619    }
620
621    #[test]
622    fn test_semver_ops() {
623        assert!(Op::SemVerEqual.matches(&astring("2.0.0"), &astring("2.0.0")));
624
625        assert!(
626            Op::SemVerEqual.matches(&astring("2.0"), &astring("2.0.0")),
627            "we allow missing components (filled in with zeroes)"
628        );
629        assert!(
630            Op::SemVerEqual.matches(&astring("2"), &astring("2.0.0")),
631            "we allow missing components (filled in with zeroes)"
632        );
633
634        assert!(!Op::SemVerEqual.matches(&astring("2.0.0"), &astring("3.0.0")));
635        assert!(!Op::SemVerEqual.matches(&astring("2.0.0"), &astring("2.1.0")));
636        assert!(!Op::SemVerEqual.matches(&astring("2.0.0"), &astring("2.0.1")));
637
638        assert!(Op::SemVerGreaterThan.matches(&astring("3.0.0"), &astring("2.0.0")));
639        assert!(Op::SemVerGreaterThan.matches(&astring("2.1.0"), &astring("2.0.0")));
640        assert!(Op::SemVerGreaterThan.matches(&astring("2.0.1"), &astring("2.0.0")));
641        assert!(Op::SemVerGreaterThan
642            .matches(&astring("2.0.0-rc.10.green"), &astring("2.0.0-rc.2.green")));
643        assert!(
644            Op::SemVerGreaterThan.matches(&astring("2.0.0-rc.2.red"), &astring("2.0.0-rc.2.green")),
645            "red > green"
646        );
647        assert!(
648            Op::SemVerGreaterThan
649                .matches(&astring("2.0.0-rc.2.green.1"), &astring("2.0.0-rc.2.green")),
650            "adding more version components makes it greater"
651        );
652
653        assert!(!Op::SemVerGreaterThan.matches(&astring("2.0.0"), &astring("2.0.0")));
654        assert!(!Op::SemVerGreaterThan.matches(&astring("1.9.0"), &astring("2.0.0")));
655        assert!(
656            !Op::SemVerGreaterThan.matches(&astring("2.0.0-rc"), &astring("2.0.0")),
657            "prerelease version < released version"
658        );
659        assert!(
660            !Op::SemVerGreaterThan.matches(&astring("2.0.0+build"), &astring("2.0.0")),
661            "build metadata is ignored, these versions are equal"
662        );
663
664        assert!(!Op::SemVerEqual.matches(&astring("2.0.0"), &astring("200")));
665
666        // we don't convert
667        assert!(!Op::SemVerEqual.matches(&astring("2.0.0"), &anum(2.0)));
668    }
669
670    #[test]
671    fn test_clause_matches() {
672        let one_val_clause = Clause {
673            attribute: Reference::new("a"),
674            negate: false,
675            op: Op::In,
676            values: vec!["foo".into()],
677            context_kind: Kind::default(),
678        };
679        let many_val_clause = Clause {
680            attribute: Reference::new("a"),
681            negate: false,
682            op: Op::In,
683            values: vec!["foo".into(), "bar".into()],
684            context_kind: Kind::default(),
685        };
686        let negated_clause = Clause {
687            attribute: Reference::new("a"),
688            negate: true,
689            op: Op::In,
690            values: vec!["foo".into()],
691            context_kind: Kind::default(),
692        };
693        let negated_many_val_clause = Clause {
694            attribute: Reference::new("a"),
695            negate: true,
696            op: Op::In,
697            values: vec!["foo".into(), "bar".into()],
698            context_kind: Kind::default(),
699        };
700        let key_clause = Clause {
701            attribute: Reference::new("key"),
702            negate: false,
703            op: Op::In,
704            values: vec!["matching".into()],
705            context_kind: Kind::default(),
706        };
707
708        let mut context_builder = ContextBuilder::new("without");
709        let context_without_attribute = context_builder.build().expect("Failed to build context");
710
711        context_builder
712            .key("matching")
713            .set_value("a", AttributeValue::String("foo".to_string()));
714        let matching_context = context_builder.build().expect("Failed to build context");
715
716        context_builder
717            .key("non-matching")
718            .set_value("a", AttributeValue::String("lol".to_string()));
719        let non_matching_context = context_builder.build().expect("Failed to build context");
720
721        let mut evaluation_stack = EvaluationStack::default();
722
723        assert!(one_val_clause
724            .matches(&matching_context, &TestStore {}, &mut evaluation_stack)
725            .unwrap());
726        assert!(!one_val_clause
727            .matches(&non_matching_context, &TestStore {}, &mut evaluation_stack)
728            .unwrap());
729        assert!(!one_val_clause
730            .matches(
731                &context_without_attribute,
732                &TestStore {},
733                &mut evaluation_stack
734            )
735            .unwrap());
736
737        assert!(!negated_clause
738            .matches(&matching_context, &TestStore {}, &mut evaluation_stack)
739            .unwrap());
740        assert!(negated_clause
741            .matches(&non_matching_context, &TestStore {}, &mut evaluation_stack)
742            .unwrap());
743
744        assert!(
745            !negated_clause
746                .matches(
747                    &context_without_attribute,
748                    &TestStore {},
749                    &mut evaluation_stack
750                )
751                .unwrap(),
752            "targeting missing attribute does not match even when negated"
753        );
754
755        assert!(
756            many_val_clause
757                .matches(&matching_context, &TestStore {}, &mut evaluation_stack)
758                .unwrap(),
759            "requires only one of the values"
760        );
761        assert!(!many_val_clause
762            .matches(&non_matching_context, &TestStore {}, &mut evaluation_stack)
763            .unwrap());
764        assert!(!many_val_clause
765            .matches(
766                &context_without_attribute,
767                &TestStore {},
768                &mut evaluation_stack
769            )
770            .unwrap());
771
772        assert!(
773            !negated_many_val_clause
774                .matches(&matching_context, &TestStore {}, &mut evaluation_stack)
775                .unwrap(),
776            "requires all values are missing"
777        );
778        assert!(negated_many_val_clause
779            .matches(&non_matching_context, &TestStore {}, &mut evaluation_stack)
780            .unwrap());
781
782        assert!(
783            !negated_many_val_clause
784                .matches(
785                    &context_without_attribute,
786                    &TestStore {},
787                    &mut evaluation_stack
788                )
789                .unwrap(),
790            "targeting missing attribute does not match even when negated"
791        );
792
793        assert!(
794            key_clause
795                .matches(&matching_context, &TestStore {}, &mut evaluation_stack)
796                .unwrap(),
797            "should match key"
798        );
799        assert!(
800            !key_clause
801                .matches(&non_matching_context, &TestStore {}, &mut evaluation_stack)
802                .unwrap(),
803            "should not match non-matching key"
804        );
805
806        context_builder.key("with-many").set_value(
807            "a",
808            AttributeValue::Array(vec![
809                AttributeValue::String("foo".to_string()),
810                AttributeValue::String("bar".to_string()),
811                AttributeValue::String("lol".to_string()),
812            ]),
813        );
814        let context_with_many = context_builder.build().expect("Failed to build context");
815
816        assert!(one_val_clause
817            .matches(&context_with_many, &TestStore {}, &mut evaluation_stack)
818            .unwrap());
819        assert!(many_val_clause
820            .matches(&context_with_many, &TestStore {}, &mut evaluation_stack)
821            .unwrap());
822
823        assert!(!negated_clause
824            .matches(&context_with_many, &TestStore {}, &mut evaluation_stack)
825            .unwrap());
826        assert!(!negated_many_val_clause
827            .matches(&context_with_many, &TestStore {}, &mut evaluation_stack)
828            .unwrap());
829    }
830
831    struct AttributeTestCase {
832        matching_context: Context,
833        non_matching_context: Context,
834        context_without_attribute: Option<Context>,
835    }
836
837    #[test]
838    fn test_clause_matches_attributes() {
839        let tests: HashMap<&str, AttributeTestCase> = hashmap! {
840            "key" => AttributeTestCase {
841                matching_context: ContextBuilder::new("match").build().unwrap(),
842                non_matching_context: ContextBuilder::new("nope").build().unwrap(),
843                context_without_attribute: None,
844            },
845            "name" => AttributeTestCase {
846                matching_context: ContextBuilder::new("matching").name("match").build().unwrap(),
847                non_matching_context: ContextBuilder::new("non-matching").name("nope").build().unwrap(),
848                context_without_attribute: Some(ContextBuilder::new("without-attribute").build().unwrap()),
849            },
850        };
851
852        let mut evaluation_stack = EvaluationStack::default();
853
854        for (attr, test_case) in tests {
855            let clause = Clause {
856                attribute: Reference::new(attr),
857                negate: false,
858                op: Op::In,
859                values: vec!["match".into()],
860                context_kind: Kind::default(),
861            };
862
863            assert!(
864                clause
865                    .matches(
866                        &test_case.matching_context,
867                        &TestStore {},
868                        &mut evaluation_stack
869                    )
870                    .unwrap(),
871                "should match {attr}"
872            );
873            assert!(
874                !clause
875                    .matches(
876                        &test_case.non_matching_context,
877                        &TestStore {},
878                        &mut evaluation_stack
879                    )
880                    .unwrap(),
881                "should not match non-matching {attr}"
882            );
883            if let Some(context_without_attribute) = test_case.context_without_attribute {
884                assert!(
885                    !clause
886                        .matches(
887                            &context_without_attribute,
888                            &TestStore {},
889                            &mut evaluation_stack
890                        )
891                        .unwrap(),
892                    "should not match user with null {attr}"
893                );
894            }
895        }
896    }
897
898    #[test]
899    fn test_clause_matches_anonymous_attribute() {
900        let clause = Clause {
901            attribute: Reference::new("anonymous"),
902            negate: false,
903            op: Op::In,
904            values: vec![true.into()],
905            context_kind: Kind::default(),
906        };
907
908        let anon_context = ContextBuilder::new("anon").anonymous(true).build().unwrap();
909        let non_anon_context = ContextBuilder::new("nonanon")
910            .anonymous(false)
911            .build()
912            .unwrap();
913        let implicitly_non_anon_context = ContextBuilder::new("implicit").build().unwrap();
914
915        let mut evaluation_stack = EvaluationStack::default();
916        assert!(clause
917            .matches(&anon_context, &TestStore {}, &mut evaluation_stack)
918            .unwrap());
919        assert!(!clause
920            .matches(&non_anon_context, &TestStore {}, &mut evaluation_stack)
921            .unwrap());
922        assert!(!clause
923            .matches(
924                &implicitly_non_anon_context,
925                &TestStore {},
926                &mut evaluation_stack
927            )
928            .unwrap());
929    }
930
931    #[test]
932    fn test_clause_matches_custom_attributes() {
933        // check we can have an attribute called "custom"
934        for attr in &["custom", "custom1"] {
935            let clause = Clause {
936                attribute: Reference::new(attr),
937                negate: false,
938                op: Op::In,
939                values: vec!["match".into()],
940                context_kind: Kind::default(),
941            };
942
943            let matching_context = ContextBuilder::new("matching")
944                .set_value(attr, AttributeValue::String("match".into()))
945                .build()
946                .unwrap();
947            let non_matching_context = ContextBuilder::new("non-matching")
948                .set_value(attr, AttributeValue::String("nope".into()))
949                .build()
950                .unwrap();
951            let context_without_attribute = ContextBuilder::new("without_attribute")
952                .set_value(attr, AttributeValue::Null)
953                .build()
954                .unwrap();
955
956            let mut evaluation_stack = EvaluationStack::default();
957            assert!(
958                clause
959                    .matches(&matching_context, &TestStore {}, &mut evaluation_stack)
960                    .unwrap(),
961                "should match {attr}"
962            );
963            assert!(
964                !clause
965                    .matches(&non_matching_context, &TestStore {}, &mut evaluation_stack)
966                    .unwrap(),
967                "should not match non-matching {attr}"
968            );
969            assert!(
970                !clause
971                    .matches(
972                        &context_without_attribute,
973                        &TestStore {},
974                        &mut evaluation_stack
975                    )
976                    .unwrap(),
977                "should not match user with null {attr}"
978            );
979        }
980    }
981
982    #[test]
983    fn test_null_attribute() {
984        let context_null_attr = ContextBuilder::new("key")
985            .set_value("attr", AttributeValue::Null)
986            .build()
987            .unwrap();
988
989        let context_missing_attr = ContextBuilder::new("key").build().unwrap();
990
991        let clause_values = vec![
992            AttributeValue::Bool(true),
993            AttributeValue::Bool(false),
994            AttributeValue::Number(1.5),
995            AttributeValue::Number(1.0),
996            AttributeValue::Null,
997            AttributeValue::String("abc".to_string()),
998            AttributeValue::Array(vec![
999                AttributeValue::String("def".to_string()),
1000                AttributeValue::Null,
1001            ]),
1002        ];
1003
1004        for op in &[
1005            Op::In,
1006            Op::StartsWith,
1007            Op::EndsWith,
1008            Op::Contains,
1009            Op::Matches,
1010            Op::LessThan,
1011            Op::LessThanOrEqual,
1012            Op::GreaterThan,
1013            Op::GreaterThanOrEqual,
1014            Op::Before,
1015            Op::After,
1016            Op::SemVerEqual,
1017            Op::SemVerGreaterThan,
1018            Op::SemVerLessThan,
1019        ] {
1020            for neg in &[true, false] {
1021                let clause = Clause {
1022                    attribute: Reference::new("attr"),
1023                    negate: *neg,
1024                    op: *op,
1025                    values: clause_values.clone(),
1026                    context_kind: Kind::default(),
1027                };
1028                let mut evaluation_stack = EvaluationStack::default();
1029                assert!(
1030                    !clause
1031                        .matches(&context_null_attr, &TestStore {}, &mut evaluation_stack)
1032                        .unwrap(),
1033                    "Null attribute matches operator {:?} when {}negated",
1034                    clause.op,
1035                    if *neg { "" } else { "not " },
1036                );
1037                assert!(
1038                    !clause
1039                        .matches(&context_missing_attr, &TestStore {}, &mut evaluation_stack)
1040                        .unwrap(),
1041                    "Missing attribute matches operator {:?} when {}negated",
1042                    clause.op,
1043                    if *neg { "" } else { "not " },
1044                );
1045            }
1046        }
1047    }
1048
1049    // The following test cases are ported from the Go implementation:
1050    // https://github.com/launchdarkly/go-server-sdk-evaluation/blob/v1/ldmodel/match_clause_operator_test.go#L28-L155
1051    fn clause_test_case<S, T>(op: Op, context_value: S, clause_value: T, expected: bool)
1052    where
1053        AttributeValue: From<S>,
1054        AttributeValue: From<T>,
1055        S: Clone,
1056        T: Clone,
1057    {
1058        let clause = Clause {
1059            attribute: Reference::new("attr"),
1060            negate: false,
1061            op,
1062            values: match clause_value.into() {
1063                AttributeValue::Array(vec) => vec,
1064                other => vec![other],
1065            },
1066            context_kind: Kind::default(),
1067        };
1068
1069        let context = ContextBuilder::new("key")
1070            .set_value("attr", context_value.into())
1071            .build()
1072            .unwrap();
1073
1074        let mut evaluation_stack = EvaluationStack::default();
1075        assert_eq!(
1076            clause
1077                .matches(&context, &TestStore {}, &mut evaluation_stack)
1078                .unwrap(),
1079            expected,
1080            "{:?} {:?} {:?} should be {}",
1081            context.get_value(&Reference::new("attr")).unwrap(),
1082            clause.op,
1083            clause.values,
1084            &expected
1085        );
1086    }
1087
1088    #[test]
1089    fn match_is_false_on_invalid_reference() {
1090        let clause = Clause {
1091            attribute: Reference::new("/"),
1092            negate: false,
1093            op: Op::In,
1094            values: vec![],
1095            context_kind: Kind::default(),
1096        };
1097
1098        let context = ContextBuilder::new("key")
1099            .set_value("attr", true.into())
1100            .build()
1101            .unwrap();
1102        let mut evaluation_stack = EvaluationStack::default();
1103        assert!(clause
1104            .matches(&context, &TestStore {}, &mut evaluation_stack)
1105            .is_err());
1106    }
1107
1108    #[test]
1109    fn match_is_false_no_context_matches() {
1110        let clause = Clause {
1111            attribute: Reference::new("attr"),
1112            negate: false,
1113            op: Op::In,
1114            values: vec![true.into()],
1115            context_kind: Kind::default(),
1116        };
1117
1118        let context = ContextBuilder::new("key")
1119            .kind("org")
1120            .set_value("attr", true.into())
1121            .build()
1122            .unwrap();
1123        let mut evaluation_stack = EvaluationStack::default();
1124        assert!(!clause
1125            .matches(&context, &TestStore {}, &mut evaluation_stack)
1126            .unwrap());
1127    }
1128
1129    #[test]
1130    fn test_numeric_clauses() {
1131        clause_test_case(Op::In, 99, 99, true);
1132        clause_test_case(Op::In, 99.0, 99, true);
1133        clause_test_case(Op::In, 99, 99.0, true);
1134        clause_test_case(Op::In, 99, vec![99, 98, 97, 96], true);
1135        clause_test_case(Op::In, 99.0001, 99.0001, true);
1136        clause_test_case(Op::In, 99.0001, vec![99.0001, 98.0, 97.0, 96.0], true);
1137        clause_test_case(Op::LessThan, 1, 1.99999, true);
1138        clause_test_case(Op::LessThan, 1.99999, 1, false);
1139        clause_test_case(Op::LessThan, 1, 2, true);
1140        clause_test_case(Op::LessThanOrEqual, 1, 1.0, true);
1141        clause_test_case(Op::GreaterThan, 2, 1.99999, true);
1142        clause_test_case(Op::GreaterThan, 1.99999, 2, false);
1143        clause_test_case(Op::GreaterThan, 2, 1, true);
1144        clause_test_case(Op::GreaterThanOrEqual, 1, 1.0, true);
1145    }
1146
1147    #[test]
1148    fn test_string_clauses() {
1149        clause_test_case(Op::In, "x", "x", true);
1150        clause_test_case(Op::In, "x", vec!["x", "a", "b", "c"], true);
1151        clause_test_case(Op::In, "x", "xyz", false);
1152        clause_test_case(Op::StartsWith, "xyz", "x", true);
1153        clause_test_case(Op::StartsWith, "x", "xyz", false);
1154        clause_test_case(Op::EndsWith, "xyz", "z", true);
1155        clause_test_case(Op::EndsWith, "z", "xyz", false);
1156        clause_test_case(Op::Contains, "xyz", "y", true);
1157        clause_test_case(Op::Contains, "y", "xyz", false);
1158    }
1159
1160    #[test]
1161    fn test_mixed_string_and_numbers() {
1162        clause_test_case(Op::In, "99", 99, false);
1163        clause_test_case(Op::In, 99, "99", false);
1164        clause_test_case(Op::Contains, "99", 99, false);
1165        clause_test_case(Op::StartsWith, "99", 99, false);
1166        clause_test_case(Op::EndsWith, "99", 99, false);
1167        clause_test_case(Op::LessThanOrEqual, "99", 99, false);
1168        clause_test_case(Op::LessThanOrEqual, 99, "99", false);
1169        clause_test_case(Op::GreaterThanOrEqual, "99", 99, false);
1170        clause_test_case(Op::GreaterThanOrEqual, 99, "99", false);
1171    }
1172
1173    #[test]
1174    fn test_boolean_equality() {
1175        clause_test_case(Op::In, true, true, true);
1176        clause_test_case(Op::In, false, false, true);
1177        clause_test_case(Op::In, true, false, false);
1178        clause_test_case(Op::In, false, true, false);
1179        clause_test_case(Op::In, true, vec![false, true], true);
1180    }
1181
1182    #[test]
1183    fn test_array_equality() {
1184        // note that the user value must be an array *of arrays*, because a single-level
1185        // array is interpreted as "any of these values"
1186        clause_test_case(Op::In, vec![vec!["x"]], vec![vec!["x"]], true);
1187        clause_test_case(Op::In, vec![vec!["x"]], vec!["x"], false);
1188        clause_test_case(
1189            Op::In,
1190            vec![vec!["x"]],
1191            vec![vec!["x"], vec!["a"], vec!["b"]],
1192            true,
1193        );
1194    }
1195
1196    #[test]
1197    fn test_object_equality() {
1198        clause_test_case(Op::In, hashmap! {"x" => "1"}, hashmap! {"x" => "1"}, true);
1199        clause_test_case(
1200            Op::In,
1201            hashmap! {"x" => "1"},
1202            vec![
1203                hashmap! {"x" => "1"},
1204                hashmap! {"a" => "2"},
1205                hashmap! {"b" => "3"},
1206            ],
1207            true,
1208        );
1209    }
1210
1211    #[test]
1212    fn test_regex_match() {
1213        clause_test_case(Op::Matches, "hello world", "hello.*rld", true);
1214        clause_test_case(Op::Matches, "hello world", "hello.*orl", true);
1215        clause_test_case(Op::Matches, "hello world", "l+", true);
1216        clause_test_case(Op::Matches, "hello world", "(world|planet)", true);
1217        clause_test_case(Op::Matches, "hello world", "aloha", false);
1218        clause_test_case(Op::Matches, "hello world", "***bad regex", false);
1219    }
1220
1221    #[test]
1222    fn test_date_clauses() {
1223        const DATE_STR1: &str = "2017-12-06T00:00:00.000-07:00";
1224        const DATE_STR2: &str = "2017-12-06T00:01:01.000-07:00";
1225        const DATE_MS1: i64 = 10000000;
1226        const DATE_MS2: i64 = 10000001;
1227        const INVALID_DATE: &str = "hey what's this?";
1228
1229        clause_test_case(Op::Before, DATE_STR1, DATE_STR2, true);
1230        clause_test_case(Op::Before, DATE_MS1, DATE_MS2, true);
1231        clause_test_case(Op::Before, DATE_STR2, DATE_STR1, false);
1232        clause_test_case(Op::Before, DATE_MS2, DATE_MS1, false);
1233        clause_test_case(Op::Before, DATE_STR1, DATE_STR1, false);
1234        clause_test_case(Op::Before, DATE_MS1, DATE_MS1, false);
1235        clause_test_case(Op::Before, AttributeValue::Null, DATE_STR1, false);
1236        clause_test_case(Op::Before, DATE_STR1, INVALID_DATE, false);
1237        clause_test_case(Op::After, DATE_STR2, DATE_STR1, true);
1238        clause_test_case(Op::After, DATE_MS2, DATE_MS1, true);
1239        clause_test_case(Op::After, DATE_STR1, DATE_STR2, false);
1240        clause_test_case(Op::After, DATE_MS1, DATE_MS2, false);
1241        clause_test_case(Op::After, DATE_STR1, DATE_STR1, false);
1242        clause_test_case(Op::After, DATE_MS1, DATE_MS1, false);
1243        clause_test_case(Op::After, AttributeValue::Null, DATE_STR1, false);
1244        clause_test_case(Op::After, DATE_STR1, INVALID_DATE, false);
1245    }
1246
1247    #[test]
1248    fn test_semver_clauses() {
1249        clause_test_case(Op::SemVerEqual, "2.0.0", "2.0.0", true);
1250        clause_test_case(Op::SemVerEqual, "2.0", "2.0.0", true);
1251        clause_test_case(Op::SemVerEqual, "2-rc1", "2.0.0-rc1", true);
1252        clause_test_case(Op::SemVerEqual, "2+build2", "2.0.0+build2", true);
1253        clause_test_case(Op::SemVerEqual, "2.0.0", "2.0.1", false);
1254        clause_test_case(Op::SemVerLessThan, "2.0.0", "2.0.1", true);
1255        clause_test_case(Op::SemVerLessThan, "2.0", "2.0.1", true);
1256        clause_test_case(Op::SemVerLessThan, "2.0.1", "2.0.0", false);
1257        clause_test_case(Op::SemVerLessThan, "2.0.1", "2.0", false);
1258        clause_test_case(Op::SemVerLessThan, "2.0.1", "xbad%ver", false);
1259        clause_test_case(Op::SemVerLessThan, "2.0.0-rc", "2.0.0-rc.beta", true);
1260        clause_test_case(Op::SemVerGreaterThan, "2.0.1", "2.0", true);
1261        clause_test_case(Op::SemVerGreaterThan, "10.0.1", "2.0", true);
1262        clause_test_case(Op::SemVerGreaterThan, "2.0.0", "2.0.1", false);
1263        clause_test_case(Op::SemVerGreaterThan, "2.0", "2.0.1", false);
1264        clause_test_case(Op::SemVerGreaterThan, "2.0.1", "xbad%ver", false);
1265        clause_test_case(Op::SemVerGreaterThan, "2.0.0-rc.1", "2.0.0-rc.0", true);
1266    }
1267
1268    #[test]
1269    fn clause_deserialize_with_attribute_missing_causes_error() {
1270        let attribute_missing = json!({
1271            "op" : "in",
1272            "values" : [],
1273        });
1274        assert!(serde_json::from_value::<IntermediateClause>(attribute_missing).is_err());
1275    }
1276
1277    #[test]
1278    fn clause_deserialize_with_op_missing_causes_error() {
1279        let op_missing = json!({
1280            "values" : [],
1281            "attribute" : "",
1282        });
1283        assert!(serde_json::from_value::<IntermediateClause>(op_missing).is_err());
1284    }
1285
1286    #[test]
1287    fn clause_deserialize_with_values_missing_causes_error() {
1288        let values_missing = json!({
1289            "op" : "in",
1290            "values" : [],
1291        });
1292        assert!(serde_json::from_value::<IntermediateClause>(values_missing).is_err());
1293    }
1294
1295    #[test]
1296    fn clause_deserialize_with_required_fields_parses_successfully() {
1297        let all_required_fields_present = json!({
1298            "attribute" : "",
1299            "op" : "in",
1300            "values" : [],
1301        });
1302
1303        assert_eq!(
1304            serde_json::from_value::<IntermediateClause>(all_required_fields_present).unwrap(),
1305            IntermediateClause::ContextOblivious(ClauseWithoutKind {
1306                attribute: AttributeName::default(),
1307                negate: false,
1308                op: Op::In,
1309                values: vec![]
1310            })
1311        );
1312    }
1313
1314    proptest! {
1315        #[test]
1316        fn arbitrary_clause_serialization_rountrip(clause in any_clause()) {
1317            let json = serde_json::to_value(clause).expect("a clause should serialize");
1318            let parsed: Clause = serde_json::from_value(json.clone()).expect("a clause should parse");
1319            assert_json_eq!(json, parsed);
1320        }
1321    }
1322
1323    #[test]
1324    fn clause_with_negate_omitted_defaults_to_false() {
1325        let negate_omitted = json!({
1326            "attribute" : "",
1327            "op" : "in",
1328            "values" : [],
1329        });
1330
1331        assert!(
1332            !serde_json::from_value::<Clause>(negate_omitted)
1333                .unwrap()
1334                .negate
1335        )
1336    }
1337
1338    #[test]
1339    fn clause_with_empty_attribute_defaults_to_invalid_attribute() {
1340        let empty_attribute = json!({
1341            "attribute" : "",
1342            "op" : "in",
1343            "values" : [],
1344        });
1345
1346        let attr = serde_json::from_value::<Clause>(empty_attribute)
1347            .unwrap()
1348            .attribute;
1349        assert_eq!(Reference::default(), attr);
1350    }
1351
1352    proptest! {
1353        #[test]
1354        fn clause_with_context_kind_implies_attribute_references(arbitrary_attribute in any::<String>()) {
1355            let with_context_kind = json!({
1356                "attribute" : arbitrary_attribute,
1357                "op" : "in",
1358                "values" : [],
1359                "contextKind" : "user",
1360            });
1361
1362            prop_assert_eq!(
1363                Reference::new(arbitrary_attribute),
1364                serde_json::from_value::<Clause>(with_context_kind)
1365                    .unwrap()
1366                    .attribute
1367            )
1368        }
1369    }
1370
1371    proptest! {
1372        #[test]
1373        fn clause_without_context_kind_implies_literal_attribute_name(arbitrary_attribute in any_valid_ref_string()) {
1374            let without_context_kind = json!({
1375                "attribute" : arbitrary_attribute,
1376                "op" : "in",
1377                "values" : [],
1378            });
1379
1380            prop_assert_eq!(
1381                Reference::from(AttributeName::new(arbitrary_attribute)),
1382                serde_json::from_value::<Clause>(without_context_kind)
1383                .unwrap()
1384                .attribute
1385            );
1386        }
1387    }
1388}