use async_trait::async_trait;
use crate::template::{ExpressionResolver, HandlebarsResolver};
use crate::{ExecutionMetrics, ExecutionResult, FlowPattern, RunError, RunId, RunStatus};
use super::{build_capability_output, execute_worker, PatternContext, PatternExecutor};
pub struct ConditionalExecutor;
impl Default for ConditionalExecutor {
fn default() -> Self {
Self::new()
}
}
impl ConditionalExecutor {
pub fn new() -> Self {
ConditionalExecutor
}
fn evaluate_condition(&self, condition: &str, ctx: &PatternContext) -> Result<bool, RunError> {
let resolver = HandlebarsResolver::new();
let resolved = resolver
.resolve(condition, &ctx.scope)
.map_err(|e| e.to_run_error())?;
let trimmed = resolved.trim().to_lowercase();
match trimmed.as_str() {
"true" => Ok(true),
"false" => Ok(false),
other => {
if let Ok(n) = other.parse::<i64>() {
Ok(n != 0)
} else if let Ok(n) = other.parse::<f64>() {
Ok(n != 0.0)
} else {
Ok(other != "false" && !other.is_empty())
}
}
}
}
}
#[async_trait]
impl PatternExecutor for ConditionalExecutor {
fn name(&self) -> &'static str {
"conditional"
}
async fn execute(
&self,
ctx: &PatternContext,
runtime: &dyn crate::RuntimeAdapter,
cancel: &crate::CancellationToken,
) -> Result<ExecutionResult, RunError> {
let (condition, then_branch, else_branch) = match &ctx.swarm.flow {
FlowPattern::Conditional {
condition,
then,
else_,
} => (condition.clone(), then.clone(), else_.clone()),
_ => {
return Err(RunError::PatternError {
pattern: "conditional".into(),
step: "flow".into(),
message: "ConditionalExecutor requires Conditional pattern in flow".into(),
})
}
};
if cancel.is_cancelled().await {
return Ok(ExecutionResult {
run_id: RunId::new(),
status: RunStatus::Cancelled,
artifacts: vec![],
error: Some(RunError::Cancelled {
reason: "Execution cancelled".into(),
}),
metrics: ExecutionMetrics::default(),
output: None,
});
}
let condition_result = self.evaluate_condition(&condition, ctx)?;
let branch_name = if condition_result {
then_branch
} else {
else_branch.ok_or_else(|| RunError::PatternError {
pattern: "conditional".into(),
step: "else".into(),
message: "No else branch defined for false condition".into(),
})?
};
let worker = ctx
.get_worker(&branch_name)
.ok_or_else(|| RunError::PatternError {
pattern: "conditional".into(),
step: branch_name.clone(),
message: format!("Worker '{}' not found in swarm for branch", branch_name),
})?;
let result = execute_worker(worker, runtime, &ctx.runtime_ctx, &ctx.scope, cancel).await?;
let mut final_scope = ctx.scope.clone();
if let Some(output) = &result.output {
final_scope.add_step_output(branch_name.clone(), output.clone());
}
let exec_result = ExecutionResult {
run_id: RunId::new(),
status: result.status,
artifacts: result.artifacts,
error: result.error,
metrics: result.metrics,
output: None,
};
Ok(build_capability_output(
exec_result,
&ctx.swarm,
&final_scope,
))
}
async fn on_failure(
&self,
_ctx: &mut PatternContext,
_runtime: &dyn crate::RuntimeAdapter,
_failed_worker: &str,
_error: &RunError,
) -> Result<bool, RunError> {
Ok(false)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{CancellationToken, ExecutionContext, FlowPattern, RuntimeKind, SwarmFile};
#[test]
fn test_conditional_executor_name() {
let executor = ConditionalExecutor::new();
assert_eq!(executor.name(), "conditional");
}
#[test]
fn test_condition_evaluation() {
let executor = ConditionalExecutor::new();
let swarm = SwarmFile::new(
"test",
FlowPattern::Conditional {
condition: "true".into(),
then: "a".into(),
else_: None,
},
);
let ctx = PatternContext::new(swarm, ExecutionContext::new("ctx", RuntimeKind::Local));
assert!(executor.evaluate_condition("true", &ctx).unwrap());
assert!(!executor.evaluate_condition("false", &ctx).unwrap());
assert!(executor.evaluate_condition("x > 0", &ctx).unwrap());
}
#[tokio::test]
async fn test_conditional_executor_wrong_pattern() {
let executor = ConditionalExecutor::new();
let swarm = SwarmFile::new("test", FlowPattern::Sequence { steps: vec![] });
let ctx = PatternContext::new(swarm, ExecutionContext::new("ctx", RuntimeKind::Local));
let cancel = CancellationToken::new();
let result = executor
.execute(&ctx, &crate::LocalRuntime::new(), &cancel)
.await;
assert!(result.is_err());
}
}