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::secrets::Secrets;
14use crate::engine::task::Task;
15use crate::engine::task_context::{TaskContext, TaskIdentity};
16use crate::engine::task_outcome::TaskOutcome;
17use datalogic_rs::Engine;
18use log::{debug, error};
19use std::any::Any;
20use std::collections::HashMap;
21use std::sync::Arc;
22
23pub struct TaskExecutor {
34 task_functions: Arc<HashMap<String, BoxedFunctionHandler>>,
36 engine: Arc<Engine>,
38 secrets: Arc<Secrets>,
41}
42
43impl TaskExecutor {
44 pub fn new(
46 task_functions: Arc<HashMap<String, BoxedFunctionHandler>>,
47 engine: Arc<Engine>,
48 ) -> Self {
49 Self::with_secrets(task_functions, engine, Arc::new(Secrets::empty()))
50 }
51
52 pub(crate) fn with_secrets(
57 task_functions: Arc<HashMap<String, BoxedFunctionHandler>>,
58 engine: Arc<Engine>,
59 secrets: Arc<Secrets>,
60 ) -> Self {
61 Self {
62 task_functions,
63 engine,
64 secrets,
65 }
66 }
67
68 pub async fn execute(
72 &self,
73 task: &Task,
74 message: &mut Message,
75 ) -> Result<(TaskOutcome, Vec<Change>)> {
76 self.execute_in_workflow(task, message, None, None).await
77 }
78
79 pub(crate) async fn execute_in_workflow(
87 &self,
88 task: &Task,
89 message: &mut Message,
90 identity: Option<TaskIdentity<'_>>,
91 loop_counter: Option<i64>,
92 ) -> Result<(TaskOutcome, Vec<Change>)> {
93 debug!(
94 "Executing task: {} with function: {:?}",
95 task.id,
96 task.function.function_name()
97 );
98
99 match &task.function {
100 FunctionConfig::Map { input, .. } => input.execute(message, &self.engine),
103 FunctionConfig::Validation { input, .. } => input.execute(message, &self.engine),
104 FunctionConfig::ParseJson { input, .. } => {
105 crate::engine::functions::parse::execute_parse_json(message, input, &self.engine)
106 }
107 FunctionConfig::ParseXml { input, .. } => {
108 crate::engine::functions::parse::execute_parse_xml(message, input, &self.engine)
109 }
110 FunctionConfig::PublishJson { input, .. } => {
111 crate::engine::functions::publish::execute_publish_json(
112 message,
113 input,
114 &self.engine,
115 )
116 }
117 FunctionConfig::PublishXml { input, .. } => {
118 crate::engine::functions::publish::execute_publish_xml(message, input, &self.engine)
119 }
120 FunctionConfig::Filter { input, .. } => input.execute(message, &self.engine),
121 FunctionConfig::Log { input, .. } => input.execute(message, &self.engine),
122 FunctionConfig::HttpCall { input, .. } => {
127 self.dispatch_handler(
128 task.function.function_name(),
129 message,
130 input,
131 identity,
132 loop_counter,
133 )
134 .await
135 }
136 FunctionConfig::Enrich { input, .. } => {
137 self.dispatch_handler(
138 task.function.function_name(),
139 message,
140 input,
141 identity,
142 loop_counter,
143 )
144 .await
145 }
146 FunctionConfig::PublishKafka { input, .. } => {
147 self.dispatch_handler(
148 task.function.function_name(),
149 message,
150 input,
151 identity,
152 loop_counter,
153 )
154 .await
155 }
156 FunctionConfig::Custom {
157 name,
158 compiled_input,
159 ..
160 } => {
161 let any_input = compiled_input.as_ref().ok_or_else(|| {
162 DataflowError::Validation(format!(
163 "Custom function '{}' has no precompiled input — \
164 was the workflow built outside Engine::new?",
165 name
166 ))
167 })?;
168 self.dispatch_handler_any(name, message, any_input.as_any(), identity, loop_counter)
169 .await
170 }
171 }
172 }
173
174 async fn dispatch_handler<T>(
179 &self,
180 name: &str,
181 message: &mut Message,
182 input: &T,
183 identity: Option<TaskIdentity<'_>>,
184 loop_counter: Option<i64>,
185 ) -> Result<(TaskOutcome, Vec<Change>)>
186 where
187 T: Any + Send + Sync,
188 {
189 let any_input: &(dyn Any + Send + Sync) = input;
190 self.dispatch_handler_any(name, message, any_input, identity, loop_counter)
191 .await
192 }
193
194 async fn dispatch_handler_any(
197 &self,
198 name: &str,
199 message: &mut Message,
200 any_input: &(dyn Any + Send + Sync),
201 identity: Option<TaskIdentity<'_>>,
202 loop_counter: Option<i64>,
203 ) -> Result<(TaskOutcome, Vec<Change>)> {
204 let handler = self.task_functions.get(name).ok_or_else(|| {
205 error!("Function handler not found: {}", name);
206 DataflowError::FunctionNotFound(name.to_string())
207 })?;
208 let mut ctx = TaskContext::with_identity(
209 message,
210 &self.engine,
211 identity,
212 loop_counter,
213 &self.secrets,
214 );
215 let outcome = handler.dyn_execute(&mut ctx, any_input).await?;
216 let changes = ctx.into_changes();
217 Ok((outcome, changes))
218 }
219
220 pub fn has_function(&self, name: &str) -> bool {
232 can_dispatch_in(&self.task_functions, name)
233 }
234
235 pub fn registry(&self) -> &HashMap<String, BoxedFunctionHandler> {
243 &self.task_functions
244 }
245
246 pub fn task_functions(&self) -> Arc<HashMap<String, BoxedFunctionHandler>> {
248 Arc::clone(&self.task_functions)
249 }
250
251 pub fn custom_function_count(&self) -> usize {
253 self.task_functions.len()
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260 use crate::engine::AsyncFunctionHandler;
261 use crate::engine::compiler::LogicCompiler;
262 use crate::engine::functions::config::is_builtin_function;
263
264 fn executor_with_no_handlers() -> TaskExecutor {
266 TaskExecutor::new(Arc::new(HashMap::new()), LogicCompiler::new().into_engine())
267 }
268
269 fn executor_with_handler(name: &str) -> TaskExecutor {
271 let mut handlers: HashMap<String, BoxedFunctionHandler> = HashMap::new();
272 handlers.insert(name.to_string(), Box::new(MockAsyncFunction));
273 TaskExecutor::new(Arc::new(handlers), LogicCompiler::new().into_engine())
274 }
275
276 #[test]
277 fn test_has_function() {
278 let task_executor = executor_with_no_handlers();
279
280 assert!(task_executor.has_function("map"));
282 assert!(task_executor.has_function("validation"));
283 assert!(task_executor.has_function("validate"));
284
285 assert!(!task_executor.has_function("nonexistent"));
287 assert!(!task_executor.has_function(""));
288 }
289
290 #[test]
291 fn has_function_is_false_for_config_only_integrations_without_a_handler() {
292 let task_executor = executor_with_no_handlers();
297
298 for name in ["http_call", "enrich", "publish_kafka"] {
299 assert!(
300 !task_executor.has_function(name),
301 "'{name}' has no registered handler, so it cannot be run"
302 );
303 assert!(is_builtin_function(name));
305 }
306 }
307
308 #[test]
309 fn has_function_is_true_for_config_only_integrations_once_registered() {
310 for name in ["http_call", "enrich", "publish_kafka"] {
311 assert!(
312 executor_with_handler(name).has_function(name),
313 "'{name}' has a registered handler, so it can be run"
314 );
315 }
316 }
317
318 #[test]
319 fn has_function_is_true_for_every_self_contained_builtin_with_an_empty_registry() {
320 let task_executor = executor_with_no_handlers();
321
322 for name in [
325 "map",
326 "validation",
327 "validate",
328 "parse_json",
329 "parse_xml",
330 "publish_json",
331 "publish_xml",
332 "filter",
333 "log",
334 ] {
335 assert!(
336 task_executor.has_function(name),
337 "'{name}' is executed by the crate and needs no registration"
338 );
339 }
340 }
341
342 #[test]
343 fn registering_a_handler_under_a_self_contained_name_does_not_change_the_answer() {
344 assert!(executor_with_handler("map").has_function("map"));
349 assert!(executor_with_no_handlers().has_function("map"));
350 }
351
352 #[test]
353 fn has_function_is_unchanged_for_custom_names() {
354 assert!(executor_with_handler("custom_test").has_function("custom_test"));
355 assert!(!executor_with_handler("custom_test").has_function("other_custom"));
356 }
357
358 #[test]
359 fn test_custom_function_count() {
360 let mut custom_functions: HashMap<String, BoxedFunctionHandler> = HashMap::new();
361 custom_functions.insert("custom_test".to_string(), Box::new(MockAsyncFunction));
362
363 let engine = LogicCompiler::new().into_engine();
364 let task_executor = TaskExecutor::new(Arc::new(custom_functions), engine);
365
366 assert_eq!(task_executor.custom_function_count(), 1);
367 }
368
369 struct MockAsyncFunction;
371
372 #[async_trait::async_trait]
373 impl AsyncFunctionHandler for MockAsyncFunction {
374 type Input = serde_json::Value;
375
376 async fn execute(
377 &self,
378 _ctx: &mut TaskContext<'_>,
379 _input: &serde_json::Value,
380 ) -> Result<TaskOutcome> {
381 Ok(TaskOutcome::Success)
382 }
383 }
384}