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