feat(wfe-core): add StepCondition types and PointerStatus::Skipped

This commit is contained in:
2026-03-26 17:05:14 +00:00
parent a71fa531f9
commit aff3df6fcf
3 changed files with 213 additions and 0 deletions

View File

@@ -0,0 +1,209 @@
use serde::{Deserialize, Serialize};
/// A condition that determines whether a workflow step should execute.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum StepCondition {
/// All sub-conditions must be true (AND).
All(Vec<StepCondition>),
/// At least one sub-condition must be true (OR).
Any(Vec<StepCondition>),
/// No sub-conditions may be true (NOR).
None(Vec<StepCondition>),
/// Exactly one sub-condition must be true (XOR).
OneOf(Vec<StepCondition>),
/// Negation of a single condition (NOT).
Not(Box<StepCondition>),
/// A leaf comparison against a field in workflow data.
Comparison(FieldComparison),
}
/// A comparison of a workflow data field against an expected value.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FieldComparison {
/// Dot-separated field path, e.g. ".outputs.docker_started".
pub field: String,
/// The comparison operator.
pub operator: ComparisonOp,
/// The value to compare against. Required for all operators except IsNull/IsNotNull.
pub value: Option<serde_json::Value>,
}
/// Comparison operators for field conditions.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ComparisonOp {
Equals,
NotEquals,
Gt,
Gte,
Lt,
Lte,
Contains,
IsNull,
IsNotNull,
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use serde_json::json;
#[test]
fn comparison_op_serde_round_trip() {
for op in [
ComparisonOp::Equals,
ComparisonOp::NotEquals,
ComparisonOp::Gt,
ComparisonOp::Gte,
ComparisonOp::Lt,
ComparisonOp::Lte,
ComparisonOp::Contains,
ComparisonOp::IsNull,
ComparisonOp::IsNotNull,
] {
let json_str = serde_json::to_string(&op).unwrap();
let deserialized: ComparisonOp = serde_json::from_str(&json_str).unwrap();
assert_eq!(op, deserialized);
}
}
#[test]
fn field_comparison_serde_round_trip() {
let comp = FieldComparison {
field: ".outputs.status".to_string(),
operator: ComparisonOp::Equals,
value: Some(json!("success")),
};
let json_str = serde_json::to_string(&comp).unwrap();
let deserialized: FieldComparison = serde_json::from_str(&json_str).unwrap();
assert_eq!(comp, deserialized);
}
#[test]
fn field_comparison_without_value_serde_round_trip() {
let comp = FieldComparison {
field: ".outputs.result".to_string(),
operator: ComparisonOp::IsNull,
value: None,
};
let json_str = serde_json::to_string(&comp).unwrap();
let deserialized: FieldComparison = serde_json::from_str(&json_str).unwrap();
assert_eq!(comp, deserialized);
}
#[test]
fn step_condition_comparison_serde_round_trip() {
let condition = StepCondition::Comparison(FieldComparison {
field: ".count".to_string(),
operator: ComparisonOp::Gt,
value: Some(json!(5)),
});
let json_str = serde_json::to_string(&condition).unwrap();
let deserialized: StepCondition = serde_json::from_str(&json_str).unwrap();
assert_eq!(condition, deserialized);
}
#[test]
fn step_condition_not_serde_round_trip() {
let condition = StepCondition::Not(Box::new(StepCondition::Comparison(FieldComparison {
field: ".active".to_string(),
operator: ComparisonOp::Equals,
value: Some(json!(false)),
})));
let json_str = serde_json::to_string(&condition).unwrap();
let deserialized: StepCondition = serde_json::from_str(&json_str).unwrap();
assert_eq!(condition, deserialized);
}
#[test]
fn step_condition_all_serde_round_trip() {
let condition = StepCondition::All(vec![
StepCondition::Comparison(FieldComparison {
field: ".a".to_string(),
operator: ComparisonOp::Equals,
value: Some(json!(1)),
}),
StepCondition::Comparison(FieldComparison {
field: ".b".to_string(),
operator: ComparisonOp::Equals,
value: Some(json!(2)),
}),
]);
let json_str = serde_json::to_string(&condition).unwrap();
let deserialized: StepCondition = serde_json::from_str(&json_str).unwrap();
assert_eq!(condition, deserialized);
}
#[test]
fn step_condition_any_serde_round_trip() {
let condition = StepCondition::Any(vec![
StepCondition::Comparison(FieldComparison {
field: ".x".to_string(),
operator: ComparisonOp::IsNull,
value: None,
}),
]);
let json_str = serde_json::to_string(&condition).unwrap();
let deserialized: StepCondition = serde_json::from_str(&json_str).unwrap();
assert_eq!(condition, deserialized);
}
#[test]
fn step_condition_none_serde_round_trip() {
let condition = StepCondition::None(vec![
StepCondition::Comparison(FieldComparison {
field: ".err".to_string(),
operator: ComparisonOp::IsNotNull,
value: None,
}),
]);
let json_str = serde_json::to_string(&condition).unwrap();
let deserialized: StepCondition = serde_json::from_str(&json_str).unwrap();
assert_eq!(condition, deserialized);
}
#[test]
fn step_condition_one_of_serde_round_trip() {
let condition = StepCondition::OneOf(vec![
StepCondition::Comparison(FieldComparison {
field: ".mode".to_string(),
operator: ComparisonOp::Equals,
value: Some(json!("fast")),
}),
StepCondition::Comparison(FieldComparison {
field: ".mode".to_string(),
operator: ComparisonOp::Equals,
value: Some(json!("slow")),
}),
]);
let json_str = serde_json::to_string(&condition).unwrap();
let deserialized: StepCondition = serde_json::from_str(&json_str).unwrap();
assert_eq!(condition, deserialized);
}
#[test]
fn nested_combinator_serde_round_trip() {
let condition = StepCondition::All(vec![
StepCondition::Any(vec![
StepCondition::Comparison(FieldComparison {
field: ".a".to_string(),
operator: ComparisonOp::Equals,
value: Some(json!(1)),
}),
StepCondition::Comparison(FieldComparison {
field: ".b".to_string(),
operator: ComparisonOp::Equals,
value: Some(json!(2)),
}),
]),
StepCondition::Not(Box::new(StepCondition::Comparison(FieldComparison {
field: ".c".to_string(),
operator: ComparisonOp::IsNull,
value: None,
}))),
]);
let json_str = serde_json::to_string(&condition).unwrap();
let deserialized: StepCondition = serde_json::from_str(&json_str).unwrap();
assert_eq!(condition, deserialized);
}
}

View File

@@ -1,3 +1,4 @@
pub mod condition;
pub mod error_behavior;
pub mod event;
pub mod execution_error;
@@ -12,6 +13,7 @@ pub mod status;
pub mod workflow_definition;
pub mod workflow_instance;
pub use condition::{ComparisonOp, FieldComparison, StepCondition};
pub use error_behavior::ErrorBehavior;
pub use event::{Event, EventSubscription};
pub use execution_error::ExecutionError;

View File

@@ -15,6 +15,7 @@ pub enum PointerStatus {
Pending,
Running,
Complete,
Skipped,
Sleeping,
WaitingForEvent,
Failed,
@@ -58,6 +59,7 @@ mod tests {
PointerStatus::Pending,
PointerStatus::Running,
PointerStatus::Complete,
PointerStatus::Skipped,
PointerStatus::Sleeping,
PointerStatus::WaitingForEvent,
PointerStatus::Failed,