2026-03-26 17:25:26 +00:00
|
|
|
use wfe_yaml::schema::{YamlCondition, YamlWorkflow, YamlWorkflowFile};
|
feat(wfe-yaml): add YAML workflow definitions with shell executor
Concourse-CI-inspired YAML format for defining workflows. Compiles
to standard WorkflowDefinition + step factories.
Features:
- Schema parsing with serde_yaml (YamlWorkflow, YamlStep, StepConfig)
- ((var.path)) interpolation from config maps at load time
- YAML anchors (&anchor/*alias) fully supported
- Validation at load time (no runtime surprises)
- Shell executor: runs commands via tokio::process, captures stdout,
parses ##wfe[output name=value] annotations for structured outputs
- Compiler: sequential wiring, parallel blocks, on_failure/on_success/
ensure hooks, error behavior mapping
- Public API: load_workflow(), load_workflow_from_str()
- 23 tests (schema, interpolation, compiler, e2e)
2026-03-25 21:32:00 +00:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_minimal_yaml() {
|
|
|
|
|
let yaml = r#"
|
|
|
|
|
workflow:
|
|
|
|
|
id: minimal
|
|
|
|
|
version: 1
|
|
|
|
|
steps:
|
|
|
|
|
- name: hello
|
|
|
|
|
type: shell
|
|
|
|
|
config:
|
|
|
|
|
run: echo hello
|
|
|
|
|
"#;
|
|
|
|
|
let parsed: YamlWorkflow = serde_yaml::from_str(yaml).unwrap();
|
|
|
|
|
assert_eq!(parsed.workflow.id, "minimal");
|
|
|
|
|
assert_eq!(parsed.workflow.version, 1);
|
|
|
|
|
assert_eq!(parsed.workflow.steps.len(), 1);
|
|
|
|
|
assert_eq!(parsed.workflow.steps[0].name, "hello");
|
|
|
|
|
assert_eq!(
|
|
|
|
|
parsed.workflow.steps[0].step_type.as_deref(),
|
|
|
|
|
Some("shell")
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_with_parallel_block() {
|
|
|
|
|
let yaml = r#"
|
|
|
|
|
workflow:
|
|
|
|
|
id: parallel-wf
|
|
|
|
|
version: 1
|
|
|
|
|
steps:
|
|
|
|
|
- name: parallel-group
|
|
|
|
|
parallel:
|
|
|
|
|
- name: task-a
|
|
|
|
|
type: shell
|
|
|
|
|
config:
|
|
|
|
|
run: echo a
|
|
|
|
|
- name: task-b
|
|
|
|
|
type: shell
|
|
|
|
|
config:
|
|
|
|
|
run: echo b
|
|
|
|
|
"#;
|
|
|
|
|
let parsed: YamlWorkflow = serde_yaml::from_str(yaml).unwrap();
|
|
|
|
|
let step = &parsed.workflow.steps[0];
|
|
|
|
|
assert!(step.parallel.is_some());
|
|
|
|
|
let children = step.parallel.as_ref().unwrap();
|
|
|
|
|
assert_eq!(children.len(), 2);
|
|
|
|
|
assert_eq!(children[0].name, "task-a");
|
|
|
|
|
assert_eq!(children[1].name, "task-b");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_with_hooks() {
|
|
|
|
|
let yaml = r#"
|
|
|
|
|
workflow:
|
|
|
|
|
id: hooks-wf
|
|
|
|
|
version: 1
|
|
|
|
|
steps:
|
|
|
|
|
- name: deploy
|
|
|
|
|
type: shell
|
|
|
|
|
config:
|
|
|
|
|
run: deploy.sh
|
|
|
|
|
on_failure:
|
|
|
|
|
name: rollback
|
|
|
|
|
type: shell
|
|
|
|
|
config:
|
|
|
|
|
run: rollback.sh
|
|
|
|
|
ensure:
|
|
|
|
|
name: cleanup
|
|
|
|
|
type: shell
|
|
|
|
|
config:
|
|
|
|
|
run: cleanup.sh
|
|
|
|
|
"#;
|
|
|
|
|
let parsed: YamlWorkflow = serde_yaml::from_str(yaml).unwrap();
|
|
|
|
|
let step = &parsed.workflow.steps[0];
|
|
|
|
|
assert!(step.on_failure.is_some());
|
|
|
|
|
assert_eq!(step.on_failure.as_ref().unwrap().name, "rollback");
|
|
|
|
|
assert!(step.ensure.is_some());
|
|
|
|
|
assert_eq!(step.ensure.as_ref().unwrap().name, "cleanup");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_with_error_behavior() {
|
|
|
|
|
let yaml = r#"
|
|
|
|
|
workflow:
|
|
|
|
|
id: retry-wf
|
|
|
|
|
version: 1
|
|
|
|
|
error_behavior:
|
|
|
|
|
type: retry
|
|
|
|
|
interval: 5s
|
|
|
|
|
max_retries: 5
|
|
|
|
|
steps:
|
|
|
|
|
- name: flaky
|
|
|
|
|
type: shell
|
|
|
|
|
config:
|
|
|
|
|
run: flaky-task.sh
|
|
|
|
|
error_behavior:
|
|
|
|
|
type: terminate
|
|
|
|
|
"#;
|
|
|
|
|
let parsed: YamlWorkflow = serde_yaml::from_str(yaml).unwrap();
|
|
|
|
|
let eb = parsed.workflow.error_behavior.as_ref().unwrap();
|
|
|
|
|
assert_eq!(eb.behavior_type, "retry");
|
|
|
|
|
assert_eq!(eb.interval.as_deref(), Some("5s"));
|
|
|
|
|
assert_eq!(eb.max_retries, Some(5));
|
|
|
|
|
|
|
|
|
|
let step_eb = parsed.workflow.steps[0].error_behavior.as_ref().unwrap();
|
|
|
|
|
assert_eq!(step_eb.behavior_type, "terminate");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn invalid_yaml_returns_error() {
|
|
|
|
|
let yaml = "this is not valid yaml: [";
|
|
|
|
|
let result: Result<YamlWorkflow, _> = serde_yaml::from_str(yaml);
|
|
|
|
|
assert!(result.is_err());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_with_yaml_anchors_and_aliases() {
|
|
|
|
|
// Direct anchor/alias: reuse entire config block via *alias.
|
|
|
|
|
let yaml = r#"
|
|
|
|
|
workflow:
|
|
|
|
|
id: test-anchors
|
|
|
|
|
version: 1
|
|
|
|
|
steps:
|
|
|
|
|
- name: build
|
|
|
|
|
type: shell
|
|
|
|
|
config: &default_config
|
|
|
|
|
shell: bash
|
|
|
|
|
timeout: 5m
|
|
|
|
|
run: cargo build
|
|
|
|
|
|
|
|
|
|
- name: test
|
|
|
|
|
type: shell
|
|
|
|
|
config: *default_config
|
|
|
|
|
"#;
|
|
|
|
|
let parsed: YamlWorkflow = serde_yaml::from_str(yaml).unwrap();
|
|
|
|
|
assert_eq!(parsed.workflow.steps.len(), 2);
|
|
|
|
|
|
|
|
|
|
// The build step has the original config.
|
|
|
|
|
let build = &parsed.workflow.steps[0];
|
|
|
|
|
let build_config = build.config.as_ref().unwrap();
|
|
|
|
|
assert_eq!(build_config.shell.as_deref(), Some("bash"));
|
|
|
|
|
assert_eq!(build_config.timeout.as_deref(), Some("5m"));
|
|
|
|
|
assert_eq!(build_config.run.as_deref(), Some("cargo build"));
|
|
|
|
|
|
|
|
|
|
// The test step gets the same config via alias.
|
|
|
|
|
let test = &parsed.workflow.steps[1];
|
|
|
|
|
let test_config = test.config.as_ref().unwrap();
|
|
|
|
|
assert_eq!(test_config.shell.as_deref(), Some("bash"));
|
|
|
|
|
assert_eq!(test_config.timeout.as_deref(), Some("5m"));
|
|
|
|
|
assert_eq!(test_config.run.as_deref(), Some("cargo build"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_with_scalar_anchors() {
|
|
|
|
|
// Anchors on scalar values.
|
|
|
|
|
let yaml = r#"
|
|
|
|
|
workflow:
|
|
|
|
|
id: scalar-anchors
|
|
|
|
|
version: 1
|
|
|
|
|
steps:
|
|
|
|
|
- name: step1
|
|
|
|
|
type: &step_type shell
|
|
|
|
|
config:
|
|
|
|
|
run: echo hi
|
|
|
|
|
- name: step2
|
|
|
|
|
type: *step_type
|
|
|
|
|
config:
|
|
|
|
|
run: echo bye
|
|
|
|
|
"#;
|
|
|
|
|
let parsed: YamlWorkflow = serde_yaml::from_str(yaml).unwrap();
|
|
|
|
|
assert_eq!(parsed.workflow.steps[0].step_type.as_deref(), Some("shell"));
|
|
|
|
|
assert_eq!(parsed.workflow.steps[1].step_type.as_deref(), Some("shell"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_with_extra_keys_for_templates() {
|
|
|
|
|
let yaml = r#"
|
|
|
|
|
workflow:
|
|
|
|
|
id: template-wf
|
|
|
|
|
version: 1
|
|
|
|
|
_templates:
|
|
|
|
|
default_shell: bash
|
|
|
|
|
steps:
|
|
|
|
|
- name: step1
|
|
|
|
|
type: shell
|
|
|
|
|
config:
|
|
|
|
|
run: echo hi
|
|
|
|
|
"#;
|
|
|
|
|
let parsed: YamlWorkflow = serde_yaml::from_str(yaml).unwrap();
|
|
|
|
|
assert_eq!(parsed.workflow.id, "template-wf");
|
|
|
|
|
assert_eq!(parsed.workflow.steps.len(), 1);
|
|
|
|
|
}
|
2026-03-26 14:14:15 +00:00
|
|
|
|
|
|
|
|
// --- Multi-workflow file tests ---
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_single_workflow_file() {
|
|
|
|
|
let yaml = r#"
|
|
|
|
|
workflow:
|
|
|
|
|
id: single
|
|
|
|
|
version: 1
|
|
|
|
|
steps:
|
|
|
|
|
- name: step1
|
|
|
|
|
type: shell
|
|
|
|
|
config:
|
|
|
|
|
run: echo hello
|
|
|
|
|
"#;
|
|
|
|
|
let parsed: YamlWorkflowFile = serde_yaml::from_str(yaml).unwrap();
|
|
|
|
|
assert!(parsed.workflow.is_some());
|
|
|
|
|
assert!(parsed.workflows.is_none());
|
|
|
|
|
assert_eq!(parsed.workflow.unwrap().id, "single");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_multi_workflow_file() {
|
|
|
|
|
let yaml = r#"
|
|
|
|
|
workflows:
|
|
|
|
|
- id: build-wf
|
|
|
|
|
version: 1
|
|
|
|
|
steps:
|
|
|
|
|
- name: build
|
|
|
|
|
type: shell
|
|
|
|
|
config:
|
|
|
|
|
run: cargo build
|
|
|
|
|
- id: test-wf
|
|
|
|
|
version: 1
|
|
|
|
|
steps:
|
|
|
|
|
- name: test
|
|
|
|
|
type: shell
|
|
|
|
|
config:
|
|
|
|
|
run: cargo test
|
|
|
|
|
"#;
|
|
|
|
|
let parsed: YamlWorkflowFile = serde_yaml::from_str(yaml).unwrap();
|
|
|
|
|
assert!(parsed.workflow.is_none());
|
|
|
|
|
assert!(parsed.workflows.is_some());
|
|
|
|
|
let workflows = parsed.workflows.unwrap();
|
|
|
|
|
assert_eq!(workflows.len(), 2);
|
|
|
|
|
assert_eq!(workflows[0].id, "build-wf");
|
|
|
|
|
assert_eq!(workflows[1].id, "test-wf");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_workflow_with_input_output_schemas() {
|
|
|
|
|
let yaml = r#"
|
|
|
|
|
workflow:
|
|
|
|
|
id: typed-wf
|
|
|
|
|
version: 1
|
|
|
|
|
inputs:
|
|
|
|
|
repo_url: string
|
|
|
|
|
tags: "list<string>"
|
|
|
|
|
verbose: bool?
|
|
|
|
|
outputs:
|
|
|
|
|
artifact_path: string
|
|
|
|
|
exit_code: integer
|
|
|
|
|
steps:
|
|
|
|
|
- name: step1
|
|
|
|
|
type: shell
|
|
|
|
|
config:
|
|
|
|
|
run: echo hello
|
|
|
|
|
"#;
|
|
|
|
|
let parsed: YamlWorkflow = serde_yaml::from_str(yaml).unwrap();
|
|
|
|
|
assert_eq!(parsed.workflow.inputs.len(), 3);
|
|
|
|
|
assert_eq!(parsed.workflow.inputs.get("repo_url").unwrap(), "string");
|
|
|
|
|
assert_eq!(
|
|
|
|
|
parsed.workflow.inputs.get("tags").unwrap(),
|
|
|
|
|
"list<string>"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(parsed.workflow.inputs.get("verbose").unwrap(), "bool?");
|
|
|
|
|
assert_eq!(parsed.workflow.outputs.len(), 2);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
parsed.workflow.outputs.get("artifact_path").unwrap(),
|
|
|
|
|
"string"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
parsed.workflow.outputs.get("exit_code").unwrap(),
|
|
|
|
|
"integer"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_step_with_workflow_type() {
|
|
|
|
|
let yaml = r#"
|
|
|
|
|
workflow:
|
|
|
|
|
id: parent-wf
|
|
|
|
|
version: 1
|
|
|
|
|
steps:
|
|
|
|
|
- name: run-child
|
|
|
|
|
type: workflow
|
|
|
|
|
config:
|
|
|
|
|
workflow: child-wf
|
|
|
|
|
workflow_version: 2
|
|
|
|
|
inputs:
|
|
|
|
|
- name: repo_url
|
|
|
|
|
path: data.repo
|
|
|
|
|
outputs:
|
|
|
|
|
- name: result
|
|
|
|
|
"#;
|
|
|
|
|
let parsed: YamlWorkflow = serde_yaml::from_str(yaml).unwrap();
|
|
|
|
|
let step = &parsed.workflow.steps[0];
|
|
|
|
|
assert_eq!(step.step_type.as_deref(), Some("workflow"));
|
|
|
|
|
let config = step.config.as_ref().unwrap();
|
|
|
|
|
assert_eq!(config.child_workflow.as_deref(), Some("child-wf"));
|
|
|
|
|
assert_eq!(config.child_version, Some(2));
|
|
|
|
|
assert_eq!(step.inputs.len(), 1);
|
|
|
|
|
assert_eq!(step.outputs.len(), 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_workflow_step_version_defaults() {
|
|
|
|
|
let yaml = r#"
|
|
|
|
|
workflow:
|
|
|
|
|
id: parent-wf
|
|
|
|
|
version: 1
|
|
|
|
|
steps:
|
|
|
|
|
- name: run-child
|
|
|
|
|
type: workflow
|
|
|
|
|
config:
|
|
|
|
|
workflow: child-wf
|
|
|
|
|
"#;
|
|
|
|
|
let parsed: YamlWorkflow = serde_yaml::from_str(yaml).unwrap();
|
|
|
|
|
let config = parsed.workflow.steps[0].config.as_ref().unwrap();
|
|
|
|
|
assert_eq!(config.child_workflow.as_deref(), Some("child-wf"));
|
|
|
|
|
// version not specified, should be None in schema (compiler defaults to 1).
|
|
|
|
|
assert_eq!(config.child_version, None);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_empty_inputs_outputs_default() {
|
|
|
|
|
let yaml = r#"
|
|
|
|
|
workflow:
|
|
|
|
|
id: no-schema-wf
|
|
|
|
|
version: 1
|
|
|
|
|
steps:
|
|
|
|
|
- name: step1
|
|
|
|
|
type: shell
|
|
|
|
|
config:
|
|
|
|
|
run: echo hello
|
|
|
|
|
"#;
|
|
|
|
|
let parsed: YamlWorkflow = serde_yaml::from_str(yaml).unwrap();
|
|
|
|
|
assert!(parsed.workflow.inputs.is_empty());
|
|
|
|
|
assert!(parsed.workflow.outputs.is_empty());
|
|
|
|
|
}
|
2026-03-26 17:25:26 +00:00
|
|
|
|
|
|
|
|
// --- Condition schema tests ---
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_step_with_simple_when_condition() {
|
|
|
|
|
let yaml = r#"
|
|
|
|
|
workflow:
|
|
|
|
|
id: cond-wf
|
|
|
|
|
version: 1
|
|
|
|
|
steps:
|
|
|
|
|
- name: deploy
|
|
|
|
|
type: shell
|
|
|
|
|
config:
|
|
|
|
|
run: deploy.sh
|
|
|
|
|
when:
|
|
|
|
|
field: .inputs.enabled
|
|
|
|
|
equals: true
|
|
|
|
|
"#;
|
|
|
|
|
let parsed: YamlWorkflow = serde_yaml::from_str(yaml).unwrap();
|
|
|
|
|
let step = &parsed.workflow.steps[0];
|
|
|
|
|
assert!(step.when.is_some());
|
|
|
|
|
match step.when.as_ref().unwrap() {
|
|
|
|
|
YamlCondition::Comparison(cmp) => {
|
|
|
|
|
assert_eq!(cmp.field, ".inputs.enabled");
|
|
|
|
|
assert!(cmp.equals.is_some());
|
|
|
|
|
}
|
|
|
|
|
_ => panic!("Expected Comparison variant"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_step_with_nested_combinator_conditions() {
|
|
|
|
|
let yaml = r#"
|
|
|
|
|
workflow:
|
|
|
|
|
id: nested-cond-wf
|
|
|
|
|
version: 1
|
|
|
|
|
steps:
|
|
|
|
|
- name: deploy
|
|
|
|
|
type: shell
|
|
|
|
|
config:
|
|
|
|
|
run: deploy.sh
|
|
|
|
|
when:
|
|
|
|
|
all:
|
|
|
|
|
- field: .inputs.count
|
|
|
|
|
gt: 5
|
|
|
|
|
- any:
|
|
|
|
|
- field: .inputs.env
|
|
|
|
|
equals: prod
|
|
|
|
|
- field: .inputs.env
|
|
|
|
|
equals: staging
|
|
|
|
|
"#;
|
|
|
|
|
let parsed: YamlWorkflow = serde_yaml::from_str(yaml).unwrap();
|
|
|
|
|
let step = &parsed.workflow.steps[0];
|
|
|
|
|
assert!(step.when.is_some());
|
|
|
|
|
match step.when.as_ref().unwrap() {
|
|
|
|
|
YamlCondition::Combinator(c) => {
|
|
|
|
|
assert!(c.all.is_some());
|
|
|
|
|
let children = c.all.as_ref().unwrap();
|
|
|
|
|
assert_eq!(children.len(), 2);
|
|
|
|
|
}
|
|
|
|
|
_ => panic!("Expected Combinator variant"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_step_with_not_condition() {
|
|
|
|
|
let yaml = r#"
|
|
|
|
|
workflow:
|
|
|
|
|
id: not-cond-wf
|
|
|
|
|
version: 1
|
|
|
|
|
steps:
|
|
|
|
|
- name: deploy
|
|
|
|
|
type: shell
|
|
|
|
|
config:
|
|
|
|
|
run: deploy.sh
|
|
|
|
|
when:
|
|
|
|
|
not:
|
|
|
|
|
field: .inputs.skip
|
|
|
|
|
equals: true
|
|
|
|
|
"#;
|
|
|
|
|
let parsed: YamlWorkflow = serde_yaml::from_str(yaml).unwrap();
|
|
|
|
|
let step = &parsed.workflow.steps[0];
|
|
|
|
|
match step.when.as_ref().unwrap() {
|
|
|
|
|
YamlCondition::Combinator(c) => {
|
|
|
|
|
assert!(c.not.is_some());
|
|
|
|
|
}
|
|
|
|
|
_ => panic!("Expected Combinator with not"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_step_with_none_condition() {
|
|
|
|
|
let yaml = r#"
|
|
|
|
|
workflow:
|
|
|
|
|
id: none-cond-wf
|
|
|
|
|
version: 1
|
|
|
|
|
steps:
|
|
|
|
|
- name: deploy
|
|
|
|
|
type: shell
|
|
|
|
|
config:
|
|
|
|
|
run: deploy.sh
|
|
|
|
|
when:
|
|
|
|
|
none:
|
|
|
|
|
- field: .inputs.skip
|
|
|
|
|
equals: true
|
|
|
|
|
- field: .inputs.disabled
|
|
|
|
|
equals: true
|
|
|
|
|
"#;
|
|
|
|
|
let parsed: YamlWorkflow = serde_yaml::from_str(yaml).unwrap();
|
|
|
|
|
let step = &parsed.workflow.steps[0];
|
|
|
|
|
match step.when.as_ref().unwrap() {
|
|
|
|
|
YamlCondition::Combinator(c) => {
|
|
|
|
|
assert!(c.none.is_some());
|
|
|
|
|
assert_eq!(c.none.as_ref().unwrap().len(), 2);
|
|
|
|
|
}
|
|
|
|
|
_ => panic!("Expected Combinator with none"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_step_with_one_of_condition() {
|
|
|
|
|
let yaml = r#"
|
|
|
|
|
workflow:
|
|
|
|
|
id: one-of-wf
|
|
|
|
|
version: 1
|
|
|
|
|
steps:
|
|
|
|
|
- name: deploy
|
|
|
|
|
type: shell
|
|
|
|
|
config:
|
|
|
|
|
run: deploy.sh
|
|
|
|
|
when:
|
|
|
|
|
one_of:
|
|
|
|
|
- field: .inputs.mode
|
|
|
|
|
equals: fast
|
|
|
|
|
- field: .inputs.mode
|
|
|
|
|
equals: slow
|
|
|
|
|
"#;
|
|
|
|
|
let parsed: YamlWorkflow = serde_yaml::from_str(yaml).unwrap();
|
|
|
|
|
let step = &parsed.workflow.steps[0];
|
|
|
|
|
match step.when.as_ref().unwrap() {
|
|
|
|
|
YamlCondition::Combinator(c) => {
|
|
|
|
|
assert!(c.one_of.is_some());
|
|
|
|
|
assert_eq!(c.one_of.as_ref().unwrap().len(), 2);
|
|
|
|
|
}
|
|
|
|
|
_ => panic!("Expected Combinator with one_of"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_comparison_with_each_operator() {
|
|
|
|
|
// Test that each operator variant deserializes correctly.
|
|
|
|
|
let operators = vec![
|
|
|
|
|
("equals: 42", "equals"),
|
|
|
|
|
("not_equals: foo", "not_equals"),
|
|
|
|
|
("gt: 10", "gt"),
|
|
|
|
|
("gte: 10", "gte"),
|
|
|
|
|
("lt: 100", "lt"),
|
|
|
|
|
("lte: 100", "lte"),
|
|
|
|
|
("contains: needle", "contains"),
|
|
|
|
|
("is_null: true", "is_null"),
|
|
|
|
|
("is_not_null: true", "is_not_null"),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (op_yaml, op_name) in operators {
|
|
|
|
|
let yaml = format!(
|
|
|
|
|
r#"
|
|
|
|
|
workflow:
|
|
|
|
|
id: op-{op_name}
|
|
|
|
|
version: 1
|
|
|
|
|
steps:
|
|
|
|
|
- name: step1
|
|
|
|
|
type: shell
|
|
|
|
|
config:
|
|
|
|
|
run: echo hi
|
|
|
|
|
when:
|
|
|
|
|
field: .inputs.x
|
|
|
|
|
{op_yaml}
|
|
|
|
|
"#
|
|
|
|
|
);
|
|
|
|
|
let parsed: YamlWorkflow = serde_yaml::from_str(&yaml)
|
|
|
|
|
.unwrap_or_else(|e| panic!("Failed to parse operator {op_name}: {e}"));
|
|
|
|
|
let step = &parsed.workflow.steps[0];
|
|
|
|
|
assert!(
|
|
|
|
|
step.when.is_some(),
|
|
|
|
|
"Step should have when condition for operator {op_name}"
|
|
|
|
|
);
|
|
|
|
|
match step.when.as_ref().unwrap() {
|
|
|
|
|
YamlCondition::Comparison(_) => {}
|
|
|
|
|
_ => panic!("Expected Comparison for operator {op_name}"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_step_without_when_has_none() {
|
|
|
|
|
let yaml = r#"
|
|
|
|
|
workflow:
|
|
|
|
|
id: no-when-wf
|
|
|
|
|
version: 1
|
|
|
|
|
steps:
|
|
|
|
|
- name: step1
|
|
|
|
|
type: shell
|
|
|
|
|
config:
|
|
|
|
|
run: echo hi
|
|
|
|
|
"#;
|
|
|
|
|
let parsed: YamlWorkflow = serde_yaml::from_str(yaml).unwrap();
|
|
|
|
|
assert!(parsed.workflow.steps[0].when.is_none());
|
|
|
|
|
}
|