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