use dataflow_rs::{MAX_GROUP_DEPTH, StepKind, walk_authored_steps};
use serde_json::Value;
pub use dataflow_rs::is_group;
pub const MAX_STEP_DEPTH: usize = MAX_GROUP_DEPTH;
#[derive(Debug, Default)]
pub struct Steps<'a> {
pub tasks: Vec<(String, &'a Value)>,
pub groups: Vec<(String, &'a Value)>,
pub too_deep: Vec<String>,
}
pub fn walk_steps(tasks: &Value) -> Steps<'_> {
let mut out = Steps::default();
for step in walk_authored_steps(tasks) {
match step.kind {
StepKind::Leaf => out.tasks.push((step.path, step.node)),
StepKind::Group => out.groups.push((step.path, step.node)),
StepKind::TooDeep => out.too_deep.push(step.path),
}
}
out
}
pub fn leaf_tasks(tasks: &Value) -> Vec<&Value> {
walk_authored_steps(tasks)
.filter(|step| step.kind == StepKind::Leaf)
.map(|step| step.node)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn task(id: &str) -> Value {
json!({"id": id, "name": id, "function": {"name": "map", "input": {"mappings": []}}})
}
#[test]
fn a_flat_array_yields_its_tasks_in_order() {
let tasks = json!([task("a"), task("b")]);
let steps = walk_steps(&tasks);
assert!(steps.groups.is_empty());
assert_eq!(
steps
.tasks
.iter()
.map(|(p, _)| p.as_str())
.collect::<Vec<_>>(),
["tasks[0]", "tasks[1]"]
);
}
#[test]
fn a_group_yields_its_members_with_addressable_paths() {
let tasks = json!([
task("first"),
{"id": "guard", "condition": true, "terminal": true, "tasks": [
task("inner_a"),
task("inner_b")
]},
task("last")
]);
let steps = walk_steps(&tasks);
assert_eq!(
steps
.tasks
.iter()
.map(|(p, _)| p.as_str())
.collect::<Vec<_>>(),
[
"tasks[0]",
"tasks[1].tasks[0]",
"tasks[1].tasks[1]",
"tasks[2]"
],
"document order, with the path that addresses each task"
);
assert_eq!(steps.groups.len(), 1);
assert_eq!(steps.groups[0].0, "tasks[1]");
}
#[test]
fn groups_nest() {
let tasks = json!([
{"id": "outer", "tasks": [
{"id": "inner", "tasks": [task("deep")]}
]}
]);
let steps = walk_steps(&tasks);
assert_eq!(steps.tasks.len(), 1);
assert_eq!(steps.tasks[0].0, "tasks[0].tasks[0].tasks[0]");
assert_eq!(steps.groups.len(), 2, "both levels are groups");
assert!(steps.too_deep.is_empty());
}
#[test]
fn nesting_past_the_engine_limit_is_reported() {
let mut inner = json!([task("leaf")]);
for i in 0..MAX_STEP_DEPTH + 2 {
inner = json!([{"id": format!("g{i}"), "tasks": inner}]);
}
let steps = walk_steps(&inner);
assert!(
!steps.too_deep.is_empty(),
"a tree past the limit must be reported, not silently truncated"
);
}
#[test]
fn the_deepest_nesting_the_parser_accepts_is_not_reported() {
let mut inner = json!([task("leaf")]);
for i in 0..MAX_STEP_DEPTH {
inner = json!([{"id": format!("g{i}"), "tasks": inner}]);
}
assert!(
walk_steps(&inner).too_deep.is_empty(),
"{MAX_STEP_DEPTH} levels of nesting is what the parser accepts"
);
assert!(
serde_json::from_value::<dataflow_rs::Workflow>(json!({
"id": "w", "name": "w", "condition": true, "tasks": inner,
}))
.is_ok(),
"and the parser agrees, which is the whole claim"
);
}
#[test]
fn a_task_missing_its_function_is_still_a_task() {
let tasks = json!([{"id": "broken", "name": "broken"}]);
let steps = walk_steps(&tasks);
assert_eq!(steps.tasks.len(), 1);
assert!(steps.groups.is_empty());
}
#[test]
fn leaf_tasks_sees_exactly_what_walk_steps_sees() {
let mut deep = json!([task("leaf")]);
for i in 0..MAX_STEP_DEPTH + 2 {
deep = json!([{"id": format!("g{i}"), "tasks": deep}]);
}
let cases = [
json!([task("a"), task("b")]),
json!([task("first"), {"id": "guard", "tasks": [task("x"), task("y")]}, task("last")]),
json!([{"id": "outer", "tasks": [{"id": "inner", "tasks": [task("deep")]}]}]),
json!([{"id": "broken", "name": "broken"}]),
json!([{"id": "empty", "tasks": []}]),
json!({"not": "an array"}),
deep,
];
for tasks in cases {
let expected: Vec<&Value> = walk_steps(&tasks)
.tasks
.into_iter()
.map(|(_, t)| t)
.collect();
assert_eq!(leaf_tasks(&tasks), expected, "disagreement on {tasks}");
}
}
}