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}