mf_engine/handler/table/
zen.rs

1use 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}