1use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9use crate::Subject;
10
11#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
13#[serde(rename_all = "snake_case")]
14pub enum Comparison {
15 Eq,
17 Ne,
19 Gt,
21 Gte,
23 Lt,
25 Lte,
27 In,
29 NotIn,
31 StartsWith,
33 EndsWith,
35}
36
37#[derive(Clone, Debug, Deserialize, Serialize)]
40#[serde(tag = "kind", rename_all = "snake_case")]
41pub enum Predicate {
42 Always,
44 Compare {
46 attr: String,
48 op: Comparison,
50 value: Value,
52 },
53 AllOf {
55 matchers: Vec<Predicate>,
57 },
58 AnyOf {
60 matchers: Vec<Predicate>,
62 },
63 Not {
65 matcher: Box<Predicate>,
67 },
68}
69
70impl Predicate {
71 pub fn matches(&self, subject: &Subject) -> bool {
75 match self {
76 Self::Always => true,
77 Self::AllOf { matchers } => matchers.iter().all(|p| p.matches(subject)),
78 Self::AnyOf { matchers } => matchers.iter().any(|p| p.matches(subject)),
79 Self::Not { matcher } => !matcher.matches(subject),
80 Self::Compare { attr, op, value } => compare(subject.attr(attr), *op, value),
81 }
82 }
83}
84
85fn compare(actual: Option<&Value>, op: Comparison, expected: &Value) -> bool {
86 match op {
87 Comparison::Eq => actual.is_some_and(|a| a == expected),
88 Comparison::Ne => actual.is_none_or(|a| a != expected),
89 Comparison::Gt => num_cmp(actual, expected, |a, b| a > b),
90 Comparison::Gte => num_cmp(actual, expected, |a, b| a >= b),
91 Comparison::Lt => num_cmp(actual, expected, |a, b| a < b),
92 Comparison::Lte => num_cmp(actual, expected, |a, b| a <= b),
93 Comparison::In => match (actual, expected.as_array()) {
94 (Some(a), Some(arr)) => arr.iter().any(|v| v == a),
95 _ => false,
96 },
97 Comparison::NotIn => match (actual, expected.as_array()) {
98 (Some(a), Some(arr)) => !arr.iter().any(|v| v == a),
99 (None, Some(_)) => true,
100 _ => false,
101 },
102 Comparison::StartsWith => str_pair(actual, expected, |a, b| a.starts_with(b)),
103 Comparison::EndsWith => str_pair(actual, expected, |a, b| a.ends_with(b)),
104 }
105}
106
107fn num_cmp(actual: Option<&Value>, expected: &Value, f: impl Fn(f64, f64) -> bool) -> bool {
108 match (actual.and_then(Value::as_f64), expected.as_f64()) {
109 (Some(a), Some(b)) => f(a, b),
110 _ => false,
111 }
112}
113
114fn str_pair(actual: Option<&Value>, expected: &Value, f: impl Fn(&str, &str) -> bool) -> bool {
115 match (actual.and_then(Value::as_str), expected.as_str()) {
116 (Some(a), Some(b)) => f(a, b),
117 _ => false,
118 }
119}