zen_engine/nodes/decision_table/
mod.rs1use crate::nodes::definition::NodeHandler;
2use crate::nodes::result::NodeResult;
3use crate::nodes::{NodeContext, NodeResponse};
4use ahash::HashMap;
5use serde::Serialize;
6use std::ops::Deref;
7use std::rc::Rc;
8use std::sync::Arc;
9use zen_expression::variable::ToVariable;
10use zen_expression::Isolate;
11use zen_types::decision::{DecisionTableContent, DecisionTableHitPolicy, TransformAttributes};
12use zen_types::variable::Variable;
13#[derive(Debug, Clone)]
14pub struct DecisionTableNodeHandler;
15
16pub type DecisionTableNodeData = DecisionTableContent;
17
18type DecisionTableContext = NodeContext<DecisionTableNodeData, DecisionTableNodeTrace>;
19
20impl NodeHandler for DecisionTableNodeHandler {
21 type NodeData = DecisionTableNodeData;
22 type TraceData = DecisionTableNodeTrace;
23
24 fn transform_attributes(
25 &self,
26 ctx: &NodeContext<Self::NodeData, Self::TraceData>,
27 ) -> Option<TransformAttributes> {
28 Some(ctx.node.transform_attributes.clone())
29 }
30
31 async fn handle(&self, ctx: NodeContext<Self::NodeData, Self::TraceData>) -> NodeResult {
32 match ctx.node.hit_policy {
33 DecisionTableHitPolicy::First => self.handle_first_hit(ctx),
34 DecisionTableHitPolicy::Collect => self.handle_collect(ctx),
35 }
36 }
37}
38
39impl DecisionTableNodeHandler {
40 fn handle_first_hit(&self, ctx: DecisionTableContext) -> NodeResult {
41 let mut isolate = Isolate::new();
42 isolate.set_environment(ctx.input.depth_clone(1));
43
44 for (index, rule) in ctx.node.rules.iter().enumerate() {
45 if let Some(result) = self.evaluate_row(&ctx, rule, &mut isolate) {
46 return match result {
47 RowResult::Output(output) => ctx.success(output),
48 RowResult::WithTrace {
49 output,
50 reference_map,
51 rule,
52 } => {
53 ctx.trace(|t| {
54 *t = DecisionTableNodeTrace::FirstHit(DecisionTableRowTrace {
55 reference_map,
56 index,
57 rule,
58 })
59 });
60
61 ctx.success(output)
62 }
63 };
64 }
65 }
66
67 Ok(NodeResponse {
68 output: Variable::Null,
69 trace_data: None,
70 })
71 }
72
73 fn handle_collect(&self, ctx: DecisionTableContext) -> NodeResult {
74 let mut isolate = Isolate::new();
75 let mut outputs = Vec::new();
76 let mut traces = Vec::new();
77 isolate.set_environment(ctx.input.depth_clone(1));
78
79 for (index, rule) in ctx.node.rules.iter().enumerate() {
80 if let Some(result) = self.evaluate_row(&ctx, rule, &mut isolate) {
81 match result {
82 RowResult::Output(output) => {
83 outputs.push(output);
84 }
85 RowResult::WithTrace {
86 output,
87 reference_map,
88 rule,
89 } => {
90 outputs.push(output);
91 traces.push(DecisionTableRowTrace {
92 index,
93 rule,
94 reference_map,
95 });
96 }
97 }
98 }
99 }
100
101 ctx.trace(|t| {
102 *t = DecisionTableNodeTrace::Collect(traces);
103 });
104
105 ctx.success(Variable::from_array(outputs))
106 }
107
108 fn evaluate_row<'a>(
109 &self,
110 ctx: &'a DecisionTableContext,
111 rule: &'a HashMap<Arc<str>, Arc<str>>,
112 isolate: &mut Isolate<'a>,
113 ) -> Option<RowResult> {
114 let content = &ctx.node;
115 for input in content.inputs.iter() {
116 let rule_value = rule.get(&input.id)?;
117 if rule_value.is_empty() {
118 continue;
119 }
120
121 match &input.field {
122 None => {
123 let result = isolate.run_standard(rule_value).ok()?;
124 if !result.as_bool().unwrap_or(false) {
125 return None;
126 }
127 }
128 Some(field) => {
129 isolate.set_reference(&field).ok()?;
130 if !isolate.run_unary(&rule_value).ok()? {
131 return None;
132 }
133 }
134 }
135 }
136
137 let outputs = Variable::empty_object();
138 for output in content.outputs.iter() {
139 let rule_value = rule.get(&output.id)?;
140 if rule_value.is_empty() {
141 continue;
142 }
143
144 let res = isolate.run_standard(rule_value).ok()?;
145 outputs.dot_insert(output.field.deref(), res);
146 }
147
148 if !ctx.config.trace {
149 return Some(RowResult::Output(outputs));
150 }
151
152 let id_str = Rc::<str>::from("_id");
153 let description_str = Rc::<str>::from("_description");
154
155 let rule_id = match rule.get(id_str.as_ref()) {
156 Some(rid) => Rc::<str>::from(rid.deref()),
157 None => Rc::from(""),
158 };
159
160 let mut expressions: HashMap<Rc<str>, Rc<str>> = Default::default();
161 let mut reference_map: HashMap<Rc<str>, Variable> = Default::default();
162
163 expressions.insert(id_str.clone(), rule_id.clone());
164 if let Some(description) = rule.get(description_str.as_ref()) {
165 expressions.insert(description_str.clone(), Rc::from(description.deref()));
166 }
167
168 for input in content.inputs.iter() {
169 let rule_value = rule.get(input.id.deref())?;
170 let Some(input_field) = &input.field else {
171 continue;
172 };
173
174 if let Some(reference) = isolate.get_reference(input_field.deref()) {
175 reference_map.insert(Rc::from(input_field.deref()), reference);
176 } else if let Some(reference) = isolate.run_standard(input_field.deref()).ok() {
177 reference_map.insert(Rc::from(input_field.deref()), reference);
178 }
179
180 let input_identifier = format!("{input_field}[{}]", &input.id);
181 expressions.insert(
182 Rc::from(input_identifier.as_str()),
183 Rc::from(rule_value.deref()),
184 );
185 }
186
187 Some(RowResult::WithTrace {
188 output: outputs.to_variable(),
189 reference_map,
190 rule: expressions,
191 })
192 }
193}
194
195enum RowResult {
196 Output(Variable),
197 WithTrace {
198 output: Variable,
199 reference_map: HashMap<Rc<str>, Variable>,
200 rule: HashMap<Rc<str>, Rc<str>>,
201 },
202}
203
204#[derive(Debug, Clone, Serialize, ToVariable)]
205pub struct DecisionTableRowTrace {
206 index: usize,
207 reference_map: HashMap<Rc<str>, Variable>,
208 rule: HashMap<Rc<str>, Rc<str>>,
209}
210
211#[derive(Debug, Clone, Serialize, ToVariable)]
212#[serde(untagged)]
213pub enum DecisionTableNodeTrace {
214 FirstHit(DecisionTableRowTrace),
215 Collect(Vec<DecisionTableRowTrace>),
216}
217
218impl Default for DecisionTableNodeTrace {
219 fn default() -> Self {
220 DecisionTableNodeTrace::Collect(Default::default())
221 }
222}