use crate::models::command::CommandStep;
use std::collections::HashMap;
use std::path::Path;
#[derive(Debug)]
pub struct CommandResult {
pub success: bool,
pub stdout: String,
pub stderr: String,
pub exit_code: Option<i32>,
pub captured_vars: HashMap<String, String>,
}
#[derive(Debug)]
pub enum CommandError {
Timeout,
ExecutionFailed(String),
JsonParsingFailed(String),
Validation(String),
}
impl std::fmt::Display for CommandError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CommandError::Timeout => write!(f, "Command timed out"),
CommandError::ExecutionFailed(msg) => write!(f, "Command execution failed: {msg}"),
CommandError::JsonParsingFailed(msg) => write!(f, "JSON parsing failed: {msg}"),
CommandError::Validation(msg) => write!(f, "Validation error: {msg}"),
}
}
}
impl std::error::Error for CommandError {}
pub async fn execute_command_step(
step: &CommandStep,
variables: &mut HashMap<String, String>,
work_dir: &Path,
allowed_commands: Option<&[String]>,
) -> Result<CommandResult, CommandError> {
let executor = super::executor::CommandExecutor::new(step, variables, work_dir, allowed_commands);
executor.execute().await
}
#[must_use]
pub fn evaluate_condition(condition: &str) -> bool {
if let Some(eq_pos) = condition.find("==") {
let left = condition[..eq_pos].trim().trim_matches('"');
let right = condition[eq_pos + 2..].trim().trim_matches('"');
return left == right;
} else if let Some(ne_pos) = condition.find("!=") {
let left = condition[..ne_pos].trim().trim_matches('"');
let right = condition[ne_pos + 2..].trim().trim_matches('"');
return left != right;
}
!condition.trim().is_empty() && condition.trim() != "false"
}
#[must_use]
pub fn extract_json_path(json: &serde_json::Value, path: &str) -> Option<String> {
crate::utils::json_path::extract_json_path(json, path)
}
pub async fn execute_command_steps(
steps: &[CommandStep],
variables: &mut HashMap<String, String>,
work_dir: &Path,
allowed_commands: Option<&[String]>,
phase_name: &str,
) -> Result<(), CommandError> {
for (i, step) in steps.iter().enumerate() {
crate::debug!("Executing {} step {}: {:?}", phase_name, i + 1, step.run);
match execute_command_step(step, variables, work_dir, allowed_commands).await {
Ok(result) => {
if !result.success && !step.should_ignore_error() {
return Err(CommandError::ExecutionFailed(format!(
"{phase_name} step {} failed with exit code {:?}: {}",
i + 1,
result.exit_code,
result.stderr
)));
}
crate::debug!(
"{phase_name} step {} completed successfully",
i + 1
);
}
Err(e) if step.should_ignore_error() => {
crate::debug!(
"{phase_name} step {} failed but ignored: {e}",
i + 1
);
}
Err(e) => return Err(e),
}
}
Ok(())
}