use super::executor::{
CommandError, CommandExecutor, CommandResult, ExecutionContext as MapReduceContext,
};
use crate::commands::{CommandRegistry, ExecutionContext};
use crate::cook::workflow::{CommandType, WorkflowStep};
use async_trait::async_trait;
use std::sync::Arc;
use std::time::Instant;
pub struct HandlerCommandExecutor {
command_registry: Arc<CommandRegistry>,
}
impl HandlerCommandExecutor {
pub fn new(command_registry: Arc<CommandRegistry>) -> Self {
Self { command_registry }
}
fn build_exec_context(context: &MapReduceContext) -> ExecutionContext {
let mut exec_context = ExecutionContext::new(context.worktree_path.clone());
exec_context.add_env_var(
"PRODIGY_WORKTREE".to_string(),
context.worktree_name.clone(),
);
exec_context.add_env_var("PRODIGY_ITEM_ID".to_string(), context.item_id.clone());
exec_context.add_env_var("PRODIGY_AUTOMATION".to_string(), "true".to_string());
for (key, value) in &context.environment {
exec_context.add_env_var(key.clone(), value.clone());
}
exec_context
}
fn convert_attributes(
attributes: &std::collections::HashMap<String, serde_json::Value>,
) -> std::collections::HashMap<String, crate::commands::AttributeValue> {
let mut converted = std::collections::HashMap::new();
for (key, value) in attributes {
let attr_value = Self::json_to_attribute_value(value);
converted.insert(key.clone(), attr_value);
}
converted
}
fn json_to_attribute_value(value: &serde_json::Value) -> crate::commands::AttributeValue {
match value {
serde_json::Value::String(s) => crate::commands::AttributeValue::from(s.clone()),
serde_json::Value::Bool(b) => crate::commands::AttributeValue::from(*b),
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
crate::commands::AttributeValue::from(i as i32)
} else if let Some(f) = n.as_f64() {
crate::commands::AttributeValue::from(f)
} else {
crate::commands::AttributeValue::from(n.to_string())
}
}
_ => crate::commands::AttributeValue::from(value.to_string()),
}
}
fn build_result(result: crate::commands::CommandResult, start: Instant) -> CommandResult {
let stdout = result.stdout.as_ref().cloned().unwrap_or_else(|| {
result
.data
.as_ref()
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string()
});
CommandResult {
output: Some(stdout),
exit_code: result.exit_code.unwrap_or(0),
variables: std::collections::HashMap::new(),
duration: start.elapsed(),
success: result.is_success(),
stderr: result.stderr.unwrap_or_default(),
json_log_location: result.json_log_location,
}
}
}
#[async_trait]
impl CommandExecutor for HandlerCommandExecutor {
async fn execute(
&self,
step: &WorkflowStep,
context: &MapReduceContext,
) -> Result<CommandResult, CommandError> {
let start = Instant::now();
let handler = step
.handler
.as_ref()
.ok_or_else(|| CommandError::InvalidConfiguration("No handler in step".to_string()))?;
let exec_context = Self::build_exec_context(context);
let converted_attributes = Self::convert_attributes(&handler.attributes);
let result = self
.command_registry
.execute(&handler.name, &exec_context, converted_attributes)
.await;
Ok(Self::build_result(result, start))
}
fn supports(&self, command_type: &CommandType) -> bool {
matches!(command_type, CommandType::Handler { .. })
}
}