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 int_equals(field: &str, val: i64) -> Condition {
323 Condition::Condition {
324 field: field.into(),
325 constraint: Constraint::IntEquals(val),
326 path: None,
327 }
328}
329
330pub fn int_not_equals(field: &str, val: i64) -> Condition {
331 Condition::Condition {
332 field: field.into(),
333 constraint: Constraint::IntNotEquals(val),
334 path: None,
335 }
336}
337
338pub fn int_contains(field: &str, val: i64) -> Condition {
339 Condition::Condition {
340 field: field.into(),
341 constraint: Constraint::IntContains(val),
342 path: None,
343 }
344}
345
346pub fn int_contains_all(field: &str, val: Vec<i64>) -> Condition {
347 Condition::Condition {
348 field: field.into(),
349 constraint: Constraint::IntContainsAll(val),
350 path: None,
351 }
352}
353
354pub fn int_contains_any(field: &str, val: Vec<i64>) -> Condition {
355 Condition::Condition {
356 field: field.into(),
357 constraint: Constraint::IntContainsAny(val),
358 path: None,
359 }
360}
361
362pub fn int_does_not_contain(field: &str, val: i64) -> Condition {
363 Condition::Condition {
364 field: field.into(),
365 constraint: Constraint::IntDoesNotContain(val),
366 path: None,
367 }
368}
369
370pub fn int_does_not_contain_any(field: &str, val: Vec<i64>) -> Condition {
371 Condition::Condition {
372 field: field.into(),
373 constraint: Constraint::IntDoesNotContainAny(val),
374 path: None,
375 }
376}
377
378pub fn int_in(field: &str, val: Vec<i64>) -> Condition {
379 Condition::Condition {
380 field: field.into(),
381 constraint: Constraint::IntIn(val),
382 path: None,
383 }
384}
385
386pub fn int_not_in(field: &str, val: Vec<i64>) -> Condition {
387 Condition::Condition {
388 field: field.into(),
389 constraint: Constraint::IntNotIn(val),
390 path: None,
391 }
392}
393
394pub fn int_in_range(field: &str, start: i64, end: i64) -> Condition {
395 Condition::Condition {
396 field: field.into(),
397 constraint: Constraint::IntInRange(start, end),
398 path: None,
399 }
400}
401
402pub fn int_not_in_range(field: &str, start: i64, end: i64) -> Condition {
403 Condition::Condition {
404 field: field.into(),
405 constraint: Constraint::IntNotInRange(start, end),
406 path: None,
407 }
408}
409
410pub fn int_less_than(field: &str, val: i64) -> Condition {
411 Condition::Condition {
412 field: field.into(),
413 constraint: Constraint::IntLessThan(val),
414 path: None,
415 }
416}
417
418pub fn int_less_than_inclusive(field: &str, val: i64) -> Condition {
419 Condition::Condition {
420 field: field.into(),
421 constraint: Constraint::IntLessThanInclusive(val),
422 path: None,
423 }
424}
425
426pub fn int_greater_than(field: &str, val: i64) -> Condition {
427 Condition::Condition {
428 field: field.into(),
429 constraint: Constraint::IntGreaterThan(val),
430 path: None,
431 }
432}
433
434pub fn int_greater_than_inclusive(field: &str, val: i64) -> Condition {
435 Condition::Condition {
436 field: field.into(),
437 constraint: Constraint::IntGreaterThanInclusive(val),
438 path: None,
439 }
440}
441
442pub fn float_equals(field: &str, val: f64) -> Condition {
444 Condition::Condition {
445 field: field.into(),
446 constraint: Constraint::FloatEquals(val),
447 path: None,
448 }
449}
450
451pub fn float_not_equals(field: &str, val: f64) -> Condition {
452 Condition::Condition {
453 field: field.into(),
454 constraint: Constraint::FloatNotEquals(val),
455 path: None,
456 }
457}
458
459pub fn float_contains(field: &str, val: f64) -> Condition {
460 Condition::Condition {
461 field: field.into(),
462 constraint: Constraint::FloatContains(val),
463 path: None,
464 }
465}
466
467pub fn float_does_not_contain(field: &str, val: f64) -> Condition {
468 Condition::Condition {
469 field: field.into(),
470 constraint: Constraint::FloatDoesNotContain(val),
471 path: None,
472 }
473}
474
475pub fn float_in(field: &str, val: Vec<f64>) -> Condition {
476 Condition::Condition {
477 field: field.into(),
478 constraint: Constraint::FloatIn(val),
479 path: None,
480 }
481}
482
483pub fn float_not_in(field: &str, val: Vec<f64>) -> Condition {
484 Condition::Condition {
485 field: field.into(),
486 constraint: Constraint::FloatNotIn(val),
487 path: None,
488 }
489}
490
491pub fn float_in_range(field: &str, start: f64, end: f64) -> Condition {
492 Condition::Condition {
493 field: field.into(),
494 constraint: Constraint::FloatInRange(start, end),
495 path: None,
496 }
497}
498
499pub fn float_not_in_range(field: &str, start: f64, end: f64) -> Condition {
500 Condition::Condition {
501 field: field.into(),
502 constraint: Constraint::FloatNotInRange(start, end),
503 path: None,
504 }
505}
506
507pub fn float_less_than(field: &str, val: f64) -> Condition {
508 Condition::Condition {
509 field: field.into(),
510 constraint: Constraint::FloatLessThan(val),
511 path: None,
512 }
513}
514
515pub fn float_less_than_inclusive(field: &str, val: f64) -> Condition {
516 Condition::Condition {
517 field: field.into(),
518 constraint: Constraint::FloatLessThanInclusive(val),
519 path: None,
520 }
521}
522
523pub fn float_greater_than(field: &str, val: f64) -> Condition {
524 Condition::Condition {
525 field: field.into(),
526 constraint: Constraint::FloatGreaterThan(val),
527 path: None,
528 }
529}
530
531pub fn float_greater_than_inclusive(field: &str, val: f64) -> Condition {
532 Condition::Condition {
533 field: field.into(),
534 constraint: Constraint::FloatGreaterThanInclusive(val),
535 path: None,
536 }
537}
538
539pub fn bool_equals(field: &str, val: bool) -> Condition {
541 Condition::Condition {
542 field: field.into(),
543 constraint: Constraint::BoolEquals(val),
544 path: None,
545 }
546}
547
548#[cfg(not(feature = "eval"))]
549#[cfg(test)]
550mod tests {
551 use super::{
552 and, at_least, bool_equals, int_equals, int_in_range, or, string_equals,
553 };
554 use crate::status::Status;
555 use serde_json::{json, Value};
556
557 fn get_test_data() -> Value {
558 json!({
559 "foo": 1,
560 "bar": "bar",
561 "baz": true
562 })
563 }
564
565 #[test]
566 fn and_rules() {
567 let map = get_test_data();
568 let mut root =
570 and(vec![int_equals("foo", 1), string_equals("bar", "bar")]);
571 let mut res = root.check_value(&map);
572
573 assert_eq!(res.status, Status::Met);
574
575 root = and(vec![int_equals("foo", 2), string_equals("bar", "bar")]);
577 res = root.check_value(&map);
578
579 assert_eq!(res.status, Status::NotMet);
580
581 root = and(vec![int_equals("quux", 2), string_equals("bar", "bar")]);
583 res = root.check_value(&map);
584
585 assert_eq!(res.status, Status::Unknown);
586
587 root = and(vec![int_equals("quux", 2), string_equals("bar", "baz")]);
589 res = root.check_value(&map);
590
591 assert_eq!(res.status, Status::NotMet);
592
593 root = and(vec![int_equals("quux", 2), string_equals("fizz", "bar")]);
595 res = root.check_value(&map);
596
597 assert_eq!(res.status, Status::Unknown);
598 }
599
600 #[test]
601 fn or_rules() {
602 let map = get_test_data();
603 let mut root =
605 or(vec![int_equals("foo", 1), string_equals("bar", "bar")]);
606 let mut res = root.check_value(&map);
607
608 assert_eq!(res.status, Status::Met);
609
610 root = or(vec![int_equals("foo", 2), string_equals("bar", "bar")]);
612 res = root.check_value(&map);
613
614 assert_eq!(res.status, Status::Met);
615
616 root = or(vec![int_equals("quux", 2), string_equals("bar", "bar")]);
618 res = root.check_value(&map);
619
620 assert_eq!(res.status, Status::Met);
621
622 root = or(vec![int_equals("quux", 2), string_equals("bar", "baz")]);
624 res = root.check_value(&map);
625
626 assert_eq!(res.status, Status::Unknown);
627
628 root = or(vec![int_equals("quux", 2), string_equals("fizz", "bar")]);
630 res = root.check_value(&map);
631
632 assert_eq!(res.status, Status::Unknown);
633 }
634
635 #[test]
636 fn n_of_rules() {
637 let map = get_test_data();
638 let mut root = at_least(
640 2,
641 vec![
642 int_equals("foo", 1),
643 string_equals("bar", "bar"),
644 bool_equals("baz", false),
645 ],
646 );
647 let mut res = root.check_value(&map);
648
649 assert_eq!(res.status, Status::Met);
650
651 root = at_least(
653 2,
654 vec![
655 int_equals("foo", 1),
656 string_equals("quux", "bar"),
657 bool_equals("baz", false),
658 ],
659 );
660 res = root.check_value(&map);
661
662 assert_eq!(res.status, Status::NotMet);
663
664 root = at_least(
666 2,
667 vec![
668 int_equals("foo", 2),
669 string_equals("quux", "baz"),
670 bool_equals("baz", false),
671 ],
672 );
673 res = root.check_value(&map);
674
675 assert_eq!(res.status, Status::NotMet);
676 }
677
678 #[test]
679 fn string_equals_rule() {
680 let map = get_test_data();
681 let mut rule = string_equals("bar", "bar");
682 let mut res = rule.check_value(&map);
683 assert_eq!(res.status, Status::Met);
684
685 rule = string_equals("bar", "baz");
686 res = rule.check_value(&map);
687 assert_eq!(res.status, Status::NotMet);
688 }
689
690 #[test]
691 fn int_equals_rule() {
692 let map = get_test_data();
693 let mut rule = int_equals("foo", 1);
694 let mut res = rule.check_value(&map);
695 assert_eq!(res.status, Status::Met);
696
697 rule = int_equals("foo", 2);
698 res = rule.check_value(&map);
699 assert_eq!(res.status, Status::NotMet);
700
701 rule = int_equals("bar", 2);
703 res = rule.check_value(&map);
704 assert_eq!(res.status, Status::NotMet);
705 }
706
707 #[test]
708 fn int_range_rule() {
709 let map = get_test_data();
710 let mut rule = int_in_range("foo", 1, 3);
711 let mut res = rule.check_value(&map);
712 assert_eq!(res.status, Status::Met);
713
714 rule = int_in_range("foo", 2, 3);
715 res = rule.check_value(&map);
716 assert_eq!(res.status, Status::NotMet);
717
718 rule = int_in_range("bar", 1, 3);
720 res = rule.check_value(&map);
721 assert_eq!(res.status, Status::NotMet);
722 }
723
724 #[test]
725 fn boolean_rule() {
726 let mut map = get_test_data();
727 let mut rule = bool_equals("baz", true);
728 let mut res = rule.check_value(&map);
729 assert_eq!(res.status, Status::Met);
730
731 rule = bool_equals("baz", false);
732 res = rule.check_value(&map);
733 assert_eq!(res.status, Status::NotMet);
734
735 rule = bool_equals("bar", true);
736 res = rule.check_value(&map);
737 assert_eq!(res.status, Status::NotMet);
738
739 rule = bool_equals("bar", false);
740 res = rule.check_value(&map);
741 assert_eq!(res.status, Status::NotMet);
742
743 map["quux".to_owned()] = json!("tRuE");
744 rule = bool_equals("quux", true);
745 res = rule.check_value(&map);
746 assert_eq!(res.status, Status::NotMet);
747 }
748}