json_filter/
lib.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use thiserror::Error;
4
5#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
6pub enum Operator {
7    // Numeric operators
8    GreaterThan(f64),
9    LessThan(f64),
10    GreaterOrEqual(f64),
11    LessOrEqual(f64),
12
13    // General equality
14    Equals(Value),
15    NotEqual(Value),
16
17    // String operators
18    StartsWith(String),
19    EndsWith(String),
20    Contains(String),
21
22    // Array operators
23    ArrayContains(Value),
24
25    // Object operators
26    HasKey(String),
27
28    // Logical operators
29    And(Vec<Filter>),
30    Or(Vec<Filter>),
31}
32
33#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
34pub struct Filter {
35    pub path: String,
36    pub operator: Operator,
37}
38
39#[derive(Error, Debug)]
40pub enum FilterError {
41    #[error("Path not found: {0}")]
42    PathNotFound(String),
43
44    #[error("Type mismatch: expected {expected}, got {got}")]
45    TypeMismatch { expected: String, got: String },
46
47    #[error("Invalid array index in path: {0}")]
48    InvalidArrayIndex(String),
49
50    #[error("Invalid path format: {0}")]
51    InvalidPath(String),
52}
53
54impl Filter {
55    pub fn new(path: impl Into<String>, operator: Operator) -> Self {
56        Self {
57            path: path.into(),
58            operator,
59        }
60    }
61
62    pub fn check(&self, value: &Value) -> Result<bool, FilterError> {
63        let target = self.resolve_path(value)?;
64        self.check_operator(target)
65    }
66
67    fn resolve_path<'a>(&self, value: &'a Value) -> Result<&'a Value, FilterError> {
68        let mut current = value;
69
70        if self.path == "." {
71            return Ok(current);
72        }
73
74        for segment in self.path.split('.') {
75            if segment.contains('[') && segment.ends_with(']') {
76                let (field, index) = self.parse_array_segment(segment)?;
77
78                if !field.is_empty() {
79                    current = current
80                        .get(&field)
81                        .ok_or_else(|| FilterError::PathNotFound(field.to_string()))?;
82                }
83
84                current = match current {
85                    Value::Array(arr) => arr
86                        .get(index)
87                        .ok_or_else(|| FilterError::InvalidArrayIndex(index.to_string()))?,
88                    _ => {
89                        return Err(FilterError::TypeMismatch {
90                            expected: "array".to_string(),
91                            got: format!("{:?}", current),
92                        })
93                    }
94                };
95            } else {
96                current = current
97                    .get(segment)
98                    .ok_or_else(|| FilterError::PathNotFound(segment.to_string()))?;
99            }
100        }
101
102        Ok(current)
103    }
104
105    fn parse_array_segment(&self, segment: &str) -> Result<(String, usize), FilterError> {
106        let bracket_idx = segment
107            .find('[')
108            .ok_or_else(|| FilterError::InvalidPath(segment.to_string()))?;
109
110        let field = segment[..bracket_idx].to_string();
111        let index_str = &segment[bracket_idx + 1..segment.len() - 1];
112
113        let index = index_str
114            .parse::<usize>()
115            .map_err(|_| FilterError::InvalidArrayIndex(index_str.to_string()))?;
116
117        Ok((field, index))
118    }
119
120    fn check_operator(&self, value: &Value) -> Result<bool, FilterError> {
121        match &self.operator {
122            Operator::GreaterThan(n) => {
123                if let Value::Number(num) = value {
124                    Ok(num.as_f64().unwrap() > *n)
125                } else {
126                    Err(FilterError::TypeMismatch {
127                        expected: "number".to_string(),
128                        got: format!("{:?}", value),
129                    })
130                }
131            }
132
133            Operator::LessThan(n) => {
134                if let Value::Number(num) = value {
135                    Ok(num.as_f64().unwrap() < *n)
136                } else {
137                    Err(FilterError::TypeMismatch {
138                        expected: "number".to_string(),
139                        got: format!("{:?}", value),
140                    })
141                }
142            }
143
144            Operator::GreaterOrEqual(n) => {
145                if let Value::Number(num) = value {
146                    Ok(num.as_f64().unwrap() >= *n)
147                } else {
148                    Err(FilterError::TypeMismatch {
149                        expected: "number".to_string(),
150                        got: format!("{:?}", value),
151                    })
152                }
153            }
154
155            Operator::LessOrEqual(n) => {
156                if let Value::Number(num) = value {
157                    Ok(num.as_f64().unwrap() <= *n)
158                } else {
159                    Err(FilterError::TypeMismatch {
160                        expected: "number".to_string(),
161                        got: format!("{:?}", value),
162                    })
163                }
164            }
165
166            Operator::Equals(target) => Ok(value == target),
167
168            Operator::NotEqual(target) => Ok(value != target),
169
170            Operator::StartsWith(s) => {
171                if let Value::String(str) = value {
172                    Ok(str.starts_with(s))
173                } else {
174                    Err(FilterError::TypeMismatch {
175                        expected: "string".to_string(),
176                        got: format!("{:?}", value),
177                    })
178                }
179            }
180
181            Operator::EndsWith(s) => {
182                if let Value::String(str) = value {
183                    Ok(str.ends_with(s))
184                } else {
185                    Err(FilterError::TypeMismatch {
186                        expected: "string".to_string(),
187                        got: format!("{:?}", value),
188                    })
189                }
190            }
191
192            Operator::Contains(s) => {
193                if let Value::String(str) = value {
194                    Ok(str.contains(s))
195                } else {
196                    Err(FilterError::TypeMismatch {
197                        expected: "string".to_string(),
198                        got: format!("{:?}", value),
199                    })
200                }
201            }
202
203            Operator::ArrayContains(target) => {
204                if let Value::Array(arr) = value {
205                    Ok(arr.contains(target))
206                } else {
207                    Err(FilterError::TypeMismatch {
208                        expected: "array".to_string(),
209                        got: format!("{:?}", value),
210                    })
211                }
212            }
213
214            Operator::HasKey(key) => {
215                if let Value::Object(obj) = value {
216                    Ok(obj.contains_key(key))
217                } else {
218                    Err(FilterError::TypeMismatch {
219                        expected: "object".to_string(),
220                        got: format!("{:?}", value),
221                    })
222                }
223            }
224
225            Operator::And(filters) => {
226                let mut results = Vec::new();
227                for filter in filters {
228                    results.push(filter.check(value)?);
229                }
230                Ok(results.iter().all(|&x| x))
231            }
232
233            Operator::Or(filters) => {
234                let mut results = Vec::new();
235                for filter in filters {
236                    results.push(filter.check(value)?);
237                }
238                Ok(results.iter().any(|&x| x))
239            }
240        }
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use super::*;
247    use serde_json::json;
248
249    #[test]
250    fn test_numeric_operators() {
251        let value = json!({ "age": 25 });
252
253        let filter = Filter::new("age", Operator::GreaterThan(20.0));
254        assert!(filter.check(&value).unwrap());
255
256        let filter = Filter::new("age", Operator::LessThan(30.0));
257        assert!(filter.check(&value).unwrap());
258
259        let filter = Filter::new("age", Operator::GreaterOrEqual(25.0));
260        assert!(filter.check(&value).unwrap());
261
262        let filter = Filter::new("age", Operator::LessOrEqual(25.0));
263        assert!(filter.check(&value).unwrap());
264    }
265
266    #[test]
267    fn test_string_operators() {
268        let value = json!({ "name": "John Doe" });
269
270        let filter = Filter::new("name", Operator::StartsWith("John".to_string()));
271        assert!(filter.check(&value).unwrap());
272
273        let filter = Filter::new("name", Operator::EndsWith("Doe".to_string()));
274        assert!(filter.check(&value).unwrap());
275
276        let filter = Filter::new("name", Operator::Contains("hn D".to_string()));
277        assert!(filter.check(&value).unwrap());
278    }
279
280    #[test]
281    fn test_array_operators() {
282        let value = json!({ "tags": ["rust", "coding", "json"] });
283
284        let filter = Filter::new("tags", Operator::ArrayContains(json!("rust")));
285        assert!(filter.check(&value).unwrap());
286
287        let filter = Filter::new("tags[1]", Operator::Equals(json!("coding")));
288        assert!(filter.check(&value).unwrap());
289    }
290
291    #[test]
292    fn test_object_operators() {
293        let value = json!({
294            "user": {
295                "id": 123,
296                "details": {
297                    "email": "john@example.com"
298                }
299            }
300        });
301
302        let filter = Filter::new("user", Operator::HasKey("id".to_string()));
303        assert!(filter.check(&value).unwrap());
304
305        let filter = Filter::new(
306            "user.details.email",
307            Operator::EndsWith("@example.com".to_string()),
308        );
309        assert!(filter.check(&value).unwrap());
310    }
311
312    #[test]
313    fn test_logical_operators() {
314        let value = json!({
315            "age": 25,
316            "name": "John Doe"
317        });
318
319        let filter = Filter::new(
320            ".",
321            Operator::And(vec![
322                Filter::new("age", Operator::GreaterThan(20.0)),
323                Filter::new("name", Operator::StartsWith("John".to_string())),
324            ]),
325        );
326        assert!(filter.check(&value).unwrap());
327
328        let filter = Filter::new(
329            ".",
330            Operator::Or(vec![
331                Filter::new("age", Operator::GreaterThan(30.0)),
332                Filter::new("name", Operator::Contains("John".to_string())),
333            ]),
334        );
335        assert!(filter.check(&value).unwrap());
336    }
337
338    #[test]
339    fn test_type_mismatch() {
340        let value = json!({ "age": "25" }); // age is a string, not a number
341
342        let filter = Filter::new("age", Operator::GreaterThan(20.0));
343        assert!(matches!(
344            filter.check(&value),
345            Err(FilterError::TypeMismatch { .. })
346        ));
347    }
348
349    #[test]
350    fn test_path_not_found() {
351        let value = json!({ "name": "John" });
352
353        let filter = Filter::new("age", Operator::GreaterThan(20.0));
354        assert!(matches!(
355            filter.check(&value),
356            Err(FilterError::PathNotFound(..))
357        ));
358    }
359}