1use crate::engine::error::{DataflowError, Result};
10use crate::engine::functions::config::can_dispatch_in;
11use crate::engine::functions::{BoxedFunctionHandler, FunctionConfig};
12use crate::engine::message::{Change, Message};
13use crate::engine::task::Task;
14use crate::engine::task_context::{TaskContext, TaskIdentity};
15use crate::engine::task_outcome::TaskOutcome;
16use datalogic_rs::Engine;
17use log::{debug, error};
18use std::any::Any;
19use std::collections::HashMap;
20use std::sync::Arc;
21
22pub struct TaskExecutor {
33 task_functions: Arc<HashMap<String, BoxedFunctionHandler>>,
35 engine: Arc<Engine>,
37}
38
39impl TaskExecutor {
40 pub fn new(
42 task_functions: Arc<HashMap<String, BoxedFunctionHandler>>,
43 engine: Arc<Engine>,
44 ) -> Self {
45 Self {
46 task_functions,
47 engine,
48 }
49 }
50
51 pub async fn execute(
55 &self,
56 task: &Task,
57 message: &mut Message,
58 ) -> Result<(TaskOutcome, Vec<Change>)> {
59 self.execute_in_workflow(task, message, None, None).await
60 }
61
62 pub(crate) async fn execute_in_workflow(
70 &self,
71 task: &Task,
72 message: &mut Message,
73 identity: Option<TaskIdentity<'_>>,
74 loop_counter: Option<i64>,
75 ) -> Result<(TaskOutcome, Vec<Change>)> {
76 debug!(
77 "Executing task: {} with function: {:?}",
78 task.id,
79 task.function.function_name()
80 );
81
82 match &task.function {
83 FunctionConfig::Map { input, .. } => input.execute(message, &self.engine),
86 FunctionConfig::Validation { input, .. } => input.execute(message, &self.engine),
87 FunctionConfig::ParseJson { input, .. } => {
88 crate::engine::functions::parse::execute_parse_json(message, input)
89 }
90 FunctionConfig::ParseXml { input, .. } => {
91 crate::engine::functions::parse::execute_parse_xml(message, input)
92 }
93 FunctionConfig::PublishJson { input, .. } => {
94 crate::engine::functions::publish::execute_publish_json(message, input)
95 }
96 FunctionConfig::PublishXml { input, .. } => {
97 crate::engine::functions::publish::execute_publish_xml(message, input)
98 }
99 FunctionConfig::Filter { input, .. } => input.execute(message, &self.engine),
100 FunctionConfig::Log { input, .. } => input.execute(message, &self.engine),
101 FunctionConfig::HttpCall { input, .. } => {
106 self.dispatch_handler(
107 task.function.function_name(),
108 message,
109 input,
110 identity,
111 loop_counter,
112 )
113 .await
114 }
115 FunctionConfig::Enrich { input, .. } => {
116 self.dispatch_handler(
117 task.function.function_name(),
118 message,
119 input,
120 identity,
121 loop_counter,
122 )
123 .await
124 }
125 FunctionConfig::PublishKafka { input, .. } => {
126 self.dispatch_handler(
127 task.function.function_name(),
128 message,
129 input,
130 identity,
131 loop_counter,
132 )
133 .await
134 }
135 FunctionConfig::Custom {
136 name,
137 compiled_input,
138 ..
139 } => {
140 let any_input = compiled_input.as_ref().ok_or_else(|| {
141 DataflowError::Validation(format!(
142 "Custom function '{}' has no precompiled input — \
143 was the workflow built outside Engine::new?",
144 name
145 ))
146 })?;
147 self.dispatch_handler_any(name, message, any_input.as_any(), identity, loop_counter)
148 .await
149 }
150 }
151 }
152
153 async fn dispatch_handler<T>(
158 &self,
159 name: &str,
160 message: &mut Message,
161 input: &T,
162 identity: Option<TaskIdentity<'_>>,
163 loop_counter: Option<i64>,
164 ) -> Result<(TaskOutcome, Vec<Change>)>
165 where
166 T: Any + Send + Sync,
167 {
168 let any_input: &(dyn Any + Send + Sync) = input;
169 self.dispatch_handler_any(name, message, any_input, identity, loop_counter)
170 .await
171 }
172
173 async fn dispatch_handler_any(
176 &self,
177 name: &str,
178 message: &mut Message,
179 any_input: &(dyn Any + Send + Sync),
180 identity: Option<TaskIdentity<'_>>,
181 loop_counter: Option<i64>,
182 ) -> Result<(TaskOutcome, Vec<Change>)> {
183 let handler = self.task_functions.get(name).ok_or_else(|| {
184 error!("Function handler not found: {}", name);
185 DataflowError::FunctionNotFound(name.to_string())
186 })?;
187 let mut ctx = TaskContext::with_identity(message, &self.engine, identity, loop_counter);
188 let outcome = handler.dyn_execute(&mut ctx, any_input).await?;
189 let changes = ctx.into_changes();
190 Ok((outcome, changes))
191 }
192
193 pub fn has_function(&self, name: &str) -> bool {
205 can_dispatch_in(&self.task_functions, name)
206 }
207
208 pub fn registry(&self) -> &HashMap<String, BoxedFunctionHandler> {
216 &self.task_functions
217 }
218
219 pub fn task_functions(&self) -> Arc<HashMap<String, BoxedFunctionHandler>> {
221 Arc::clone(&self.task_functions)
222 }
223
224 pub fn custom_function_count(&self) -> usize {
226 self.task_functions.len()
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233 use crate::engine::AsyncFunctionHandler;
234 use crate::engine::compiler::LogicCompiler;
235 use crate::engine::functions::config::is_builtin_function;
236
237 fn executor_with_no_handlers() -> TaskExecutor {
239 TaskExecutor::new(Arc::new(HashMap::new()), LogicCompiler::new().into_engine())
240 }
241
242 fn executor_with_handler(name: &str) -> TaskExecutor {
244 let mut handlers: HashMap<String, BoxedFunctionHandler> = HashMap::new();
245 handlers.insert(name.to_string(), Box::new(MockAsyncFunction));
246 TaskExecutor::new(Arc::new(handlers), LogicCompiler::new().into_engine())
247 }
248
249 #[test]
250 fn test_has_function() {
251 let task_executor = executor_with_no_handlers();
252
253 assert!(task_executor.has_function("map"));
255 assert!(task_executor.has_function("validation"));
256 assert!(task_executor.has_function("validate"));
257
258 assert!(!task_executor.has_function("nonexistent"));
260 assert!(!task_executor.has_function(""));
261 }
262
263 #[test]
264 fn has_function_is_false_for_config_only_integrations_without_a_handler() {
265 let task_executor = executor_with_no_handlers();
270
271 for name in ["http_call", "enrich", "publish_kafka"] {
272 assert!(
273 !task_executor.has_function(name),
274 "'{name}' has no registered handler, so it cannot be run"
275 );
276 assert!(is_builtin_function(name));
278 }
279 }
280
281 #[test]
282 fn has_function_is_true_for_config_only_integrations_once_registered() {
283 for name in ["http_call", "enrich", "publish_kafka"] {
284 assert!(
285 executor_with_handler(name).has_function(name),
286 "'{name}' has a registered handler, so it can be run"
287 );
288 }
289 }
290
291 #[test]
292 fn has_function_is_true_for_every_self_contained_builtin_with_an_empty_registry() {
293 let task_executor = executor_with_no_handlers();
294
295 for name in [
298 "map",
299 "validation",
300 "validate",
301 "parse_json",
302 "parse_xml",
303 "publish_json",
304 "publish_xml",
305 "filter",
306 "log",
307 ] {
308 assert!(
309 task_executor.has_function(name),
310 "'{name}' is executed by the crate and needs no registration"
311 );
312 }
313 }
314
315 #[test]
316 fn registering_a_handler_under_a_self_contained_name_does_not_change_the_answer() {
317 assert!(executor_with_handler("map").has_function("map"));
322 assert!(executor_with_no_handlers().has_function("map"));
323 }
324
325 #[test]
326 fn has_function_is_unchanged_for_custom_names() {
327 assert!(executor_with_handler("custom_test").has_function("custom_test"));
328 assert!(!executor_with_handler("custom_test").has_function("other_custom"));
329 }
330
331 #[test]
332 fn test_custom_function_count() {
333 let mut custom_functions: HashMap<String, BoxedFunctionHandler> = HashMap::new();
334 custom_functions.insert("custom_test".to_string(), Box::new(MockAsyncFunction));
335
336 let engine = LogicCompiler::new().into_engine();
337 let task_executor = TaskExecutor::new(Arc::new(custom_functions), engine);
338
339 assert_eq!(task_executor.custom_function_count(), 1);
340 }
341
342 struct MockAsyncFunction;
344
345 #[async_trait::async_trait]
346 impl AsyncFunctionHandler for MockAsyncFunction {
347 type Input = serde_json::Value;
348
349 async fn execute(
350 &self,
351 _ctx: &mut TaskContext<'_>,
352 _input: &serde_json::Value,
353 ) -> Result<TaskOutcome> {
354 Ok(TaskOutcome::Success)
355 }
356 }
357}