simple_json_filter/
lib.rs

1use serde_json::Number;
2use serde_json::Value;
3
4/// A struct representing a filter that can be applied on a JSON Value.
5///
6/// A filter consists of a field, an operator, and a value to compare with.
7/// The field and value can be optionally multiplied by a multiplier.
8/// The value to compare with can also be taken from another field.
9///
10/// # Fields
11///
12/// * `field` - The name of the field in the JSON Value to apply the filter on.
13/// * `operator` - The operator used for comparison.
14/// * `value` - The value to compare with.
15/// * `value_field` - The name of the field in the JSON Value to take the comparison value from.
16/// * `multiplier_field` - The multiplier for the field value.
17/// * `multiplier_value` - The multiplier for the comparison value.
18///
19#[derive(Debug)]
20pub struct Filter<'a> {
21    field: Option<&'a str>,
22    operator: &'a str,
23    value: Option<Value>,
24    value_field: Option<String>,
25    multiplier_field: Option<i64>,
26    multiplier_value: Option<i64>,
27}
28
29impl<'a> Default for Filter<'a> {
30    fn default() -> Self {
31        Filter {
32            field: None,
33            operator: "=",
34            value: None,
35            value_field: None,
36            multiplier_field: None,
37            multiplier_value: None,
38        }
39    }
40}
41
42/// Parses a filter string into a list of Filters.
43///
44/// The function splits the filter string by " AND " to get a list of filter parts.
45/// Each part is further split into field, operator, and value.
46/// The field and value can optionally have a multiplier and be prefixed with a multiplier followed by "*".
47/// The value can also be a reference to a field if it starts with ".".
48///
49/// # Arguments
50///
51/// * `filter_string` - The string representation of filters to parse.
52///
53/// # Returns
54///
55/// * `Option<Vec<Filter>>` - Returns a list of Filters if the parsing is successful, otherwise returns None.
56///
57pub fn parse(filter_string: &str) -> Option<Vec<Filter>> {
58    let filters = filter_string
59        .split(" AND ")
60        .map(|filter_part| {
61            let parts: Vec<&str> = filter_part.split_whitespace().collect();
62
63            let field_parts: Vec<&str> = parts[0].split('*').collect();
64            let multiplier_field = if field_parts.len() == 2 {
65                field_parts[0].parse::<i64>().ok()
66            } else {
67                None
68            };
69            let field = if field_parts.len() == 1 || multiplier_field.is_some() {
70                Some(field_parts[field_parts.len() - 1].trim_start_matches('.'))
71            } else {
72                None
73            };
74
75            let operator = parts[1];
76
77            let value_parts: Vec<&str> = parts[2].split('*').collect();
78            let multiplier_value = if value_parts.len() == 2 {
79                value_parts[0].parse::<i64>().ok()
80            } else {
81                None
82            };
83
84            let value = value_parts[value_parts.len() - 1].trim_matches('\'');
85
86            let value_field = if value.starts_with('.') {
87                Some(value.trim_start_matches('.').to_string())
88            } else {
89                None
90            };
91
92            let value = if value_field.is_none() {
93                if let Ok(n) = value.parse::<i64>() {
94                    Some(Value::Number(Number::from(n)))
95                } else {
96                    Some(Value::String(value.to_string()))
97                }
98            } else {
99                None
100            };
101
102            Filter {
103                field,
104                operator,
105                value,
106                value_field,
107                multiplier_field,
108                multiplier_value,
109            }
110        })
111        .collect();
112    Some(filters)
113}
114
115/// Applies a set of filters on a JSON Value and returns whether the Value passes the filters.
116///
117/// The function iterates over a list of filters and applies each filter on the Value `v`.
118/// The field to be compared is extracted from the Value, based on the `field` attribute of the filter.
119/// The value to compare with is determined based on the `value_field` or `value` attributes of the filter.
120///
121/// The comparison is done either as a string comparison or as a number comparison,
122/// depending on the types of the extracted field and value.
123/// For number comparisons, a multiplier can be applied to the field or value.
124///
125/// If a filter comparison is unsuccessful, the function immediately returns `false`.
126/// If all filter comparisons are successful, the function returns `true`.
127///
128/// # Arguments
129///
130/// * `v` - The JSON Value to apply the filters on.
131/// * `filters` - A slice of Filters to apply on the Value.
132///
133/// # Returns
134///
135/// * `bool` - Returns `true` if the Value `v` passes all the filters, otherwise returns `false`.
136///
137pub fn apply(v: &Value, filters: &[Filter]) -> bool {
138    for filter in filters {
139        // The field we're comparing is taken from the JSON value.
140        let f = filter.field.as_deref().and_then(|field| v.get(field));
141        let f_is_number = matches!(f, Some(Value::Number(_)));
142
143        // If the filter has a value_field, we take the value to compare from the JSON value.
144        // If there is no value_field, we use the value directly.
145        let value = filter.value_field.as_deref().and_then(|vf| v.get(vf));
146
147        // Then we perform the comparison according to the operator in the filter.
148        // If both are strings, compare them as strings. If not, try to compare as numbers.
149        let comparison = if !f_is_number {
150            let f_str = f.and_then(|val| val.as_str());
151            // if value id true get from value, if not get from value_filed
152            let value_str = if filter.value.is_some() {
153                filter.value.as_ref().and_then(|val| val.as_str())
154            } else {
155                filter
156                    .value_field
157                    .as_deref()
158                    .and_then(|vf| v.get(vf))
159                    .and_then(|val| val.as_str())
160            };
161            match (f_str, value_str) {
162                (Some(f_str), Some(value_str)) => match filter.operator {
163                    "=" => f_str == value_str,
164                    "!=" => f_str != value_str,
165                    _ => false, // Unknown operator for string comparisons
166                },
167                _ => false, // In case there's a mismatch in type (one is number and the other is string)
168            }
169        } else {
170            // Now we multiply it by its multiplier if there is one.
171            let f = if let (Some(mult), Some(val)) = (filter.multiplier_field, f) {
172                val.as_i64().map(|v| v * mult)
173            } else {
174                f.and_then(|val| val.as_i64())
175            };
176
177            let value = if let (Some(mult), Some(val)) = (filter.multiplier_value, value) {
178                val.as_i64().map(|v| v * mult)
179            } else {
180                value
181                    .and_then(|val| val.as_i64())
182                    .or_else(|| filter.value.clone().and_then(|val| val.as_i64()))
183            };
184
185            match (f, value) {
186                (Some(f), Some(value)) => match filter.operator {
187                    "=" => f == value,
188                    "!=" => f != value,
189                    ">=" => f >= value,
190                    ">" => f > value,
191                    "<=" => f <= value,
192                    "<" => f < value,
193                    _ => false, // Unknown operator
194                },
195                _ => false, // In case there's a mismatch in type (one is number and the other is string)
196            }
197        };
198
199        // If the comparison is false, we return false immediately.
200        if !comparison {
201            return false;
202        }
203    }
204    // If none of the filters returned false, we return true.
205    true
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211    use serde_json::json;
212
213    #[test]
214    fn test_parse() {
215        let filter_string = ".field = 'hello' AND .value >= 20";
216        let filters = parse(filter_string).unwrap();
217        assert_eq!(filters.len(), 2);
218        assert_eq!(filters[0].field, Some("field"));
219        assert_eq!(filters[0].operator, "=");
220        assert_eq!(filters[0].value, Some(json!("hello")));
221        assert_eq!(filters[1].field, Some("value"));
222        assert_eq!(filters[1].operator, ">=");
223        assert_eq!(filters[1].value, Some(json!(20)));
224    }
225
226    #[test]
227    fn test_apply() {
228        let v = json!({ "field": 100, "hello": "world" });
229        let filters = vec![
230            Filter {
231                field: Some("field"),
232                operator: ">",
233                value: Some(json!(50)),
234                ..Default::default()
235            },
236            Filter {
237                field: Some("hello"),
238                operator: "=",
239                value: Some(json!("world")),
240                ..Default::default()
241            },
242        ];
243        assert!(apply(&v, &filters));
244    }
245}