1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use thiserror::Error;
4
5#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
6pub enum Operator {
7 GreaterThan(f64),
9 LessThan(f64),
10 GreaterOrEqual(f64),
11 LessOrEqual(f64),
12
13 Equals(Value),
15 NotEqual(Value),
16
17 StartsWith(String),
19 EndsWith(String),
20 Contains(String),
21
22 ArrayContains(Value),
24
25 HasKey(String),
27
28 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" }); 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}