mirror of
https://github.com/gorules/zen.git
synced 2026-10-04 08:02:28 +00:00
149 lines
5.0 KiB
Rust
149 lines
5.0 KiB
Rust
//! # ZEN Engine
|
|
//!
|
|
//! ZEN Engine is business-friendly Open-Source Business Rules Engine (BRE) which executes decision
|
|
//! models according to the GoRules JSON Decision Model (JDM) standard. It's written in Rust and
|
|
//! provides native bindings for NodeJS and Python.
|
|
//!
|
|
//! # Usage
|
|
//!
|
|
//! To execute a simple decision using a Noop (default) loader you can use the code below.
|
|
//!
|
|
//! ```rust
|
|
//! use serde_json::json;
|
|
//! use zen_engine::DecisionEngine;
|
|
//! use zen_engine::model::DecisionContent;
|
|
//!
|
|
//! async fn evaluate() {
|
|
//! let decision_content: DecisionContent = serde_json::from_str(include_str!("jdm_graph.json")).unwrap();
|
|
//! let engine = DecisionEngine::default();
|
|
//! let decision = engine.create_decision(decision_content.into());
|
|
//!
|
|
//! let result = decision.evaluate(&json!({ "input": 12 })).await;
|
|
//! }
|
|
//! ```
|
|
//!
|
|
//! Alternatively, you may create decision indirectly without constructing the engine utilising
|
|
//! `Decision::from` function.
|
|
//!
|
|
//! # Loaders
|
|
//!
|
|
//! For more advanced use cases where you want to load multiple decisions and utilise graphs you
|
|
//! may use one of the following pre-made loaders:
|
|
//! - FilesystemLoader - with a given path as a root it tries to load a decision based on relative path
|
|
//! - MemoryLoader - works as a HashMap (key-value store)
|
|
//! - ClosureLoader - allows for definition of simple async callback function which takes key as a parameter
|
|
//! and returns an `Arc<DecisionContent>` instance
|
|
//! - NoopLoader - (default) fails to load decision, allows for usage of create_decision
|
|
//! (mostly existing for streamlining API across languages)
|
|
//!
|
|
//! ## Filesystem loader
|
|
//!
|
|
//! Assuming that you have a folder with decision models (.json files) which is located under /app/decisions,
|
|
//! you may use FilesystemLoader in the following way:
|
|
//!
|
|
//! ```rust
|
|
//! use serde_json::json;
|
|
//! use zen_engine::DecisionEngine;
|
|
//! use zen_engine::loader::{FilesystemLoader, FilesystemLoaderOptions};
|
|
//!
|
|
//! async fn evaluate() {
|
|
//! let engine = DecisionEngine::new(FilesystemLoader::new(FilesystemLoaderOptions {
|
|
//! root: "/app/decisions"
|
|
//! }));
|
|
//!
|
|
//! let context = json!({ "customer": { "joinedAt": "2022-01-01" } });
|
|
//! // If you plan on using it multiple times, you may cache JDM for minor performance gains
|
|
//! // In case of bindings (in other languages, this increase is much greater)
|
|
//! {
|
|
//! let promotion_decision = engine.get_decision("commercial/promotion.json").await.unwrap();
|
|
//! let result = promotion_decision.evaluate(&context).await.unwrap();
|
|
//! }
|
|
//!
|
|
//! // Or on demand
|
|
//! {
|
|
//! let result = engine.evaluate("commercial/promotion.json", &context).await.unwrap();
|
|
//! }
|
|
//! }
|
|
//!
|
|
//!
|
|
//! ```
|
|
//!
|
|
//! ## Custom loader
|
|
//! You may create a custom loader for zen engine by implementing `DecisionLoader` trait.
|
|
//! Here's an example of how MemoryLoader has been implemented.
|
|
//! ```rust
|
|
//! use std::collections::HashMap;
|
|
//! use std::sync::{Arc, RwLock};
|
|
//! use zen_engine::loader::{DecisionLoader, LoaderError, LoaderResponse};
|
|
//! use zen_engine::model::DecisionContent;
|
|
//!
|
|
//! #[derive(Debug, Default)]
|
|
//! pub struct MemoryLoader {
|
|
//! memory_refs: RwLock<HashMap<String, Arc<DecisionContent>>>,
|
|
//! }
|
|
//!
|
|
//! impl MemoryLoader {
|
|
//! pub fn add<K, D>(&self, key: K, content: D)
|
|
//! where
|
|
//! K: Into<String>,
|
|
//! D: Into<DecisionContent>,
|
|
//! {
|
|
//! let mut mref = self.memory_refs.write().unwrap();
|
|
//! mref.insert(key.into(), Arc::new(content.into()));
|
|
//! }
|
|
//!
|
|
//! pub fn get<K>(&self, key: K) -> Option<Arc<DecisionContent>>
|
|
//! where
|
|
//! K: AsRef<str>,
|
|
//! {
|
|
//! let mref = self.memory_refs.read().unwrap();
|
|
//! mref.get(key.as_ref()).map(|r| r.clone())
|
|
//! }
|
|
//!
|
|
//! pub fn remove<K>(&self, key: K) -> bool
|
|
//! where
|
|
//! K: AsRef<str>,
|
|
//! {
|
|
//! let mut mref = self.memory_refs.write().unwrap();
|
|
//! mref.remove(key.as_ref()).is_some()
|
|
//! }
|
|
//! }
|
|
//!
|
|
//! impl DecisionLoader for MemoryLoader {
|
|
//! fn load<'a>(&'a self, key: &'a str) -> impl Future<Output = LoaderResponse> + 'a {
|
|
//! async move {
|
|
//! self.get(&key)
|
|
//! .ok_or_else(|| LoaderError::NotFound(key.to_string()).into())
|
|
//! }
|
|
//! }
|
|
//! ```
|
|
|
|
#![forbid(unsafe_code)]
|
|
#![deny(clippy::unwrap_used)]
|
|
#![allow(clippy::module_inception)]
|
|
|
|
mod config;
|
|
mod decision;
|
|
mod decision_graph;
|
|
mod engine;
|
|
pub mod error;
|
|
pub mod loader;
|
|
pub mod model;
|
|
pub mod nodes;
|
|
pub mod policy;
|
|
pub mod workspace;
|
|
|
|
pub const ENGINE_VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
|
|
pub use config::ZEN_CONFIG;
|
|
pub use decision::Decision;
|
|
pub use decision_graph::{
|
|
DecisionGraphResponse, DecisionGraphTrace, DecisionGraphValidationError, EvaluationTrace,
|
|
};
|
|
pub use engine::{
|
|
DecisionEngine, EvaluationOptions, EvaluationSerializedOptions, EvaluationTraceKind,
|
|
};
|
|
pub use error::{CompileFailure, ContentKindError, EvaluationError};
|
|
pub use workspace::Workspace;
|
|
pub use zen_expression::Variable;
|