1use crate::engine::error::{DataflowError, Result};
10use crate::engine::functions::config::{BuiltinKind, builtin_function_kind};
11use crate::engine::functions::{BoxedFunctionHandler, FunctionConfig};
12use crate::engine::message::{Change, Message};
13use crate::engine::task::Task;
14use crate::engine::task_context::TaskContext;
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 debug!(
60 "Executing task: {} with function: {:?}",
61 task.id,
62 task.function.function_name()
63 );
64
65 match &task.function {
66 FunctionConfig::Map { input, .. } => input.execute(message, &self.engine),
69 FunctionConfig::Validation { input, .. } => input.execute(message, &self.engine),
70 FunctionConfig::ParseJson { input, .. } => {
71 crate::engine::functions::parse::execute_parse_json(message, input)
72 }
73 FunctionConfig::ParseXml { input, .. } => {
74 crate::engine::functions::parse::execute_parse_xml(message, input)
75 }
76 FunctionConfig::PublishJson { input, .. } => {
77 crate::engine::functions::publish::execute_publish_json(message, input)
78 }
79 FunctionConfig::PublishXml { input, .. } => {
80 crate::engine::functions::publish::execute_publish_xml(message, input)
81 }
82 FunctionConfig::Filter { input, .. } => input.execute(message, &self.engine),
83 FunctionConfig::Log { input, .. } => input.execute(message, &self.engine),
84 FunctionConfig::HttpCall { input, .. } => {
89 self.dispatch_handler(task.function.function_name(), message, input)
90 .await
91 }
92 FunctionConfig::Enrich { input, .. } => {
93 self.dispatch_handler(task.function.function_name(), message, input)
94 .await
95 }
96 FunctionConfig::PublishKafka { input, .. } => {
97 self.dispatch_handler(task.function.function_name(), message, input)
98 .await
99 }
100 FunctionConfig::Custom {
101 name,
102 compiled_input,
103 ..
104 } => {
105 let any_input = compiled_input.as_ref().ok_or_else(|| {
106 DataflowError::Validation(format!(
107 "Custom function '{}' has no precompiled input — \
108 was the workflow built outside Engine::new?",
109 name
110 ))
111 })?;
112 self.dispatch_handler_any(name, message, any_input.as_any())
113 .await
114 }
115 }
116 }
117
118 async fn dispatch_handler<T>(
123 &self,
124 name: &str,
125 message: &mut Message,
126 input: &T,
127 ) -> Result<(TaskOutcome, Vec<Change>)>
128 where
129 T: Any + Send + Sync,
130 {
131 let any_input: &(dyn Any + Send + Sync) = input;
132 self.dispatch_handler_any(name, message, any_input).await
133 }
134
135 async fn dispatch_handler_any(
138 &self,
139 name: &str,
140 message: &mut Message,
141 any_input: &(dyn Any + Send + Sync),
142 ) -> Result<(TaskOutcome, Vec<Change>)> {
143 let handler = self.task_functions.get(name).ok_or_else(|| {
144 error!("Function handler not found: {}", name);
145 DataflowError::FunctionNotFound(name.to_string())
146 })?;
147 let mut ctx = TaskContext::new(message, &self.engine);
148 let outcome = handler.dyn_execute(&mut ctx, any_input).await?;
149 let changes = ctx.into_changes();
150 Ok((outcome, changes))
151 }
152
153 pub fn has_function(&self, name: &str) -> bool {
165 match builtin_function_kind(name) {
166 Some(BuiltinKind::SelfContained) => true,
167 _ => self.task_functions.contains_key(name),
169 }
170 }
171
172 pub fn task_functions(&self) -> Arc<HashMap<String, BoxedFunctionHandler>> {
174 Arc::clone(&self.task_functions)
175 }
176
177 pub fn custom_function_count(&self) -> usize {
179 self.task_functions.len()
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use crate::engine::AsyncFunctionHandler;
187 use crate::engine::compiler::LogicCompiler;
188 use crate::engine::functions::config::is_builtin_function;
189
190 fn executor_with_no_handlers() -> TaskExecutor {
192 TaskExecutor::new(Arc::new(HashMap::new()), LogicCompiler::new().into_engine())
193 }
194
195 fn executor_with_handler(name: &str) -> TaskExecutor {
197 let mut handlers: HashMap<String, BoxedFunctionHandler> = HashMap::new();
198 handlers.insert(name.to_string(), Box::new(MockAsyncFunction));
199 TaskExecutor::new(Arc::new(handlers), LogicCompiler::new().into_engine())
200 }
201
202 #[test]
203 fn test_has_function() {
204 let task_executor = executor_with_no_handlers();
205
206 assert!(task_executor.has_function("map"));
208 assert!(task_executor.has_function("validation"));
209 assert!(task_executor.has_function("validate"));
210
211 assert!(!task_executor.has_function("nonexistent"));
213 assert!(!task_executor.has_function(""));
214 }
215
216 #[test]
217 fn has_function_is_false_for_config_only_integrations_without_a_handler() {
218 let task_executor = executor_with_no_handlers();
223
224 for name in ["http_call", "enrich", "publish_kafka"] {
225 assert!(
226 !task_executor.has_function(name),
227 "'{name}' has no registered handler, so it cannot be run"
228 );
229 assert!(is_builtin_function(name));
231 }
232 }
233
234 #[test]
235 fn has_function_is_true_for_config_only_integrations_once_registered() {
236 for name in ["http_call", "enrich", "publish_kafka"] {
237 assert!(
238 executor_with_handler(name).has_function(name),
239 "'{name}' has a registered handler, so it can be run"
240 );
241 }
242 }
243
244 #[test]
245 fn has_function_is_true_for_every_self_contained_builtin_with_an_empty_registry() {
246 let task_executor = executor_with_no_handlers();
247
248 for name in [
251 "map",
252 "validation",
253 "validate",
254 "parse_json",
255 "parse_xml",
256 "publish_json",
257 "publish_xml",
258 "filter",
259 "log",
260 ] {
261 assert!(
262 task_executor.has_function(name),
263 "'{name}' is executed by the crate and needs no registration"
264 );
265 }
266 }
267
268 #[test]
269 fn registering_a_handler_under_a_self_contained_name_does_not_change_the_answer() {
270 assert!(executor_with_handler("map").has_function("map"));
275 assert!(executor_with_no_handlers().has_function("map"));
276 }
277
278 #[test]
279 fn has_function_is_unchanged_for_custom_names() {
280 assert!(executor_with_handler("custom_test").has_function("custom_test"));
281 assert!(!executor_with_handler("custom_test").has_function("other_custom"));
282 }
283
284 #[test]
285 fn test_custom_function_count() {
286 let mut custom_functions: HashMap<String, BoxedFunctionHandler> = HashMap::new();
287 custom_functions.insert("custom_test".to_string(), Box::new(MockAsyncFunction));
288
289 let engine = LogicCompiler::new().into_engine();
290 let task_executor = TaskExecutor::new(Arc::new(custom_functions), engine);
291
292 assert_eq!(task_executor.custom_function_count(), 1);
293 }
294
295 struct MockAsyncFunction;
297
298 #[async_trait::async_trait]
299 impl AsyncFunctionHandler for MockAsyncFunction {
300 type Input = serde_json::Value;
301
302 async fn execute(
303 &self,
304 _ctx: &mut TaskContext<'_>,
305 _input: &serde_json::Value,
306 ) -> Result<TaskOutcome> {
307 Ok(TaskOutcome::Success)
308 }
309 }
310}