use crate::engine::error::{DataflowError, Result};
use crate::engine::functions::config::{BuiltinKind, builtin_function_kind};
use crate::engine::functions::{BoxedFunctionHandler, FunctionConfig};
use crate::engine::message::{Change, Message};
use crate::engine::task::Task;
use crate::engine::task_context::TaskContext;
use crate::engine::task_outcome::TaskOutcome;
use datalogic_rs::Engine;
use log::{debug, error};
use std::any::Any;
use std::collections::HashMap;
use std::sync::Arc;
pub struct TaskExecutor {
task_functions: Arc<HashMap<String, BoxedFunctionHandler>>,
engine: Arc<Engine>,
}
impl TaskExecutor {
pub fn new(
task_functions: Arc<HashMap<String, BoxedFunctionHandler>>,
engine: Arc<Engine>,
) -> Self {
Self {
task_functions,
engine,
}
}
pub async fn execute(
&self,
task: &Task,
message: &mut Message,
) -> Result<(TaskOutcome, Vec<Change>)> {
debug!(
"Executing task: {} with function: {:?}",
task.id,
task.function.function_name()
);
match &task.function {
FunctionConfig::Map { input, .. } => input.execute(message, &self.engine),
FunctionConfig::Validation { input, .. } => input.execute(message, &self.engine),
FunctionConfig::ParseJson { input, .. } => {
crate::engine::functions::parse::execute_parse_json(message, input)
}
FunctionConfig::ParseXml { input, .. } => {
crate::engine::functions::parse::execute_parse_xml(message, input)
}
FunctionConfig::PublishJson { input, .. } => {
crate::engine::functions::publish::execute_publish_json(message, input)
}
FunctionConfig::PublishXml { input, .. } => {
crate::engine::functions::publish::execute_publish_xml(message, input)
}
FunctionConfig::Filter { input, .. } => input.execute(message, &self.engine),
FunctionConfig::Log { input, .. } => input.execute(message, &self.engine),
FunctionConfig::HttpCall { input, .. } => {
self.dispatch_handler(task.function.function_name(), message, input)
.await
}
FunctionConfig::Enrich { input, .. } => {
self.dispatch_handler(task.function.function_name(), message, input)
.await
}
FunctionConfig::PublishKafka { input, .. } => {
self.dispatch_handler(task.function.function_name(), message, input)
.await
}
FunctionConfig::Custom {
name,
compiled_input,
..
} => {
let any_input = compiled_input.as_ref().ok_or_else(|| {
DataflowError::Validation(format!(
"Custom function '{}' has no precompiled input — \
was the workflow built outside Engine::new?",
name
))
})?;
self.dispatch_handler_any(name, message, any_input.as_any())
.await
}
}
}
async fn dispatch_handler<T>(
&self,
name: &str,
message: &mut Message,
input: &T,
) -> Result<(TaskOutcome, Vec<Change>)>
where
T: Any + Send + Sync,
{
let any_input: &(dyn Any + Send + Sync) = input;
self.dispatch_handler_any(name, message, any_input).await
}
async fn dispatch_handler_any(
&self,
name: &str,
message: &mut Message,
any_input: &(dyn Any + Send + Sync),
) -> Result<(TaskOutcome, Vec<Change>)> {
let handler = self.task_functions.get(name).ok_or_else(|| {
error!("Function handler not found: {}", name);
DataflowError::FunctionNotFound(name.to_string())
})?;
let mut ctx = TaskContext::new(message, &self.engine);
let outcome = handler.dyn_execute(&mut ctx, any_input).await?;
let changes = ctx.into_changes();
Ok((outcome, changes))
}
pub fn has_function(&self, name: &str) -> bool {
match builtin_function_kind(name) {
Some(BuiltinKind::SelfContained) => true,
_ => self.task_functions.contains_key(name),
}
}
pub fn task_functions(&self) -> Arc<HashMap<String, BoxedFunctionHandler>> {
Arc::clone(&self.task_functions)
}
pub fn custom_function_count(&self) -> usize {
self.task_functions.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::AsyncFunctionHandler;
use crate::engine::compiler::LogicCompiler;
use crate::engine::functions::config::is_builtin_function;
fn executor_with_no_handlers() -> TaskExecutor {
TaskExecutor::new(Arc::new(HashMap::new()), LogicCompiler::new().into_engine())
}
fn executor_with_handler(name: &str) -> TaskExecutor {
let mut handlers: HashMap<String, BoxedFunctionHandler> = HashMap::new();
handlers.insert(name.to_string(), Box::new(MockAsyncFunction));
TaskExecutor::new(Arc::new(handlers), LogicCompiler::new().into_engine())
}
#[test]
fn test_has_function() {
let task_executor = executor_with_no_handlers();
assert!(task_executor.has_function("map"));
assert!(task_executor.has_function("validation"));
assert!(task_executor.has_function("validate"));
assert!(!task_executor.has_function("nonexistent"));
assert!(!task_executor.has_function(""));
}
#[test]
fn has_function_is_false_for_config_only_integrations_without_a_handler() {
let task_executor = executor_with_no_handlers();
for name in ["http_call", "enrich", "publish_kafka"] {
assert!(
!task_executor.has_function(name),
"'{name}' has no registered handler, so it cannot be run"
);
assert!(is_builtin_function(name));
}
}
#[test]
fn has_function_is_true_for_config_only_integrations_once_registered() {
for name in ["http_call", "enrich", "publish_kafka"] {
assert!(
executor_with_handler(name).has_function(name),
"'{name}' has a registered handler, so it can be run"
);
}
}
#[test]
fn has_function_is_true_for_every_self_contained_builtin_with_an_empty_registry() {
let task_executor = executor_with_no_handlers();
for name in [
"map",
"validation",
"validate",
"parse_json",
"parse_xml",
"publish_json",
"publish_xml",
"filter",
"log",
] {
assert!(
task_executor.has_function(name),
"'{name}' is executed by the crate and needs no registration"
);
}
}
#[test]
fn registering_a_handler_under_a_self_contained_name_does_not_change_the_answer() {
assert!(executor_with_handler("map").has_function("map"));
assert!(executor_with_no_handlers().has_function("map"));
}
#[test]
fn has_function_is_unchanged_for_custom_names() {
assert!(executor_with_handler("custom_test").has_function("custom_test"));
assert!(!executor_with_handler("custom_test").has_function("other_custom"));
}
#[test]
fn test_custom_function_count() {
let mut custom_functions: HashMap<String, BoxedFunctionHandler> = HashMap::new();
custom_functions.insert("custom_test".to_string(), Box::new(MockAsyncFunction));
let engine = LogicCompiler::new().into_engine();
let task_executor = TaskExecutor::new(Arc::new(custom_functions), engine);
assert_eq!(task_executor.custom_function_count(), 1);
}
struct MockAsyncFunction;
#[async_trait::async_trait]
impl AsyncFunctionHandler for MockAsyncFunction {
type Input = serde_json::Value;
async fn execute(
&self,
_ctx: &mut TaskContext<'_>,
_input: &serde_json::Value,
) -> Result<TaskOutcome> {
Ok(TaskOutcome::Success)
}
}
}