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::{CallStamp, 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, CallStamp::default())
77 .await
78 }
79
80 pub(crate) async fn execute_in_workflow(
88 &self,
89 task: &Task,
90 message: &mut Message,
91 identity: Option<TaskIdentity<'_>>,
92 stamp: CallStamp,
93 ) -> Result<(TaskOutcome, Vec<Change>)> {
94 debug!(
95 "Executing task: {} with function: {:?}",
96 task.id,
97 task.function.function_name()
98 );
99
100 match &task.function {
101 FunctionConfig::Map { input, .. } => input.execute(message, &self.engine),
104 FunctionConfig::Validation { input, .. } => input.execute(message, &self.engine),
105 FunctionConfig::ParseJson { input, .. } => {
106 crate::engine::functions::parse::execute_parse_json(message, input, &self.engine)
107 }
108 FunctionConfig::ParseXml { input, .. } => {
109 crate::engine::functions::parse::execute_parse_xml(message, input, &self.engine)
110 }
111 FunctionConfig::PublishJson { input, .. } => {
112 crate::engine::functions::publish::execute_publish_json(
113 message,
114 input,
115 &self.engine,
116 )
117 }
118 FunctionConfig::PublishXml { input, .. } => {
119 crate::engine::functions::publish::execute_publish_xml(message, input, &self.engine)
120 }
121 FunctionConfig::Filter { input, .. } => input.execute(message, &self.engine),
122 FunctionConfig::Log { input, .. } => input.execute(message, &self.engine),
123 FunctionConfig::HttpCall { input, .. } => {
128 self.dispatch_handler(
129 task.function.function_name(),
130 message,
131 input,
132 identity,
133 stamp,
134 )
135 .await
136 }
137 FunctionConfig::Enrich { input, .. } => {
138 self.dispatch_handler(
139 task.function.function_name(),
140 message,
141 input,
142 identity,
143 stamp,
144 )
145 .await
146 }
147 FunctionConfig::PublishKafka { input, .. } => {
148 self.dispatch_handler(
149 task.function.function_name(),
150 message,
151 input,
152 identity,
153 stamp,
154 )
155 .await
156 }
157 FunctionConfig::Custom {
158 name,
159 compiled_input,
160 ..
161 } => {
162 let any_input = compiled_input.as_ref().ok_or_else(|| {
163 DataflowError::Validation(format!(
164 "Custom function '{}' has no precompiled input — \
165 was the workflow built outside Engine::new?",
166 name
167 ))
168 })?;
169 self.dispatch_handler_any(name, message, any_input.as_any(), identity, stamp)
170 .await
171 }
172 }
173 }
174
175 async fn dispatch_handler<T>(
180 &self,
181 name: &str,
182 message: &mut Message,
183 input: &T,
184 identity: Option<TaskIdentity<'_>>,
185 stamp: CallStamp,
186 ) -> Result<(TaskOutcome, Vec<Change>)>
187 where
188 T: Any + Send + Sync,
189 {
190 let any_input: &(dyn Any + Send + Sync) = input;
191 self.dispatch_handler_any(name, message, any_input, identity, stamp)
192 .await
193 }
194
195 async fn dispatch_handler_any(
198 &self,
199 name: &str,
200 message: &mut Message,
201 any_input: &(dyn Any + Send + Sync),
202 identity: Option<TaskIdentity<'_>>,
203 stamp: CallStamp,
204 ) -> Result<(TaskOutcome, Vec<Change>)> {
205 let handler = self.task_functions.get(name).ok_or_else(|| {
206 error!("Function handler not found: {}", name);
207 DataflowError::FunctionNotFound(name.to_string())
208 })?;
209 let mut ctx =
210 TaskContext::with_identity(message, &self.engine, identity, stamp, &self.secrets);
211 let outcome = handler.dyn_execute(&mut ctx, any_input).await?;
212 let changes = ctx.into_changes();
213 Ok((outcome, changes))
214 }
215
216 pub fn has_function(&self, name: &str) -> bool {
228 can_dispatch_in(&self.task_functions, name)
229 }
230
231 pub fn registry(&self) -> &HashMap<String, BoxedFunctionHandler> {
239 &self.task_functions
240 }
241
242 pub fn task_functions(&self) -> Arc<HashMap<String, BoxedFunctionHandler>> {
244 Arc::clone(&self.task_functions)
245 }
246
247 pub fn custom_function_count(&self) -> usize {
249 self.task_functions.len()
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256 use crate::engine::AsyncFunctionHandler;
257 use crate::engine::compiler::LogicCompiler;
258 use crate::engine::functions::config::is_builtin_function;
259
260 fn executor_with_no_handlers() -> TaskExecutor {
262 TaskExecutor::new(Arc::new(HashMap::new()), LogicCompiler::new().into_engine())
263 }
264
265 fn executor_with_handler(name: &str) -> TaskExecutor {
267 let mut handlers: HashMap<String, BoxedFunctionHandler> = HashMap::new();
268 handlers.insert(name.to_string(), Box::new(MockAsyncFunction));
269 TaskExecutor::new(Arc::new(handlers), LogicCompiler::new().into_engine())
270 }
271
272 #[test]
273 fn test_has_function() {
274 let task_executor = executor_with_no_handlers();
275
276 assert!(task_executor.has_function("map"));
278 assert!(task_executor.has_function("validation"));
279 assert!(task_executor.has_function("validate"));
280
281 assert!(!task_executor.has_function("nonexistent"));
283 assert!(!task_executor.has_function(""));
284 }
285
286 #[test]
287 fn has_function_is_false_for_config_only_integrations_without_a_handler() {
288 let task_executor = executor_with_no_handlers();
293
294 for name in ["http_call", "enrich", "publish_kafka"] {
295 assert!(
296 !task_executor.has_function(name),
297 "'{name}' has no registered handler, so it cannot be run"
298 );
299 assert!(is_builtin_function(name));
301 }
302 }
303
304 #[test]
305 fn has_function_is_true_for_config_only_integrations_once_registered() {
306 for name in ["http_call", "enrich", "publish_kafka"] {
307 assert!(
308 executor_with_handler(name).has_function(name),
309 "'{name}' has a registered handler, so it can be run"
310 );
311 }
312 }
313
314 #[test]
315 fn has_function_is_true_for_every_self_contained_builtin_with_an_empty_registry() {
316 let task_executor = executor_with_no_handlers();
317
318 for name in [
321 "map",
322 "validation",
323 "validate",
324 "parse_json",
325 "parse_xml",
326 "publish_json",
327 "publish_xml",
328 "filter",
329 "log",
330 ] {
331 assert!(
332 task_executor.has_function(name),
333 "'{name}' is executed by the crate and needs no registration"
334 );
335 }
336 }
337
338 #[test]
339 fn registering_a_handler_under_a_self_contained_name_does_not_change_the_answer() {
340 assert!(executor_with_handler("map").has_function("map"));
345 assert!(executor_with_no_handlers().has_function("map"));
346 }
347
348 #[test]
349 fn has_function_is_unchanged_for_custom_names() {
350 assert!(executor_with_handler("custom_test").has_function("custom_test"));
351 assert!(!executor_with_handler("custom_test").has_function("other_custom"));
352 }
353
354 #[test]
355 fn test_custom_function_count() {
356 let mut custom_functions: HashMap<String, BoxedFunctionHandler> = HashMap::new();
357 custom_functions.insert("custom_test".to_string(), Box::new(MockAsyncFunction));
358
359 let engine = LogicCompiler::new().into_engine();
360 let task_executor = TaskExecutor::new(Arc::new(custom_functions), engine);
361
362 assert_eq!(task_executor.custom_function_count(), 1);
363 }
364
365 struct MockAsyncFunction;
367
368 #[async_trait::async_trait]
369 impl AsyncFunctionHandler for MockAsyncFunction {
370 type Input = serde_json::Value;
371
372 async fn execute(
373 &self,
374 _ctx: &mut TaskContext<'_>,
375 _input: &serde_json::Value,
376 ) -> Result<TaskOutcome> {
377 Ok(TaskOutcome::Success)
378 }
379 }
380}