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}