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