moduforge_rules_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 moduforge_rules_expression::variable::Variable;
11use moduforge_rules_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 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 })
82 .await
83 }
84
85 async fn handle_first_hit(
86 &mut self,
87 content: &'a DecisionTableContent,
88 ) -> NodeResult {
89 for i in 0..content.rules.len() {
90 if let Some(result) = self.evaluate_row(&content, i) {
91 return Ok(NodeResponse {
92 output: result.output.to_json().await,
93 trace_data: self
94 .trace
95 .then(|| serde_json::to_value(&result).ok())
96 .flatten(),
97 });
98 }
99 }
100
101 Ok(NodeResponse { output: Variable::Null, trace_data: None })
102 }
103
104 async fn handle_collect(
105 &mut self,
106 content: &'a DecisionTableContent,
107 ) -> NodeResult {
108 let mut results = Vec::new();
109 for i in 0..content.rules.len() {
110 if let Some(result) = self.evaluate_row(&content, i) {
111 results.push(result);
112 }
113 }
114
115 let mut outputs = Vec::with_capacity(results.len());
116 for res in &results {
117 outputs.push(res.output.to_json().await);
118 }
119
120 Ok(NodeResponse {
121 output: Variable::from_array(outputs),
122 trace_data: self
123 .trace
124 .then(|| serde_json::to_value(&results).ok())
125 .flatten(),
126 })
127 }
128
129 fn evaluate_row(
130 &mut self,
131 content: &'a DecisionTableContent,
132 index: usize,
133 ) -> Option<RowResult> {
134 let rule = content.rules.get(index)?;
135 for input in &content.inputs {
136 let rule_value = rule.get(input.id.as_str())?;
137 if rule_value.trim().is_empty() {
138 continue;
139 }
140
141 match &input.field {
142 None => {
143 let result =
144 self.isolate.run_standard(rule_value.as_str()).ok()?;
145 if !result.as_bool().unwrap_or(false) {
146 return None;
147 }
148 },
149 Some(field) => {
150 self.isolate.set_reference(field.as_str()).ok()?;
151 if !self.isolate.run_unary(rule_value.as_str()).ok()? {
152 return None;
153 }
154 },
155 }
156 }
157
158 let mut outputs: RowOutput = Default::default();
159 for output in &content.outputs {
160 let rule_value = rule.get(output.id.as_str())?;
161 if rule_value.trim().is_empty() {
162 continue;
163 }
164
165 let res = self.isolate.run_standard(rule_value).ok()?;
166 outputs.push(&output.field, RowOutputKind::Variable(res));
167 }
168
169 if !self.trace {
170 return Some(RowResult {
171 output: outputs,
172 rule: None,
173 reference_map: None,
174 index,
175 });
176 }
177
178 let rule_id = match rule.get("_id") {
179 Some(rid) => rid.clone(),
180 None => "".to_string(),
181 };
182
183 let mut expressions: HashMap<String, String> = Default::default();
184 let mut reference_map: HashMap<String, Variable> = Default::default();
185
186 expressions.insert("_id".to_string(), rule_id.clone());
187 if let Some(description) = rule.get("_description") {
188 expressions.insert("_description".to_string(), description.clone());
189 }
190
191 for input in &content.inputs {
192 let rule_value = rule.get(input.id.as_str())?;
193 let Some(input_field) = &input.field else {
194 continue;
195 };
196
197 if let Some(reference) =
198 self.isolate.get_reference(input_field.as_str())
199 {
200 reference_map.insert(input_field.clone(), reference);
201 } else if let Some(reference) =
202 self.isolate.run_standard(input_field.as_str()).ok()
203 {
204 reference_map.insert(input_field.clone(), reference);
205 }
206
207 let input_identifier = format!("{input_field}[{}]", &input.id);
208 expressions.insert(input_identifier, rule_value.clone());
209 }
210
211 Some(RowResult {
212 output: outputs,
213 rule: Some(expressions),
214 reference_map: Some(reference_map),
215 index,
216 })
217 }
218}