Skip to main content

zen_engine/
lib.rs

1//! # ZEN Engine
2//!
3//! ZEN Engine is business-friendly Open-Source Business Rules Engine (BRE) which executes decision
4//! models according to the GoRules JSON Decision Model (JDM) standard. It's written in Rust and
5//! provides native bindings for NodeJS and Python.
6//!
7//! # Usage
8//!
9//! To execute a simple decision using a Noop (default) loader you can use the code below.
10//!
11//! ```rust
12//! use serde_json::json;
13//! use zen_engine::DecisionEngine;
14//! use zen_engine::model::DecisionContent;
15//!
16//! async fn evaluate() {
17//!     let decision_content: DecisionContent = serde_json::from_str(include_str!("jdm_graph.json")).unwrap();
18//!     let engine = DecisionEngine::default();
19//!     let decision = engine.create_decision(decision_content.into());
20//!
21//!     let result = decision.evaluate(&json!({ "input": 12 })).await;
22//! }
23//! ```
24//!
25//! Alternatively, you may create decision indirectly without constructing the engine utilising
26//! `Decision::from` function.
27//!
28//! # Loaders
29//!
30//! For more advanced use cases where you want to load multiple decisions and utilise graphs you
31//! may use one of the following pre-made loaders:
32//! - FilesystemLoader - with a given path as a root it tries to load a decision based on relative path
33//! - MemoryLoader - works as a HashMap (key-value store)
34//! - ClosureLoader - allows for definition of simple async callback function which takes key as a parameter
35//! and returns an `Arc<DecisionContent>` instance
36//! - NoopLoader - (default) fails to load decision, allows for usage of create_decision
37//! (mostly existing for streamlining API across languages)
38//!
39//! ## Filesystem loader
40//!
41//! Assuming that you have a folder with decision models (.json files) which is located under /app/decisions,
42//! you may use FilesystemLoader in the following way:
43//!
44//! ```rust
45//! use serde_json::json;
46//! use zen_engine::DecisionEngine;
47//! use zen_engine::loader::{FilesystemLoader, FilesystemLoaderOptions};
48//!
49//! async fn evaluate() {
50//!     let engine = DecisionEngine::new(FilesystemLoader::new(FilesystemLoaderOptions {
51//!         root: "/app/decisions"
52//!     }));
53//!     
54//!     let context = json!({ "customer": { "joinedAt": "2022-01-01" } });
55//!     // If you plan on using it multiple times, you may cache JDM for minor performance gains
56//!     // In case of bindings (in other languages, this increase is much greater)
57//!     {
58//!         let promotion_decision = engine.get_decision("commercial/promotion.json").await.unwrap();
59//!         let result = promotion_decision.evaluate(&context).await.unwrap();
60//!     }
61//!     
62//!     // Or on demand
63//!     {
64//!         let result = engine.evaluate("commercial/promotion.json", &context).await.unwrap();
65//!     }
66//! }
67//!
68//!
69//! ```
70//!
71//! ## Custom loader
72//! You may create a custom loader for zen engine by implementing `DecisionLoader` trait.
73//! Here's an example of how MemoryLoader has been implemented.
74//! ```rust
75//! use std::collections::HashMap;
76//! use std::sync::{Arc, RwLock};
77//! use zen_engine::loader::{DecisionLoader, LoaderError, LoaderResponse};
78//! use zen_engine::model::DecisionContent;
79//!
80//! #[derive(Debug, Default)]
81//! pub struct MemoryLoader {
82//!     memory_refs: RwLock<HashMap<String, Arc<DecisionContent>>>,
83//! }
84//!
85//! impl MemoryLoader {
86//!     pub fn add<K, D>(&self, key: K, content: D)
87//!         where
88//!             K: Into<String>,
89//!             D: Into<DecisionContent>,
90//!     {
91//!         let mut mref = self.memory_refs.write().unwrap();
92//!         mref.insert(key.into(), Arc::new(content.into()));
93//!     }
94//!
95//!     pub fn get<K>(&self, key: K) -> Option<Arc<DecisionContent>>
96//!         where
97//!             K: AsRef<str>,
98//!     {
99//!         let mref = self.memory_refs.read().unwrap();
100//!         mref.get(key.as_ref()).map(|r| r.clone())
101//!     }
102//!
103//!     pub fn remove<K>(&self, key: K) -> bool
104//!         where
105//!             K: AsRef<str>,
106//!     {
107//!         let mut mref = self.memory_refs.write().unwrap();
108//!         mref.remove(key.as_ref()).is_some()
109//!     }
110//! }
111//!
112//! impl DecisionLoader for MemoryLoader {
113//! fn load<'a>(&'a self, key: &'a str) -> impl Future<Output = LoaderResponse> + 'a {
114//!     async move {
115//!         self.get(&key)
116//!             .ok_or_else(|| LoaderError::NotFound(key.to_string()).into())
117//!     }
118//! }
119//! ```
120
121#![forbid(unsafe_code)]
122#![deny(clippy::unwrap_used)]
123#![allow(clippy::module_inception)]
124
125mod config;
126mod decision;
127mod decision_graph;
128mod engine;
129pub mod error;
130pub mod loader;
131pub mod model;
132pub mod nodes;
133pub mod policy;
134pub mod workspace;
135
136pub const ENGINE_VERSION: &str = env!("CARGO_PKG_VERSION");
137
138pub use config::ZEN_CONFIG;
139pub use decision::Decision;
140pub use decision_graph::{
141    DecisionGraphResponse, DecisionGraphTrace, DecisionGraphValidationError, EvaluationTrace,
142};
143pub use engine::{
144    DecisionEngine, EvaluationOptions, EvaluationSerializedOptions, EvaluationTraceKind,
145};
146pub use error::{CompileFailure, ContentKindError, EvaluationError};
147pub use workspace::Workspace;
148pub use zen_expression::Variable;