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#[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 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 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 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 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}