use crate::cook::execution::errors::{MapReduceError, MapReduceResult};
use crate::cook::workflow::{StepResult, WorkflowStep};
use async_trait::async_trait;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
pub use super::context::ExecutionContext;
use super::types;
#[derive(Debug, Clone)]
pub struct CommandResult {
pub output: Option<String>,
pub exit_code: i32,
pub variables: HashMap<String, String>,
pub duration: Duration,
pub success: bool,
pub stderr: String,
pub json_log_location: Option<String>,
}
impl From<CommandResult> for StepResult {
fn from(result: CommandResult) -> Self {
StepResult {
success: result.success,
exit_code: Some(result.exit_code),
stdout: result.output.unwrap_or_default(),
stderr: result.stderr,
json_log_location: result.json_log_location,
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum CommandError {
#[error("Execution failed: {0}")]
ExecutionFailed(String),
#[error("Timeout occurred: {0}")]
Timeout(String),
#[error("Invalid configuration: {0}")]
InvalidConfiguration(String),
#[error("Interpolation failed: {0}")]
InterpolationFailed(String),
}
impl From<CommandError> for MapReduceError {
fn from(error: CommandError) -> Self {
MapReduceError::General {
message: error.to_string(),
source: None,
}
}
}
#[async_trait]
pub trait CommandExecutor: Send + Sync {
async fn execute(
&self,
step: &WorkflowStep,
context: &ExecutionContext,
) -> Result<CommandResult, CommandError>;
fn supports(&self, command_type: &crate::cook::workflow::CommandType) -> bool;
}
pub struct CommandRouter {
executors: HashMap<String, Arc<dyn CommandExecutor>>,
}
impl CommandRouter {
pub fn new() -> Self {
Self {
executors: HashMap::new(),
}
}
pub fn register(&mut self, name: String, executor: Arc<dyn CommandExecutor>) {
self.executors.insert(name, executor);
}
pub async fn execute(
&self,
step: &WorkflowStep,
context: &ExecutionContext,
) -> MapReduceResult<CommandResult> {
let command_type = types::determine_command_type(step)?;
for executor in self.executors.values() {
if executor.supports(&command_type) {
return executor.execute(step, context).await.map_err(|e| e.into());
}
}
Err(MapReduceError::InvalidConfiguration {
reason: "No executor found for command type".to_string(),
field: "command_type".to_string(),
value: format!("{:?}", command_type),
})
}
}
impl Default for CommandRouter {
fn default() -> Self {
Self::new()
}
}