use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::Subject;
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum Comparison {
Eq,
Ne,
Gt,
Gte,
Lt,
Lte,
In,
NotIn,
StartsWith,
EndsWith,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum Predicate {
Always,
Compare {
attr: String,
op: Comparison,
value: Value,
},
AllOf {
matchers: Vec<Predicate>,
},
AnyOf {
matchers: Vec<Predicate>,
},
Not {
matcher: Box<Predicate>,
},
}
impl Predicate {
pub fn matches(&self, subject: &Subject) -> bool {
match self {
Self::Always => true,
Self::AllOf { matchers } => matchers.iter().all(|p| p.matches(subject)),
Self::AnyOf { matchers } => matchers.iter().any(|p| p.matches(subject)),
Self::Not { matcher } => !matcher.matches(subject),
Self::Compare { attr, op, value } => compare(subject.attr(attr), *op, value),
}
}
}
fn compare(actual: Option<&Value>, op: Comparison, expected: &Value) -> bool {
match op {
Comparison::Eq => actual.is_some_and(|a| a == expected),
Comparison::Ne => actual.is_none_or(|a| a != expected),
Comparison::Gt => num_cmp(actual, expected, |a, b| a > b),
Comparison::Gte => num_cmp(actual, expected, |a, b| a >= b),
Comparison::Lt => num_cmp(actual, expected, |a, b| a < b),
Comparison::Lte => num_cmp(actual, expected, |a, b| a <= b),
Comparison::In => match (actual, expected.as_array()) {
(Some(a), Some(arr)) => arr.iter().any(|v| v == a),
_ => false,
},
Comparison::NotIn => match (actual, expected.as_array()) {
(Some(a), Some(arr)) => !arr.iter().any(|v| v == a),
(None, Some(_)) => true,
_ => false,
},
Comparison::StartsWith => str_pair(actual, expected, |a, b| a.starts_with(b)),
Comparison::EndsWith => str_pair(actual, expected, |a, b| a.ends_with(b)),
}
}
fn num_cmp(actual: Option<&Value>, expected: &Value, f: impl Fn(f64, f64) -> bool) -> bool {
match (actual.and_then(Value::as_f64), expected.as_f64()) {
(Some(a), Some(b)) => f(a, b),
_ => false,
}
}
fn str_pair(actual: Option<&Value>, expected: &Value, f: impl Fn(&str, &str) -> bool) -> bool {
match (actual.and_then(Value::as_str), expected.as_str()) {
(Some(a), Some(b)) => f(a, b),
_ => false,
}
}