1use crate::engine::error::{DataflowError, Result};
11use crate::engine::functions::integration::{EnrichConfig, HttpCallConfig, PublishKafkaConfig};
12use crate::engine::functions::{FilterConfig, LogConfig, MapConfig, ValidationConfig};
13use crate::engine::{FunctionConfig, Workflow};
14use datalogic_rs::{Engine, Logic};
15use log::debug;
16use serde_json::Value;
17use std::sync::Arc;
18
19pub struct LogicCompiler {
22 engine: Arc<Engine>,
24}
25
26impl Default for LogicCompiler {
27 fn default() -> Self {
28 Self::new()
29 }
30}
31
32impl LogicCompiler {
33 pub fn new() -> Self {
36 Self {
37 engine: Arc::new(Engine::builder().with_templating(true).build()),
38 }
39 }
40
41 pub fn engine(&self) -> Arc<Engine> {
43 Arc::clone(&self.engine)
44 }
45
46 pub fn into_engine(self) -> Arc<Engine> {
48 self.engine
49 }
50
51 pub fn compile_workflows(&self, workflows: Vec<Workflow>) -> Result<Vec<Workflow>> {
56 let mut compiled_workflows = Vec::with_capacity(workflows.len());
57
58 for mut workflow in workflows {
59 workflow.validate()?;
60
61 workflow.id_arc = Arc::from(workflow.id.as_str());
64 for task in &mut workflow.tasks {
65 task.id_arc = Arc::from(task.id.as_str());
66 }
67
68 let label = format!("workflow {} condition", workflow.id);
71 workflow.compiled_condition = self.compile_condition(&workflow.condition, &label)?;
72 debug!("Workflow {} condition compiled", workflow.id);
73
74 self.compile_workflow_tasks(&mut workflow)?;
76
77 workflow.fully_sync = workflow.tasks.iter().all(|t| t.function.is_sync_builtin());
84
85 compiled_workflows.push(workflow);
86 }
87
88 compiled_workflows.sort_by_key(|w| w.priority);
90 Ok(compiled_workflows)
91 }
92
93 fn compile_workflow_tasks(&self, workflow: &mut Workflow) -> Result<()> {
95 for task in &mut workflow.tasks {
96 let label = format!("task {} condition (workflow {})", task.id, workflow.id);
97 task.compiled_condition = self.compile_condition(&task.condition, &label)?;
98
99 self.compile_function_logic(&mut task.function, &task.id, &workflow.id)?;
101 }
102 Ok(())
103 }
104
105 fn compile_function_logic(
107 &self,
108 function: &mut FunctionConfig,
109 task_id: &str,
110 workflow_id: &str,
111 ) -> Result<()> {
112 match function {
113 FunctionConfig::Map { input, .. } => {
114 self.compile_map_logic(input, task_id, workflow_id)
115 }
116 FunctionConfig::Validation { input, .. } => {
117 self.compile_validation_logic(input, task_id, workflow_id)
118 }
119 FunctionConfig::Filter { input, .. } => {
120 self.compile_filter_logic(input, task_id, workflow_id)
121 }
122 FunctionConfig::Log { input, .. } => {
123 self.compile_log_logic(input, task_id, workflow_id)
124 }
125 FunctionConfig::HttpCall { input, .. } => {
126 self.compile_http_call_logic(input, task_id, workflow_id)
127 }
128 FunctionConfig::Enrich { input, .. } => {
129 self.compile_enrich_logic(input, task_id, workflow_id)
130 }
131 FunctionConfig::PublishKafka { input, .. } => {
132 self.compile_publish_kafka_logic(input, task_id, workflow_id)
133 }
134 FunctionConfig::ParseJson { input, .. } | FunctionConfig::ParseXml { input, .. } => {
138 input.precompute_target_path();
139 Ok(())
140 }
141 FunctionConfig::PublishJson { input, .. }
142 | FunctionConfig::PublishXml { input, .. } => {
143 input.precompute_target_path();
144 Ok(())
145 }
146 _ => Ok(()),
148 }
149 }
150
151 fn compile(&self, logic: &Value, ctx_label: &str) -> Result<Arc<Logic>> {
155 self.engine
156 .compile_arc(logic)
157 .map_err(|e| DataflowError::LogicEvaluation(format!("{}: {}", ctx_label, e)))
158 }
159
160 fn compile_condition(&self, condition: &Value, ctx_label: &str) -> Result<Option<Arc<Logic>>> {
170 if matches!(condition, Value::Bool(true)) {
171 return Ok(None);
172 }
173 Ok(Some(self.compile(condition, ctx_label)?))
174 }
175
176 fn compile_map_logic(
178 &self,
179 config: &mut MapConfig,
180 task_id: &str,
181 workflow_id: &str,
182 ) -> Result<()> {
183 for mapping in &mut config.mappings {
184 let parts: Vec<Arc<str>> = mapping.path.split('.').map(Arc::from).collect();
190 mapping.path_parts = Arc::from(parts.into_boxed_slice());
191 mapping.path_arc = Arc::from(mapping.path.as_str());
192
193 let label = format!(
194 "map logic for task {} in workflow {} (path {})",
195 task_id, workflow_id, mapping.path
196 );
197 mapping.compiled_logic = Some(self.compile(&mapping.logic, &label)?);
198 }
199 Ok(())
200 }
201
202 fn compile_validation_logic(
204 &self,
205 config: &mut ValidationConfig,
206 task_id: &str,
207 workflow_id: &str,
208 ) -> Result<()> {
209 for (idx, rule) in config.rules.iter_mut().enumerate() {
210 let label = format!(
211 "validation rule {} for task {} in workflow {}",
212 idx, task_id, workflow_id
213 );
214 rule.compiled_logic = Some(self.compile(&rule.logic, &label)?);
215 }
216 Ok(())
217 }
218
219 fn compile_log_logic(
221 &self,
222 config: &mut LogConfig,
223 task_id: &str,
224 workflow_id: &str,
225 ) -> Result<()> {
226 let msg_label = format!(
227 "log message for task {} in workflow {}",
228 task_id, workflow_id
229 );
230 config.compiled_message = Some(self.compile(&config.message, &msg_label)?);
231
232 let mut compiled_fields = Vec::with_capacity(config.fields.len());
236 for (key, logic) in &config.fields {
237 let label = format!(
238 "log field '{}' for task {} in workflow {}",
239 key, task_id, workflow_id
240 );
241 compiled_fields.push((key.clone(), Some(self.compile(logic, &label)?)));
242 }
243 config.compiled_fields = compiled_fields;
244 Ok(())
245 }
246
247 fn compile_filter_logic(
249 &self,
250 config: &mut FilterConfig,
251 task_id: &str,
252 workflow_id: &str,
253 ) -> Result<()> {
254 let label = format!(
255 "filter condition for task {} in workflow {}",
256 task_id, workflow_id
257 );
258 config.compiled_condition = Some(self.compile(&config.condition, &label)?);
259 Ok(())
260 }
261
262 fn compile_http_call_logic(
264 &self,
265 config: &mut HttpCallConfig,
266 task_id: &str,
267 workflow_id: &str,
268 ) -> Result<()> {
269 if let Some(logic) = &config.path_logic {
270 let label = format!(
271 "http_call path_logic for task {} in workflow {}",
272 task_id, workflow_id
273 );
274 config.compiled_path_logic = Some(self.compile(logic, &label)?);
275 }
276 if let Some(logic) = &config.body_logic {
277 let label = format!(
278 "http_call body_logic for task {} in workflow {}",
279 task_id, workflow_id
280 );
281 config.compiled_body_logic = Some(self.compile(logic, &label)?);
282 }
283 Ok(())
284 }
285
286 fn compile_enrich_logic(
288 &self,
289 config: &mut EnrichConfig,
290 task_id: &str,
291 workflow_id: &str,
292 ) -> Result<()> {
293 if let Some(logic) = &config.path_logic {
294 let label = format!(
295 "enrich path_logic for task {} in workflow {}",
296 task_id, workflow_id
297 );
298 config.compiled_path_logic = Some(self.compile(logic, &label)?);
299 }
300 Ok(())
301 }
302
303 fn compile_publish_kafka_logic(
305 &self,
306 config: &mut PublishKafkaConfig,
307 task_id: &str,
308 workflow_id: &str,
309 ) -> Result<()> {
310 if let Some(logic) = &config.key_logic {
311 let label = format!(
312 "publish_kafka key_logic for task {} in workflow {}",
313 task_id, workflow_id
314 );
315 config.compiled_key_logic = Some(self.compile(logic, &label)?);
316 }
317 if let Some(logic) = &config.value_logic {
318 let label = format!(
319 "publish_kafka value_logic for task {} in workflow {}",
320 task_id, workflow_id
321 );
322 config.compiled_value_logic = Some(self.compile(logic, &label)?);
323 }
324 Ok(())
325 }
326}