use crate::conditional::expressions::{Expression, ExpressionContext};
use crate::error::{Result, WorkflowError};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConditionalBranch {
pub condition: Expression,
pub then_tasks: Vec<String>,
pub else_tasks: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SwitchCase {
pub variable: String,
pub cases: Vec<Case>,
pub default: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Case {
pub value: serde_json::Value,
pub tasks: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoopCondition {
pub condition: Expression,
pub body_tasks: Vec<String>,
pub max_iterations: usize,
}
impl ConditionalBranch {
pub fn new(condition: Expression, then_tasks: Vec<String>) -> Self {
Self {
condition,
then_tasks,
else_tasks: None,
}
}
pub fn with_else(mut self, else_tasks: Vec<String>) -> Self {
self.else_tasks = Some(else_tasks);
self
}
pub fn evaluate(&self, context: &ExpressionContext) -> Result<Vec<String>> {
let condition_result = self.condition.evaluate(context)?;
let is_true = condition_result
.as_bool()
.ok_or_else(|| WorkflowError::conditional("Condition must evaluate to boolean"))?;
if is_true {
Ok(self.then_tasks.clone())
} else if let Some(ref else_tasks) = self.else_tasks {
Ok(else_tasks.clone())
} else {
Ok(Vec::new())
}
}
}
impl SwitchCase {
pub fn new(variable: String, cases: Vec<Case>) -> Self {
Self {
variable,
cases,
default: None,
}
}
pub fn with_default(mut self, default_tasks: Vec<String>) -> Self {
self.default = Some(default_tasks);
self
}
pub fn evaluate(&self, context: &ExpressionContext) -> Result<Vec<String>> {
let value = context.get(&self.variable).ok_or_else(|| {
WorkflowError::conditional(format!("Variable '{}' not found", self.variable))
})?;
for case in &self.cases {
if &case.value == value {
return Ok(case.tasks.clone());
}
}
Ok(self.default.clone().unwrap_or_default())
}
}
impl LoopCondition {
pub fn new(condition: Expression, body_tasks: Vec<String>, max_iterations: usize) -> Self {
Self {
condition,
body_tasks,
max_iterations,
}
}
pub fn should_continue(&self, context: &ExpressionContext) -> Result<bool> {
let condition_result = self.condition.evaluate(context)?;
condition_result
.as_bool()
.ok_or_else(|| WorkflowError::conditional("Loop condition must evaluate to boolean"))
}
pub fn get_body_tasks(&self) -> Vec<String> {
self.body_tasks.clone()
}
}
#[derive(Debug, Clone)]
pub enum ExecutionDecision {
Execute(Vec<String>),
Skip,
Repeat(Vec<String>),
}
pub struct ConditionalEvaluator {
branches: Vec<ConditionalBranch>,
switches: Vec<SwitchCase>,
loops: Vec<LoopCondition>,
}
impl ConditionalEvaluator {
pub fn new() -> Self {
Self {
branches: Vec::new(),
switches: Vec::new(),
loops: Vec::new(),
}
}
pub fn add_branch(&mut self, branch: ConditionalBranch) {
self.branches.push(branch);
}
pub fn add_switch(&mut self, switch: SwitchCase) {
self.switches.push(switch);
}
pub fn add_loop(&mut self, loop_cond: LoopCondition) {
self.loops.push(loop_cond);
}
pub fn evaluate(&self, context: &ExpressionContext) -> Result<Vec<String>> {
let mut tasks_to_execute = Vec::new();
for branch in &self.branches {
tasks_to_execute.extend(branch.evaluate(context)?);
}
for switch in &self.switches {
tasks_to_execute.extend(switch.evaluate(context)?);
}
Ok(tasks_to_execute)
}
}
impl Default for ConditionalEvaluator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::conditional::expressions::Expression;
use serde_json::Value;
use std::collections::HashMap;
#[test]
fn test_conditional_branch() {
let branch = ConditionalBranch::new(
Expression::eq(
Expression::variable("status"),
Expression::literal(Value::String("success".to_string())),
),
vec!["task1".to_string(), "task2".to_string()],
)
.with_else(vec!["task3".to_string()]);
let mut ctx = HashMap::new();
ctx.insert("status".to_string(), Value::String("success".to_string()));
let tasks = branch.evaluate(&ctx).expect("Failed to evaluate");
assert_eq!(tasks, vec!["task1".to_string(), "task2".to_string()]);
}
#[test]
fn test_switch_case() {
let switch = SwitchCase::new(
"env".to_string(),
vec![
Case {
value: Value::String("dev".to_string()),
tasks: vec!["dev_task".to_string()],
},
Case {
value: Value::String("prod".to_string()),
tasks: vec!["prod_task".to_string()],
},
],
)
.with_default(vec!["default_task".to_string()]);
let mut ctx = HashMap::new();
ctx.insert("env".to_string(), Value::String("prod".to_string()));
let tasks = switch.evaluate(&ctx).expect("Failed to evaluate");
assert_eq!(tasks, vec!["prod_task".to_string()]);
}
#[test]
fn test_loop_condition() {
let loop_cond = LoopCondition::new(
Expression::binary(
Expression::variable("count"),
crate::conditional::expressions::BinaryOperator::Lt,
Expression::literal(Value::Number(5.into())),
),
vec!["increment".to_string()],
10,
);
let mut ctx = HashMap::new();
ctx.insert("count".to_string(), Value::Number(3.into()));
let should_continue = loop_cond.should_continue(&ctx).expect("Failed to evaluate");
assert!(should_continue);
}
}