json_rules_engine/
constraint.rs

1use crate::status::Status;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use strum::VariantNames;
5use strum_macros::EnumVariantNames;
6
7#[derive(Clone, Debug, Serialize, Deserialize, EnumVariantNames)]
8#[serde(rename_all = "snake_case")]
9#[strum(serialize_all = "snake_case")]
10#[serde(tag = "operator", content = "value")]
11pub enum Constraint {
12    StringEquals(String),
13    StringNotEquals(String),
14    StringContains(String),
15    StringContainsAll(Vec<String>),
16    StringContainsAny(Vec<String>),
17    StringDoesNotContain(String),
18    StringDoesNotContainAny(Vec<String>),
19    StringIn(Vec<String>),
20    StringNotIn(Vec<String>),
21    IntEquals(i64),
22    IntNotEquals(i64),
23    IntContains(i64),
24    IntContainsAll(Vec<i64>),
25    IntContainsAny(Vec<i64>),
26    IntDoesNotContain(i64),
27    IntDoesNotContainAny(Vec<i64>),
28    IntIn(Vec<i64>),
29    IntNotIn(Vec<i64>),
30    IntInRange(i64, i64),
31    IntNotInRange(i64, i64),
32    IntLessThan(i64),
33    IntLessThanInclusive(i64),
34    IntGreaterThan(i64),
35    IntGreaterThanInclusive(i64),
36    FloatEquals(f64),
37    FloatNotEquals(f64),
38    FloatContains(f64),
39    FloatDoesNotContain(f64),
40    FloatIn(Vec<f64>),
41    FloatNotIn(Vec<f64>),
42    FloatInRange(f64, f64),
43    FloatNotInRange(f64, f64),
44    FloatLessThan(f64),
45    FloatLessThanInclusive(f64),
46    FloatGreaterThan(f64),
47    FloatGreaterThanInclusive(f64),
48    BoolEquals(bool),
49}
50
51impl Constraint {
52    fn value_as_str_array(v: &Value) -> Option<Vec<&str>> {
53        v.as_array()
54            .map(|x| x.iter().filter_map(|y| y.as_str()).collect::<Vec<_>>())
55    }
56
57    fn value_as_i64_array(v: &Value) -> Option<Vec<i64>> {
58        v.as_array()
59            .map(|x| x.iter().filter_map(|y| y.as_i64()).collect::<Vec<_>>())
60    }
61
62    fn value_as_f64_array(v: &Value) -> Option<Vec<f64>> {
63        v.as_array()
64            .map(|x| x.iter().filter_map(|y| y.as_f64()).collect::<Vec<_>>())
65    }
66
67    pub fn check_value(&self, v: &Value) -> Status {
68        match *self {
69            Constraint::StringEquals(ref s) => match v.as_str() {
70                None => Status::NotMet,
71                Some(v) => {
72                    if v == s {
73                        Status::Met
74                    } else {
75                        Status::NotMet
76                    }
77                }
78            },
79            Constraint::StringNotEquals(ref s) => match v.as_str() {
80                None => Status::NotMet,
81                Some(v) => {
82                    if v != s {
83                        Status::Met
84                    } else {
85                        Status::NotMet
86                    }
87                }
88            },
89            Constraint::StringContains(ref s) => {
90                match Self::value_as_str_array(v) {
91                    None => Status::NotMet,
92                    Some(v) => {
93                        if v.contains(&s.as_str()) {
94                            Status::Met
95                        } else {
96                            Status::NotMet
97                        }
98                    }
99                }
100            }
101            Constraint::StringContainsAll(ref s) => {
102                match Self::value_as_str_array(v) {
103                    None => Status::NotMet,
104                    Some(v) => {
105                        if s.iter().all(|y| v.contains(&y.as_str())) {
106                            Status::Met
107                        } else {
108                            Status::NotMet
109                        }
110                    }
111                }
112            }
113            Constraint::StringContainsAny(ref s) => {
114                match Self::value_as_str_array(v) {
115                    None => Status::NotMet,
116                    Some(v) => {
117                        if s.iter().any(|y| v.contains(&y.as_str())) {
118                            Status::Met
119                        } else {
120                            Status::NotMet
121                        }
122                    }
123                }
124            }
125            Constraint::StringDoesNotContain(ref s) => {
126                match Self::value_as_str_array(v) {
127                    None => Status::NotMet,
128                    Some(v) => {
129                        if !v.contains(&s.as_str()) {
130                            Status::Met
131                        } else {
132                            Status::NotMet
133                        }
134                    }
135                }
136            }
137            Constraint::StringDoesNotContainAny(ref s) => {
138                match Self::value_as_str_array(v) {
139                    None => Status::NotMet,
140                    Some(v) => {
141                        if s.iter().all(|y| !v.contains(&y.as_str())) {
142                            Status::Met
143                        } else {
144                            Status::NotMet
145                        }
146                    }
147                }
148            }
149            Constraint::StringIn(ref ss) => match v.as_str() {
150                None => Status::NotMet,
151                Some(v) => {
152                    if ss.iter().any(|s| s == v) {
153                        Status::Met
154                    } else {
155                        Status::NotMet
156                    }
157                }
158            },
159            Constraint::StringNotIn(ref ss) => match v.as_str() {
160                None => Status::NotMet,
161                Some(v) => {
162                    if ss.iter().all(|s| s != v) {
163                        Status::Met
164                    } else {
165                        Status::NotMet
166                    }
167                }
168            },
169            Constraint::IntEquals(num) => match v.as_i64() {
170                None => Status::NotMet,
171                Some(v) => {
172                    if v == num {
173                        Status::Met
174                    } else {
175                        Status::NotMet
176                    }
177                }
178            },
179            Constraint::IntNotEquals(num) => match v.as_i64() {
180                None => Status::NotMet,
181                Some(v) => {
182                    if v != num {
183                        Status::Met
184                    } else {
185                        Status::NotMet
186                    }
187                }
188            },
189            Constraint::IntContains(num) => match Self::value_as_i64_array(v) {
190                None => Status::NotMet,
191                Some(v) => {
192                    if v.contains(&num) {
193                        Status::Met
194                    } else {
195                        Status::NotMet
196                    }
197                }
198            },
199            Constraint::IntContainsAll(ref nums) => {
200                match Self::value_as_i64_array(v) {
201                    None => Status::NotMet,
202                    Some(v) => {
203                        if nums.iter().all(|num| v.contains(&num)) {
204                            Status::Met
205                        } else {
206                            Status::NotMet
207                        }
208                    }
209                }
210            }
211            Constraint::IntContainsAny(ref nums) => {
212                match Self::value_as_i64_array(v) {
213                    None => Status::NotMet,
214                    Some(v) => {
215                        if nums.iter().any(|num| v.contains(&num)) {
216                            Status::Met
217                        } else {
218                            Status::NotMet
219                        }
220                    }
221                }
222            }
223            Constraint::IntDoesNotContain(num) => {
224                match Self::value_as_i64_array(v) {
225                    None => Status::NotMet,
226                    Some(v) => {
227                        if !v.contains(&num) {
228                            Status::Met
229                        } else {
230                            Status::NotMet
231                        }
232                    }
233                }
234            }
235            Constraint::IntDoesNotContainAny(ref nums) => {
236                match Self::value_as_i64_array(v) {
237                    None => Status::NotMet,
238                    Some(v) => {
239                        if nums.iter().all(|num| !v.contains(&num)) {
240                            Status::Met
241                        } else {
242                            Status::NotMet
243                        }
244                    }
245                }
246            }
247            Constraint::IntIn(ref nums) => match v.as_i64() {
248                None => Status::NotMet,
249                Some(v) => {
250                    if nums.iter().any(|&num| num == v) {
251                        Status::Met
252                    } else {
253                        Status::NotMet
254                    }
255                }
256            },
257            Constraint::IntNotIn(ref nums) => match v.as_i64() {
258                None => Status::NotMet,
259                Some(v) => {
260                    if nums.iter().all(|&num| num != v) {
261                        Status::Met
262                    } else {
263                        Status::NotMet
264                    }
265                }
266            },
267            Constraint::IntInRange(start, end) => match v.as_i64() {
268                None => Status::NotMet,
269                Some(v) => {
270                    if start <= v && v <= end {
271                        Status::Met
272                    } else {
273                        Status::NotMet
274                    }
275                }
276            },
277            Constraint::IntNotInRange(start, end) => match v.as_i64() {
278                None => Status::NotMet,
279                Some(v) => {
280                    if start <= v && v <= end {
281                        Status::NotMet
282                    } else {
283                        Status::Met
284                    }
285                }
286            },
287            Constraint::IntLessThan(num) => match v.as_i64() {
288                None => Status::NotMet,
289                Some(v) => {
290                    if v < num {
291                        Status::Met
292                    } else {
293                        Status::NotMet
294                    }
295                }
296            },
297            Constraint::IntLessThanInclusive(num) => match v.as_i64() {
298                None => Status::NotMet,
299                Some(v) => {
300                    if v <= num {
301                        Status::Met
302                    } else {
303                        Status::NotMet
304                    }
305                }
306            },
307            Constraint::IntGreaterThan(num) => match v.as_i64() {
308                None => Status::NotMet,
309                Some(v) => {
310                    if v > num {
311                        Status::Met
312                    } else {
313                        Status::NotMet
314                    }
315                }
316            },
317            Constraint::IntGreaterThanInclusive(num) => match v.as_i64() {
318                None => Status::NotMet,
319                Some(v) => {
320                    if v >= num {
321                        Status::Met
322                    } else {
323                        Status::NotMet
324                    }
325                }
326            },
327            Constraint::FloatEquals(num) => match v.as_f64() {
328                None => Status::NotMet,
329                Some(v) => {
330                    if (v - num).abs() < f64::EPSILON {
331                        Status::Met
332                    } else {
333                        Status::NotMet
334                    }
335                }
336            },
337            Constraint::FloatNotEquals(num) => match v.as_f64() {
338                None => Status::NotMet,
339                Some(v) => {
340                    if (v - num).abs() > f64::EPSILON {
341                        Status::Met
342                    } else {
343                        Status::NotMet
344                    }
345                }
346            },
347            Constraint::FloatContains(num) => {
348                match Self::value_as_f64_array(v) {
349                    None => Status::NotMet,
350                    Some(v) => {
351                        if v.contains(&num) {
352                            Status::Met
353                        } else {
354                            Status::NotMet
355                        }
356                    }
357                }
358            }
359            Constraint::FloatDoesNotContain(num) => {
360                match Self::value_as_f64_array(v) {
361                    None => Status::NotMet,
362                    Some(v) => {
363                        if !v.contains(&num) {
364                            Status::Met
365                        } else {
366                            Status::NotMet
367                        }
368                    }
369                }
370            }
371            Constraint::FloatIn(ref nums) => match v.as_f64() {
372                None => Status::NotMet,
373                Some(v) => {
374                    if nums.iter().any(|&num| (v - num).abs() < f64::EPSILON) {
375                        Status::Met
376                    } else {
377                        Status::NotMet
378                    }
379                }
380            },
381            Constraint::FloatNotIn(ref nums) => match v.as_f64() {
382                None => Status::NotMet,
383                Some(v) => {
384                    if nums.iter().all(|&num| (v - num).abs() > f64::EPSILON) {
385                        Status::Met
386                    } else {
387                        Status::NotMet
388                    }
389                }
390            },
391            Constraint::FloatInRange(start, end) => match v.as_f64() {
392                None => Status::NotMet,
393                Some(v) => {
394                    if start <= v && v <= end {
395                        Status::Met
396                    } else {
397                        Status::NotMet
398                    }
399                }
400            },
401            Constraint::FloatNotInRange(start, end) => match v.as_f64() {
402                None => Status::NotMet,
403                Some(v) => {
404                    if start <= v && v <= end {
405                        Status::NotMet
406                    } else {
407                        Status::Met
408                    }
409                }
410            },
411            Constraint::FloatLessThan(num) => match v.as_f64() {
412                None => Status::NotMet,
413                Some(v) => {
414                    if v < num {
415                        Status::Met
416                    } else {
417                        Status::NotMet
418                    }
419                }
420            },
421            Constraint::FloatLessThanInclusive(num) => match v.as_f64() {
422                None => Status::NotMet,
423                Some(v) => {
424                    if v <= num {
425                        Status::Met
426                    } else {
427                        Status::NotMet
428                    }
429                }
430            },
431            Constraint::FloatGreaterThan(num) => match v.as_f64() {
432                None => Status::NotMet,
433                Some(v) => {
434                    if v > num {
435                        Status::Met
436                    } else {
437                        Status::NotMet
438                    }
439                }
440            },
441            Constraint::FloatGreaterThanInclusive(num) => match v.as_f64() {
442                None => Status::NotMet,
443                Some(v) => {
444                    if v >= num {
445                        Status::Met
446                    } else {
447                        Status::NotMet
448                    }
449                }
450            },
451            Constraint::BoolEquals(b) => match v.as_bool() {
452                None => Status::NotMet,
453                Some(v) => {
454                    if v == b {
455                        Status::Met
456                    } else {
457                        Status::NotMet
458                    }
459                }
460            },
461        }
462    }
463
464    pub fn operators() -> &'static [&'static str] {
465        Constraint::VARIANTS
466    }
467}
468
469#[cfg(test)]
470mod tests {
471    use super::Constraint;
472
473    #[test]
474    fn available_operators() {
475        assert_eq!(Constraint::operators().len(), 37);
476    }
477}