zen_engine/
engine.rs

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