mf_engine/handler/table/
zen.rs1use ahash::HashMap;
2use anyhow::anyhow;
3use std::sync::Arc;
4
5use crate::handler::node::{NodeRequest, NodeResponse, NodeResult};
6use crate::handler::table::{RowOutput, RowOutputKind};
7use crate::model::{DecisionNodeKind, DecisionTableContent, DecisionTableHitPolicy};
8use serde::Serialize;
9use tokio::sync::Mutex;
10use mf_expression::variable::Variable;
11use mf_expression::Isolate;
12
13#[derive(Debug, Serialize)]
14struct RowResult {
15 rule: Option<HashMap<String, String>>,
16 reference_map: Option<HashMap<String, Variable>>,
17 index: usize,
18 #[serde(skip)]
19 output: RowOutput,
20}
21
22#[derive(Debug)]
23pub struct DecisionTableHandler {
24 trace: bool,
25}
26
27impl DecisionTableHandler {
28 pub fn new(trace: bool) -> Self {
29 Self { trace }
30 }
31
32 pub async fn handle(
33 &mut self,
34 request: NodeRequest,
35 ) -> NodeResult {
36 let content = match &request.node.kind {
37 DecisionNodeKind::DecisionTableNode { content } => Ok(content),
38 _ => Err(anyhow!("Unexpected node type")),
39 }?;
40
41 let inner_handler = DecisionTableHandlerInner::new(self.trace);
42 inner_handler.handle(request.input.depth_clone(1), content).await
43 }
44}
45
46struct DecisionTableHandlerInner<'a> {
47 isolate: Isolate<'a>,
48 trace: bool,
49}
50
51impl<'a> DecisionTableHandlerInner<'a> {
52 pub fn new(trace: bool) -> Self {
53 Self { isolate: Isolate::new(), trace }
54 }
55
56 pub async fn handle(
57 self,
58 input: Variable,
59 content: &'a DecisionTableContent,
60 ) -> NodeResult {
61 let self_mutex = Arc::new(Mutex::new(self));
62
63 content
64 .transform_attributes
65 .run_with(input, |input| {
66 let self_mutex = self_mutex.clone();
67 async move {
68 let mut self_ref = self_mutex.lock().await;
69
70 self_ref.isolate.clear_references();
71 self_ref.isolate.set_environment(input);
72 let result = match &content.hit_policy {
73 DecisionTableHitPolicy::First => {
74 self_ref.handle_first_hit(&content).await
75 },
76 DecisionTableHitPolicy::Collect => {
77 self_ref.handle_collect(&content).await
78 },
79 };
80
81 self_ref.isolate.update_environment(|env| {
82 if let Some(env) = env {
83 env.dot_remove("$");
84 };
85 });
86
87 result
88 }
89 })
90 .await
91 }
92
93 async fn handle_first_hit(
94 &mut self,
95 content: &'a DecisionTableContent,
96 ) -> NodeResult {
97 for i in 0..content.rules.len() {
98 if let Some(result) = self.evaluate_row(&content, i) {
99 return Ok(NodeResponse {
100 output: result.output.to_json().await,
101 trace_data: self
102 .trace
103 .then(|| serde_json::to_value(&result).ok())
104 .flatten(),
105 });
106 }
107 }
108
109 Ok(NodeResponse { output: Variable::Null, trace_data: None })
110 }
111
112 async fn handle_collect(
113 &mut self,
114 content: &'a DecisionTableContent,
115 ) -> NodeResult {
116 let mut results = Vec::new();
117 for i in 0..content.rules.len() {
118 if let Some(result) = self.evaluate_row(&content, i) {
119 results.push(result);
120 }
121 }
122
123 let mut outputs = Vec::with_capacity(results.len());
124 for res in &results {
125 outputs.push(res.output.to_json().await);
126 }
127
128 Ok(NodeResponse {
129 output: Variable::from_array(outputs),
130 trace_data: self
131 .trace
132 .then(|| serde_json::to_value(&results).ok())
133 .flatten(),
134 })
135 }
136
137 fn evaluate_row(
138 &mut self,
139 content: &'a DecisionTableContent,
140 index: usize,
141 ) -> Option<RowResult> {
142 let rule = content.rules.get(index)?;
143 for input in &content.inputs {
144 let rule_value = rule.get(input.id.as_str())?;
145 if rule_value.trim().is_empty() {
146 continue;
147 }
148
149 match &input.field {
150 None => {
151 let result =
152 self.isolate.run_standard(rule_value.as_str()).ok()?;
153 if !result.as_bool().unwrap_or(false) {
154 return None;
155 }
156 },
157 Some(field) => {
158 self.isolate.set_reference(field.as_str()).ok()?;
159 if !self.isolate.run_unary(rule_value.as_str()).ok()? {
160 return None;
161 }
162 },
163 }
164 }
165
166 let mut outputs: RowOutput = Default::default();
167 for output in &content.outputs {
168 let rule_value = rule.get(output.id.as_str())?;
169 if rule_value.trim().is_empty() {
170 continue;
171 }
172
173 let res = self.isolate.run_standard(rule_value).ok()?;
174 outputs.push(&output.field, RowOutputKind::Variable(res));
175 }
176
177 if !self.trace {
178 return Some(RowResult {
179 output: outputs,
180 rule: None,
181 reference_map: None,
182 index,
183 });
184 }
185
186 let rule_id = match rule.get("_id") {
187 Some(rid) => rid.clone(),
188 None => "".to_string(),
189 };
190
191 let mut expressions: HashMap<String, String> = Default::default();
192 let mut reference_map: HashMap<String, Variable> = Default::default();
193
194 expressions.insert("_id".to_string(), rule_id.clone());
195 if let Some(description) = rule.get("_description") {
196 expressions.insert("_description".to_string(), description.clone());
197 }
198
199 for input in &content.inputs {
200 let rule_value = rule.get(input.id.as_str())?;
201 let Some(input_field) = &input.field else {
202 continue;
203 };
204
205 if let Some(reference) =
206 self.isolate.get_reference(input_field.as_str())
207 {
208 reference_map.insert(input_field.clone(), reference);
209 } else if let Some(reference) =
210 self.isolate.run_standard(input_field.as_str()).ok()
211 {
212 reference_map.insert(input_field.clone(), reference);
213 }
214
215 let input_identifier = format!("{input_field}[{}]", &input.id);
216 expressions.insert(input_identifier, rule_value.clone());
217 }
218
219 Some(RowResult {
220 output: outputs,
221 rule: Some(expressions),
222 reference_map: Some(reference_map),
223 index,
224 })
225 }
226}