1use serde_json::Value;
2
3pub fn matches(metadata: &Value, filter: &Value) -> bool {
12 let filter_obj = match filter {
13 Value::Object(f) => f,
14 _ => return false,
15 };
16 for (key, condition) in filter_obj {
17 match key.as_str() {
18 "$and" => {
19 if let Value::Array(clauses) = condition {
20 if !clauses.iter().all(|c| matches(metadata, c)) {
21 return false;
22 }
23 } else {
24 return false;
25 }
26 }
27 "$or" => {
28 if let Value::Array(clauses) = condition {
29 if !clauses.iter().any(|c| matches(metadata, c)) {
30 return false;
31 }
32 } else {
33 return false;
34 }
35 }
36 "$not" => {
37 if matches(metadata, condition) {
38 return false;
39 }
40 }
41 field => {
42 let field_value = get_nested(metadata, field);
43 match condition {
44 Value::Object(ops) => {
45 for (op, operand) in ops {
46 if !apply_op(op, field_value, operand) {
47 return false;
48 }
49 }
50 }
51 expected => {
52 if field_value != Some(expected) {
53 return false;
54 }
55 }
56 }
57 }
58 }
59 }
60 true
61}
62
63fn get_nested<'a>(val: &'a Value, path: &str) -> Option<&'a Value> {
65 let mut current = val;
66 for segment in path.split('.') {
67 current = current.get(segment)?;
68 }
69 Some(current)
70}
71
72fn apply_op(op: &str, field: Option<&Value>, operand: &Value) -> bool {
73 match op {
74 "$eq" => field == Some(operand),
75 "$ne" => field != Some(operand),
76 "$exists" => field.is_some() == operand.as_bool().unwrap_or(true),
77 "$gt" => cmp_num(field, operand, |a, b| a > b),
78 "$gte" => cmp_num(field, operand, |a, b| a >= b),
79 "$lt" => cmp_num(field, operand, |a, b| a < b),
80 "$lte" => cmp_num(field, operand, |a, b| a <= b),
81 "$in" => match (field, operand) {
82 (Some(v), Value::Array(arr)) => arr.contains(v),
83 _ => false,
84 },
85 "$nin" => match (field, operand) {
86 (Some(v), Value::Array(arr)) => !arr.contains(v),
87 _ => true,
88 },
89 "$contains" => match (field, operand.as_str()) {
91 (Some(Value::String(s)), Some(pattern)) => s.contains(pattern),
92 _ => false,
93 },
94 "$regex" => match (field, operand.as_str()) {
96 (Some(Value::String(s)), Some(pattern)) => {
97 regex::Regex::new(pattern).map(|re| re.is_match(s)).unwrap_or(false)
98 }
99 _ => false,
100 },
101 _ => false,
102 }
103}
104
105fn cmp_num<F>(field: Option<&Value>, operand: &Value, cmp: F) -> bool
106where
107 F: Fn(f64, f64) -> bool,
108{
109 match (field, operand) {
110 (Some(Value::Number(a)), Value::Number(b)) => match (a.as_f64(), b.as_f64()) {
111 (Some(av), Some(bv)) => cmp(av, bv),
112 _ => false,
113 },
114 _ => false,
115 }
116}