zen_engine/
engine.rs

1use crate::decision::Decision;
2use crate::decision_graph::graph::DecisionGraphResponse;
3use crate::loader::{ClosureLoader, DynamicLoader, LoaderResponse, LoaderResult, NoopLoader};
4use crate::model::DecisionContent;
5use crate::nodes::custom::{DynamicCustomNode, NoopCustomNode};
6use crate::nodes::function::http_handler::DynamicHttpHandler;
7use crate::EvaluationError;
8use serde_json::Value;
9use std::fmt::Debug;
10use std::future::Future;
11use std::sync::Arc;
12use strum::{EnumString, IntoStaticStr};
13use zen_expression::variable::Variable;
14
15/// Structure used for generating and evaluating JDM decisions
16#[derive(Debug, Clone)]
17pub struct DecisionEngine {
18    loader: DynamicLoader,
19    adapter: DynamicCustomNode,
20    http_handler: DynamicHttpHandler,
21}
22
23#[derive(Debug)]
24pub struct EvaluationOptions {
25    pub trace: bool,
26    pub max_depth: u8,
27}
28
29impl Default for EvaluationOptions {
30    fn default() -> Self {
31        Self {
32            trace: false,
33            max_depth: 10,
34        }
35    }
36}
37
38#[derive(Debug)]
39pub struct EvaluationSerializedOptions {
40    pub trace: EvaluationTraceKind,
41    pub max_depth: u8,
42}
43
44impl Default for EvaluationSerializedOptions {
45    fn default() -> Self {
46        Self {
47            trace: EvaluationTraceKind::None,
48            max_depth: 10,
49        }
50    }
51}
52
53#[derive(Debug, Default, PartialEq, Eq, EnumString, IntoStaticStr)]
54#[strum(serialize_all = "camelCase")]
55pub enum EvaluationTraceKind {
56    #[default]
57    None,
58    Default,
59    String,
60    Reference,
61    ReferenceString,
62}
63
64impl EvaluationTraceKind {
65    pub fn serialize_trace(&self, trace: &Variable) -> Value {
66        match self {
67            EvaluationTraceKind::None => Value::Null,
68            EvaluationTraceKind::Default => serde_json::to_value(&trace).unwrap_or_default(),
69            EvaluationTraceKind::String => {
70                Value::String(serde_json::to_string(&trace).unwrap_or_default())
71            }
72            EvaluationTraceKind::Reference => {
73                serde_json::to_value(&trace.serialize_ref()).unwrap_or_default()
74            }
75            EvaluationTraceKind::ReferenceString => {
76                Value::String(serde_json::to_string(&trace.serialize_ref()).unwrap_or_default())
77            }
78        }
79    }
80}
81
82impl Default for DecisionEngine {
83    fn default() -> Self {
84        Self {
85            loader: Arc::new(NoopLoader::default()),
86            adapter: Arc::new(NoopCustomNode::default()),
87            http_handler: None,
88        }
89    }
90}
91
92impl DecisionEngine {
93    pub fn new(loader: DynamicLoader, adapter: DynamicCustomNode) -> Self {
94        Self {
95            loader,
96            adapter,
97            http_handler: None,
98        }
99    }
100
101    pub fn with_adapter(mut self, adapter: DynamicCustomNode) -> Self {
102        self.adapter = adapter;
103        self
104    }
105
106    pub fn with_loader(mut self, loader: DynamicLoader) -> Self {
107        self.loader = loader;
108        self
109    }
110
111    pub fn with_http_handler(mut self, http_handler: DynamicHttpHandler) -> Self {
112        self.http_handler = http_handler;
113        self
114    }
115
116    pub fn with_closure_loader<F, O>(mut self, loader: F) -> Self
117    where
118        F: Fn(String) -> O + Sync + Send + 'static,
119        O: Future<Output = LoaderResponse> + Send,
120    {
121        self.loader = Arc::new(ClosureLoader::new(loader));
122        self
123    }
124
125    /// Evaluates a decision through loader using a key
126    pub async fn evaluate<K>(
127        &self,
128        key: K,
129        context: Variable,
130    ) -> Result<DecisionGraphResponse, Box<EvaluationError>>
131    where
132        K: AsRef<str>,
133    {
134        self.evaluate_with_opts(key, context, Default::default())
135            .await
136    }
137
138    /// Evaluates a decision through loader using a key with advanced options
139    pub async fn evaluate_with_opts<K>(
140        &self,
141        key: K,
142        context: Variable,
143        options: EvaluationOptions,
144    ) -> Result<DecisionGraphResponse, Box<EvaluationError>>
145    where
146        K: AsRef<str>,
147    {
148        let content = self.loader.load(key.as_ref()).await?;
149        let decision = self.create_decision(content);
150        decision.evaluate_with_opts(context, options).await
151    }
152
153    pub async fn evaluate_serialized<K>(
154        &self,
155        key: K,
156        context: Variable,
157        options: EvaluationSerializedOptions,
158    ) -> Result<Value, Value>
159    where
160        K: AsRef<str>,
161    {
162        let content = self
163            .loader
164            .load(key.as_ref())
165            .await
166            .map_err(|err| Value::String(err.to_string()))?;
167
168        let decision = self.create_decision(content);
169        decision.evaluate_serialized(context, options).await
170    }
171
172    /// Creates a decision from DecisionContent, exists for easier binding creation
173    pub fn create_decision(&self, content: Arc<DecisionContent>) -> Decision {
174        Decision::from(content)
175            .with_loader(self.loader.clone())
176            .with_adapter(self.adapter.clone())
177            .with_http_handler(self.http_handler.clone())
178    }
179
180    /// Retrieves a decision based on the loader
181    pub async fn get_decision(&self, key: &str) -> LoaderResult<Decision> {
182        let content = self.loader.load(key).await?;
183        Ok(self.create_decision(content))
184    }
185    pub fn loader(&self) -> DynamicLoader {
186        self.loader.clone()
187    }
188}