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