json_rules_engine_fork/
lib.rs

1#![allow(dead_code)]
2//! Simple rules engine that represents requirements as a tree, with each node having one or more requirements in order to be "Met".
3//!
4//! A tree of rules is constructed, and then the `.check_json()` method is called.
5//! `json` is a nested `field: value` that will be given to each node in the tree for testing.
6//!
7//! Status output can be either `Met`, `NotMet`, or `Unknown` if the tested field is not present in the json.
8//!
9//! To construct a tree, see the following methods.
10//!
11//! ## Example
12//!
13//! ```rust
14//! extern crate json_rules_engine_fork;
15//! use serde_json::json;
16//!
17//! let tree = json_rules_engine_fork::and(vec![
18//!     json_rules_engine_fork::string_equals("name", "John Doe"),
19//!     json_rules_engine_fork::or(vec![
20//!         json_rules_engine_fork::int_equals("fav_number", 5),
21//!         json_rules_engine_fork::int_in_range("thinking_of", 5, 10)
22//!     ])
23//! ]);
24//! let mut facts = json!({
25//!     "name": "John Doe",
26//!     "fav_number": 5
27//! });
28//! #[cfg(not(feature = "eval"))]
29//! {
30//!     let result = tree.check_value(&facts);
31//!     println!("{:?}", result);
32//!     assert!(result.status == json_rules_engine_fork::Status::Met);
33//! }
34//! // result = ConditionResult { name: "And", status: Met, children: [ConditionResult { name: "Name is John Doe", status: Met, children: [] }, ConditionResult { name: "Or", status: Met, children: [ConditionResult { name: "Favorite number is 5", status: Met, children: [] }, ConditionResult { name: "Thinking of a number between 5 and 10", status: Unknown, children: [] }] }] }
35//! ```
36//!
37//! This creates a tree like the following:
38//!
39//! ```text
40//!                              +---------+
41//!                              |   AND   |
42//!                              +---------+
43//!           _____________________/\_______________
44//!          |                                      |
45//!          V                                      V
46//! +-------------------+                       +--------+
47//! | Name is John Doe  |                       |   OR   |
48//! +-------------------+                       +--------+
49//! | field: "name"     |             ______________/\___________
50//! | value: "John Doe" |            |                           |
51//! +-------------------+            V                           V
52//!                       +----------------------+  +-------------------------+
53//!                       | Favorite number is 5 |  | Number between 5 and 10 |
54//!                       +----------------------+  +-------------------------+
55//!                       | field: "fav_number"  |  | field: "thinking_of"    |
56//!                       | value: 5             |  | start: 5                |
57//!                       +----------------------+  | end: 10                 |
58//!                                                 +-------------------------+
59//! ```
60//!
61//! [1]: enum.Rule.html#method.check
62
63mod condition;
64mod constraint;
65mod error;
66mod event;
67mod rule;
68mod status;
69
70pub use crate::{condition::*, constraint::*, event::*, rule::*, status::*};
71
72#[cfg(feature = "eval")]
73pub use rhai::{serde::from_dynamic, Map};
74
75#[cfg(feature = "eval")]
76use rhai::{
77    def_package,
78    packages::{
79        ArithmeticPackage, BasicArrayPackage, BasicMapPackage, LogicPackage,
80        Package,
81    },
82    Engine as RhaiEngine,
83};
84use serde_json::value::to_value;
85use std::{collections::HashMap, time::Instant};
86
87#[cfg(feature = "email")]
88use crate::event::email_notification::EmailNotification;
89#[cfg(feature = "callback")]
90use crate::event::post_callback::PostCallback;
91
92pub use crate::error::*;
93use serde::Serialize;
94use std::{rc::Rc, sync::RwLock};
95
96#[cfg(feature = "eval")]
97def_package!(rhai:JsonRulesEnginePackage:"Package for json-rules-engine", lib, {
98    ArithmeticPackage::init(lib);
99    LogicPackage::init(lib);
100    BasicArrayPackage::init(lib);
101    BasicMapPackage::init(lib);
102});
103
104#[derive(Default)]
105pub struct Engine {
106    rules: Vec<Rule>,
107    events: HashMap<String, Rc<RwLock<dyn EventTrait>>>,
108    #[cfg(feature = "eval")]
109    rhai_engine: RhaiEngine,
110    coalescences: HashMap<String, (Instant, u64)>,
111}
112
113impl Engine {
114    pub fn new() -> Self {
115        #[allow(unused_mut)]
116        let mut events: HashMap<_, Rc<RwLock<dyn EventTrait>>> = HashMap::new();
117
118        #[cfg(feature = "callback")]
119        {
120            let event = Rc::new(RwLock::new(PostCallback::new()));
121            let key = event.read().unwrap().get_type().to_string();
122            events.insert(key, event);
123        }
124
125        #[cfg(feature = "email")]
126        {
127            let event = Rc::new(RwLock::new(EmailNotification::new()));
128            let key = event.read().unwrap().get_type().to_string();
129            events.insert(key, event);
130        }
131
132        Self {
133            rules: Vec::new(),
134            #[cfg(feature = "eval")]
135            rhai_engine: {
136                let mut engine = RhaiEngine::new_raw();
137                engine.register_global_module(
138                    JsonRulesEnginePackage::new().as_shared_module(),
139                );
140                engine
141            },
142            coalescences: HashMap::new(),
143            events,
144        }
145    }
146
147    pub fn add_rule(&mut self, rule: Rule) {
148        self.rules.push(rule)
149    }
150
151    pub fn add_rules(&mut self, rules: Vec<Rule>) {
152        self.rules.extend(rules)
153    }
154
155    pub fn load_rules(&mut self, rules: Vec<Rule>) {
156        self.rules = rules;
157    }
158
159    pub fn clear(&mut self) {
160        self.rules.clear();
161    }
162
163    #[cfg(feature = "eval")]
164    pub fn add_function(&mut self, fname: &str, f: fn(Map) -> bool) {
165        self.rhai_engine.register_fn(fname, f);
166    }
167
168    pub fn add_event(&mut self, f: Rc<RwLock<dyn EventTrait>>) {
169        let key = f.read().unwrap().get_type().to_string();
170        self.events.insert(key, f);
171    }
172
173    pub async fn run<T: Serialize>(
174        &mut self,
175        facts: &T,
176    ) -> Result<Vec<RuleResult>> {
177        let facts = to_value(facts)?;
178        let mut met_rule_results: Vec<RuleResult> = self
179            .rules
180            .iter()
181            .map(|rule| {
182                rule.check_value(
183                    &facts,
184                    #[cfg(feature = "eval")]
185                    &self.rhai_engine,
186                )
187            })
188            .filter(|rule_result| {
189                rule_result.condition_result.status == Status::Met
190            })
191            .collect();
192
193        self.coalescences.retain(|_k, (start, expiration)| {
194            start.elapsed().as_secs() < *expiration
195        });
196
197        for rule_result in met_rule_results.iter_mut() {
198            // filter the events
199            let mut cole = self.coalescences.clone();
200            rule_result.events.retain(|event| {
201                if let (Some(coalescence_group), Some(coalescence)) =
202                    (&event.coalescence_group, event.coalescence)
203                {
204                    if cole.contains_key(coalescence_group) {
205                        return false;
206                    } else {
207                        cole.insert(
208                            coalescence_group.clone(),
209                            (Instant::now(), coalescence),
210                        );
211                    }
212                }
213
214                true
215            });
216
217            self.coalescences = cole;
218
219            // TODO run all the async events in parallel
220            // run the events
221            for event in &rule_result.events {
222                let e =
223                    self.events.get_mut(&event.event.ty).ok_or_else(|| {
224                        Error::EventError(
225                            "Event type doesn't exist".to_string(),
226                        )
227                    })?;
228
229                e.read()
230                    .unwrap()
231                    .validate(&event.event.params)
232                    .map_err(Error::EventError)?;
233                e.write()
234                    .unwrap()
235                    .trigger(&event.event.params, &facts)
236                    .await?;
237            }
238        }
239
240        Ok(met_rule_results)
241    }
242}