1use crate::{status::Status, Constraint};
2#[cfg(feature = "eval")]
3use rhai::{serde::to_dynamic, Engine, Scope};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7#[derive(Clone, Debug, Serialize, Deserialize)]
8#[serde(untagged)]
9pub enum Condition {
10 And {
11 and: Vec<Condition>,
12 },
13 Or {
14 or: Vec<Condition>,
15 },
16 Not {
17 not: Box<Condition>,
18 },
19 AtLeast {
20 should_minimum_meet: usize,
21 conditions: Vec<Condition>,
22 },
23 Condition {
24 field: String,
25 #[serde(flatten)]
26 constraint: Constraint,
27 path: Option<String>,
28 },
29 #[cfg(feature = "eval")]
30 Eval {
31 expr: String,
32 },
33}
34
35impl Condition {
36 pub fn check_value(
39 &self,
40 info: &Value,
41 #[cfg(feature = "eval")] rhai_engine: &Engine,
42 ) -> ConditionResult {
43 match *self {
44 Condition::And { ref and } => {
45 let mut status = Status::Met;
46 let children = and
47 .iter()
48 .map(|c| {
49 c.check_value(
50 info,
51 #[cfg(feature = "eval")]
52 rhai_engine,
53 )
54 })
55 .inspect(|r| status = status & r.status)
56 .collect::<Vec<_>>();
57
58 ConditionResult {
59 name: "And".into(),
60 status,
61 children,
62 }
63 }
64 Condition::Not { not: ref c } => {
65 let res = c.check_value(
66 info,
67 #[cfg(feature = "eval")]
68 rhai_engine,
69 );
70
71 ConditionResult {
72 name: "Not".into(),
73 status: !res.status,
74 children: res.children,
75 }
76 }
77 Condition::Or { ref or } => {
78 let mut status = Status::NotMet;
79 let children = or
80 .iter()
81 .map(|c| {
82 c.check_value(
83 info,
84 #[cfg(feature = "eval")]
85 rhai_engine,
86 )
87 })
88 .inspect(|r| status = status | r.status)
89 .collect::<Vec<_>>();
90
91 ConditionResult {
92 name: "Or".into(),
93 status,
94 children,
95 }
96 }
97 Condition::AtLeast {
98 should_minimum_meet,
99 ref conditions,
100 } => {
101 let mut met_count = 0;
102 let children = conditions
103 .iter()
104 .map(|c| {
105 c.check_value(
106 info,
107 #[cfg(feature = "eval")]
108 rhai_engine,
109 )
110 })
111 .inspect(|r| {
112 if r.status == Status::Met {
113 met_count += 1;
114 }
115 })
116 .collect::<Vec<_>>();
117
118 let status = if met_count >= should_minimum_meet {
119 Status::Met
120 } else {
121 Status::NotMet
122 };
123
124 ConditionResult {
125 name: format!(
126 "At least meet {} of {}",
127 should_minimum_meet,
128 conditions.len()
129 ),
130 status,
131 children,
132 }
133 }
134 #[allow(unused_variables)]
135 Condition::Condition {
136 ref field,
137 ref constraint,
138 ref path,
139 } => {
140 let node_path = if field.starts_with('/') {
141 field.to_owned()
142 } else {
143 format!("/{}", field)
144 };
145
146 let mut status = Status::Unknown;
147
148 #[allow(unused_mut)]
149 if let Some(mut node) = info.pointer(&node_path).cloned() {
150 #[cfg(feature = "path")]
151 {
152 if let Some(p) = path {
153 let x = jsonpath_lib::select(&node, p)
154 .unwrap()
155 .into_iter()
156 .cloned()
157 .collect();
158 node = Value::Array(x);
159 }
160 }
161
162 status = constraint.check_value(&node);
163 }
164
165 ConditionResult {
166 name: field.to_owned(),
167 status,
168 children: Vec::new(),
169 }
170 }
171 #[cfg(feature = "eval")]
172 Condition::Eval { ref expr } => {
173 let mut scope = Scope::new();
174 if let Ok(val) = to_dynamic(info) {
175 scope.push_dynamic("facts", val);
176 }
177 let status = if rhai_engine
178 .eval_with_scope::<bool>(&mut scope, expr)
179 .unwrap_or(false)
180 {
181 Status::Met
182 } else {
183 Status::NotMet
184 };
185
186 ConditionResult {
187 name: "Eval".to_owned(),
188 status,
189 children: Vec::new(),
190 }
191 }
192 }
193 }
194}
195
196#[derive(Debug, Serialize, Deserialize)]
198pub struct ConditionResult {
199 pub name: String,
201 pub status: Status,
203 pub children: Vec<ConditionResult>,
205}
206
207pub fn and(and: Vec<Condition>) -> Condition {
213 Condition::And { and }
214}
215
216pub fn or(or: Vec<Condition>) -> Condition {
222 Condition::Or { or }
223}
224
225pub fn at_least(
229 should_minimum_meet: usize,
230 conditions: Vec<Condition>,
231) -> Condition {
232 Condition::AtLeast {
233 should_minimum_meet,
234 conditions,
235 }
236}
237
238pub fn string_equals(field: &str, val: &str) -> Condition {
240 Condition::Condition {
241 field: field.into(),
242 constraint: Constraint::StringEquals(val.into()),
243 path: None,
244 }
245}
246
247pub fn string_not_equals(field: &str, val: &str) -> Condition {
248 Condition::Condition {
249 field: field.into(),
250 constraint: Constraint::StringNotEquals(val.into()),
251 path: None,
252 }
253}
254
255pub fn string_contains(field: &str, val: &str) -> Condition {
256 Condition::Condition {
257 field: field.into(),
258 constraint: Constraint::StringContains(val.into()),
259 path: None,
260 }
261}
262
263pub fn string_contains_all(field: &str, val: Vec<&str>) -> Condition {
264 Condition::Condition {
265 field: field.into(),
266 constraint: Constraint::StringContainsAll(
267 val.into_iter().map(ToOwned::to_owned).collect(),
268 ),
269 path: None,
270 }
271}
272
273pub fn string_contains_any(field: &str, val: Vec<&str>) -> Condition {
274 Condition::Condition {
275 field: field.into(),
276 constraint: Constraint::StringContainsAny(
277 val.into_iter().map(ToOwned::to_owned).collect(),
278 ),
279 path: None,
280 }
281}
282
283pub fn string_does_not_contain(field: &str, val: &str) -> Condition {
284 Condition::Condition {
285 field: field.into(),
286 constraint: Constraint::StringDoesNotContain(val.into()),
287 path: None,
288 }
289}
290
291pub fn string_does_not_contain_any(field: &str, val: Vec<&str>) -> Condition {
292 Condition::Condition {
293 field: field.into(),
294 constraint: Constraint::StringDoesNotContainAny(
295 val.into_iter().map(ToOwned::to_owned).collect(),
296 ),
297 path: None,
298 }
299}
300
301pub fn string_in(field: &str, val: Vec<&str>) -> Condition {
302 Condition::Condition {
303 field: field.into(),
304 constraint: Constraint::StringIn(
305 val.into_iter().map(ToOwned::to_owned).collect(),
306 ),
307 path: None,
308 }
309}
310
311pub fn string_not_in(field: &str, val: Vec<&str>) -> Condition {
312 Condition::Condition {
313 field: field.into(),
314 constraint: Constraint::StringNotIn(
315 val.into_iter().map(ToOwned::to_owned).collect(),
316 ),
317 path: None,
318 }
319}
320
321pub fn string_is_subset(field: &str, val: Vec<&str>) -> Condition {
322 Condition::Condition {
323 field: field.into(),
324 constraint: Constraint::StringIsSubset(
325 val.into_iter().map(ToOwned::to_owned).collect(),
326 ),
327 path: None,
328 }
329}
330
331pub fn string_is_substring(field: &str, val: &str) -> Condition {
332 Condition::Condition {
333 field: field.into(),
334 constraint: Constraint::StringIsSubstring(val.into()),
335 path: None,
336 }
337}
338
339pub fn string_has_substring(field: &str, val: &str) -> Condition {
340 Condition::Condition {
341 field: field.into(),
342 constraint: Constraint::StringHasSubstring(val.into()),
343 path: None,
344 }
345}
346
347pub fn int_equals(field: &str, val: i64) -> Condition {
349 Condition::Condition {
350 field: field.into(),
351 constraint: Constraint::IntEquals(val),
352 path: None,
353 }
354}
355
356pub fn int_not_equals(field: &str, val: i64) -> Condition {
357 Condition::Condition {
358 field: field.into(),
359 constraint: Constraint::IntNotEquals(val),
360 path: None,
361 }
362}
363
364pub fn int_contains(field: &str, val: i64) -> Condition {
365 Condition::Condition {
366 field: field.into(),
367 constraint: Constraint::IntContains(val),
368 path: None,
369 }
370}
371
372pub fn int_contains_all(field: &str, val: Vec<i64>) -> Condition {
373 Condition::Condition {
374 field: field.into(),
375 constraint: Constraint::IntContainsAll(val),
376 path: None,
377 }
378}
379
380pub fn int_contains_any(field: &str, val: Vec<i64>) -> Condition {
381 Condition::Condition {
382 field: field.into(),
383 constraint: Constraint::IntContainsAny(val),
384 path: None,
385 }
386}
387
388pub fn int_does_not_contain(field: &str, val: i64) -> Condition {
389 Condition::Condition {
390 field: field.into(),
391 constraint: Constraint::IntDoesNotContain(val),
392 path: None,
393 }
394}
395
396pub fn int_does_not_contain_any(field: &str, val: Vec<i64>) -> Condition {
397 Condition::Condition {
398 field: field.into(),
399 constraint: Constraint::IntDoesNotContainAny(val),
400 path: None,
401 }
402}
403
404pub fn int_in(field: &str, val: Vec<i64>) -> Condition {
405 Condition::Condition {
406 field: field.into(),
407 constraint: Constraint::IntIn(val),
408 path: None,
409 }
410}
411
412pub fn int_not_in(field: &str, val: Vec<i64>) -> Condition {
413 Condition::Condition {
414 field: field.into(),
415 constraint: Constraint::IntNotIn(val),
416 path: None,
417 }
418}
419
420pub fn int_in_range(field: &str, start: i64, end: i64) -> Condition {
421 Condition::Condition {
422 field: field.into(),
423 constraint: Constraint::IntInRange(start, end),
424 path: None,
425 }
426}
427
428pub fn int_not_in_range(field: &str, start: i64, end: i64) -> Condition {
429 Condition::Condition {
430 field: field.into(),
431 constraint: Constraint::IntNotInRange(start, end),
432 path: None,
433 }
434}
435
436pub fn int_less_than(field: &str, val: i64) -> Condition {
437 Condition::Condition {
438 field: field.into(),
439 constraint: Constraint::IntLessThan(val),
440 path: None,
441 }
442}
443
444pub fn int_less_than_inclusive(field: &str, val: i64) -> Condition {
445 Condition::Condition {
446 field: field.into(),
447 constraint: Constraint::IntLessThanInclusive(val),
448 path: None,
449 }
450}
451
452pub fn int_greater_than(field: &str, val: i64) -> Condition {
453 Condition::Condition {
454 field: field.into(),
455 constraint: Constraint::IntGreaterThan(val),
456 path: None,
457 }
458}
459
460pub fn int_greater_than_inclusive(field: &str, val: i64) -> Condition {
461 Condition::Condition {
462 field: field.into(),
463 constraint: Constraint::IntGreaterThanInclusive(val),
464 path: None,
465 }
466}
467
468pub fn float_equals(field: &str, val: f64) -> Condition {
470 Condition::Condition {
471 field: field.into(),
472 constraint: Constraint::FloatEquals(val),
473 path: None,
474 }
475}
476
477pub fn float_not_equals(field: &str, val: f64) -> Condition {
478 Condition::Condition {
479 field: field.into(),
480 constraint: Constraint::FloatNotEquals(val),
481 path: None,
482 }
483}
484
485pub fn float_contains(field: &str, val: f64) -> Condition {
486 Condition::Condition {
487 field: field.into(),
488 constraint: Constraint::FloatContains(val),
489 path: None,
490 }
491}
492
493pub fn float_does_not_contain(field: &str, val: f64) -> Condition {
494 Condition::Condition {
495 field: field.into(),
496 constraint: Constraint::FloatDoesNotContain(val),
497 path: None,
498 }
499}
500
501pub fn float_in(field: &str, val: Vec<f64>) -> Condition {
502 Condition::Condition {
503 field: field.into(),
504 constraint: Constraint::FloatIn(val),
505 path: None,
506 }
507}
508
509pub fn float_not_in(field: &str, val: Vec<f64>) -> Condition {
510 Condition::Condition {
511 field: field.into(),
512 constraint: Constraint::FloatNotIn(val),
513 path: None,
514 }
515}
516
517pub fn float_in_range(field: &str, start: f64, end: f64) -> Condition {
518 Condition::Condition {
519 field: field.into(),
520 constraint: Constraint::FloatInRange(start, end),
521 path: None,
522 }
523}
524
525pub fn float_not_in_range(field: &str, start: f64, end: f64) -> Condition {
526 Condition::Condition {
527 field: field.into(),
528 constraint: Constraint::FloatNotInRange(start, end),
529 path: None,
530 }
531}
532
533pub fn float_less_than(field: &str, val: f64) -> Condition {
534 Condition::Condition {
535 field: field.into(),
536 constraint: Constraint::FloatLessThan(val),
537 path: None,
538 }
539}
540
541pub fn float_less_than_inclusive(field: &str, val: f64) -> Condition {
542 Condition::Condition {
543 field: field.into(),
544 constraint: Constraint::FloatLessThanInclusive(val),
545 path: None,
546 }
547}
548
549pub fn float_greater_than(field: &str, val: f64) -> Condition {
550 Condition::Condition {
551 field: field.into(),
552 constraint: Constraint::FloatGreaterThan(val),
553 path: None,
554 }
555}
556
557pub fn float_greater_than_inclusive(field: &str, val: f64) -> Condition {
558 Condition::Condition {
559 field: field.into(),
560 constraint: Constraint::FloatGreaterThanInclusive(val),
561 path: None,
562 }
563}
564
565pub fn bool_equals(field: &str, val: bool) -> Condition {
567 Condition::Condition {
568 field: field.into(),
569 constraint: Constraint::BoolEquals(val),
570 path: None,
571 }
572}
573
574#[cfg(not(feature = "eval"))]
575#[cfg(test)]
576mod tests {
577 use super::{
578 and, at_least, bool_equals, int_equals, int_in_range, or, string_equals, string_is_subset
579 };
580 use crate::{status::Status, string_is_substring, string_has_substring};
581 use serde_json::{json, Value};
582
583 fn get_test_data() -> Value {
584 json!({
585 "foo": 1,
586 "bar": "bar",
587 "baz": true
588 })
589 }
590
591 #[test]
592 fn and_rules() {
593 let map = get_test_data();
594 let mut root =
596 and(vec![int_equals("foo", 1), string_equals("bar", "bar")]);
597 let mut res = root.check_value(&map);
598
599 assert_eq!(res.status, Status::Met);
600
601 root = and(vec![int_equals("foo", 2), string_equals("bar", "bar")]);
603 res = root.check_value(&map);
604
605 assert_eq!(res.status, Status::NotMet);
606
607 root = and(vec![int_equals("quux", 2), string_equals("bar", "bar")]);
609 res = root.check_value(&map);
610
611 assert_eq!(res.status, Status::Unknown);
612
613 root = and(vec![int_equals("quux", 2), string_equals("bar", "baz")]);
615 res = root.check_value(&map);
616
617 assert_eq!(res.status, Status::NotMet);
618
619 root = and(vec![int_equals("quux", 2), string_equals("fizz", "bar")]);
621 res = root.check_value(&map);
622
623 assert_eq!(res.status, Status::Unknown);
624 }
625
626 #[test]
627 fn or_rules() {
628 let map = get_test_data();
629 let mut root =
631 or(vec![int_equals("foo", 1), string_equals("bar", "bar")]);
632 let mut res = root.check_value(&map);
633
634 assert_eq!(res.status, Status::Met);
635
636 root = or(vec![int_equals("foo", 2), string_equals("bar", "bar")]);
638 res = root.check_value(&map);
639
640 assert_eq!(res.status, Status::Met);
641
642 root = or(vec![int_equals("quux", 2), string_equals("bar", "bar")]);
644 res = root.check_value(&map);
645
646 assert_eq!(res.status, Status::Met);
647
648 root = or(vec![int_equals("quux", 2), string_equals("bar", "baz")]);
650 res = root.check_value(&map);
651
652 assert_eq!(res.status, Status::Unknown);
653
654 root = or(vec![int_equals("quux", 2), string_equals("fizz", "bar")]);
656 res = root.check_value(&map);
657
658 assert_eq!(res.status, Status::Unknown);
659 }
660
661 #[test]
662 fn n_of_rules() {
663 let map = get_test_data();
664 let mut root = at_least(
666 2,
667 vec![
668 int_equals("foo", 1),
669 string_equals("bar", "bar"),
670 bool_equals("baz", false),
671 ],
672 );
673 let mut res = root.check_value(&map);
674
675 assert_eq!(res.status, Status::Met);
676
677 root = at_least(
679 2,
680 vec![
681 int_equals("foo", 1),
682 string_equals("quux", "bar"),
683 bool_equals("baz", false),
684 ],
685 );
686 res = root.check_value(&map);
687
688 assert_eq!(res.status, Status::NotMet);
689
690 root = at_least(
692 2,
693 vec![
694 int_equals("foo", 2),
695 string_equals("quux", "baz"),
696 bool_equals("baz", false),
697 ],
698 );
699 res = root.check_value(&map);
700
701 assert_eq!(res.status, Status::NotMet);
702 }
703
704 #[test]
705 fn string_equals_rule() {
706 let map = get_test_data();
707 let mut rule = string_equals("bar", "bar");
708 let mut res = rule.check_value(&map);
709 assert_eq!(res.status, Status::Met);
710
711 rule = string_equals("bar", "baz");
712 res = rule.check_value(&map);
713 assert_eq!(res.status, Status::NotMet);
714 }
715
716 #[test]
717 fn string_is_subset_rule() {
718 let map = json!({ "foo": ["a", "b"] });
719 let rule = string_is_subset("foo", vec!["a", "c", "b"]);
720 let res = rule.check_value(&map);
721 assert_eq!(res.status, Status::Met);
722 let rule = string_is_subset("foo", vec!["a", "b"]);
723 let res = rule.check_value(&map);
724 assert_eq!(res.status, Status::Met);
725 let rule = string_is_subset("foo", vec!["a", "c"]);
726 let res = rule.check_value(&map);
727 assert_eq!(res.status, Status::NotMet);
728 let rule = string_is_subset("foo", vec![]);
729 let res = rule.check_value(&map);
730 assert_eq!(res.status, Status::NotMet);
731
732 let map = json!({ "foo": [] });
734 let rule = string_is_subset("foo", vec!["a", "c", "b"]);
735 let res = rule.check_value(&map);
736 assert_eq!(res.status, Status::Met);
737 }
738
739 #[test]
740 fn string_is_substring_rule() {
741 let map = json!({ "foo": "abc" });
742 let rule = string_is_substring("foo", "abcd");
743 let res = rule.check_value(&map);
744 assert_eq!(res.status, Status::Met);
745
746 let map = json!({ "foo": "abc" });
747 let rule = string_is_substring("foo", "ab");
748 let res = rule.check_value(&map);
749 assert_eq!(res.status, Status::NotMet);
750 }
751
752 #[test]
753 fn string_has_substring_rule() {
754 let map = json!({ "foo": "abc" });
755 let rule = string_has_substring("foo", "ab");
756 let res = rule.check_value(&map);
757 assert_eq!(res.status, Status::Met);
758
759 let map = json!({ "foo": "abc" });
760 let rule = string_has_substring("foo", "abcd");
761 let res = rule.check_value(&map);
762 assert_eq!(res.status, Status::NotMet);
763 }
764
765 #[test]
766 fn int_equals_rule() {
767 let map = get_test_data();
768 let mut rule = int_equals("foo", 1);
769 let mut res = rule.check_value(&map);
770 assert_eq!(res.status, Status::Met);
771
772 rule = int_equals("foo", 2);
773 res = rule.check_value(&map);
774 assert_eq!(res.status, Status::NotMet);
775
776 rule = int_equals("bar", 2);
778 res = rule.check_value(&map);
779 assert_eq!(res.status, Status::NotMet);
780 }
781
782 #[test]
783 fn int_range_rule() {
784 let map = get_test_data();
785 let mut rule = int_in_range("foo", 1, 3);
786 let mut res = rule.check_value(&map);
787 assert_eq!(res.status, Status::Met);
788
789 rule = int_in_range("foo", 2, 3);
790 res = rule.check_value(&map);
791 assert_eq!(res.status, Status::NotMet);
792
793 rule = int_in_range("bar", 1, 3);
795 res = rule.check_value(&map);
796 assert_eq!(res.status, Status::NotMet);
797 }
798
799 #[test]
800 fn boolean_rule() {
801 let mut map = get_test_data();
802 let mut rule = bool_equals("baz", true);
803 let mut res = rule.check_value(&map);
804 assert_eq!(res.status, Status::Met);
805
806 rule = bool_equals("baz", false);
807 res = rule.check_value(&map);
808 assert_eq!(res.status, Status::NotMet);
809
810 rule = bool_equals("bar", true);
811 res = rule.check_value(&map);
812 assert_eq!(res.status, Status::NotMet);
813
814 rule = bool_equals("bar", false);
815 res = rule.check_value(&map);
816 assert_eq!(res.status, Status::NotMet);
817
818 map["quux".to_owned()] = json!("tRuE");
819 rule = bool_equals("quux", true);
820 res = rule.check_value(&map);
821 assert_eq!(res.status, Status::NotMet);
822 }
823}