minion_engine/workflow/
parser.rs1use std::path::Path;
2
3use anyhow::{Context, Result};
4
5use super::schema::WorkflowDef;
6
7pub fn parse_file(path: &Path) -> Result<WorkflowDef> {
9 let content =
10 std::fs::read_to_string(path).with_context(|| format!("Cannot read {}", path.display()))?;
11 parse_str(&content)
12}
13
14pub fn parse_str(yaml: &str) -> Result<WorkflowDef> {
16 let workflow: WorkflowDef =
17 serde_yaml::from_str(yaml).context("Failed to parse workflow YAML")?;
18 Ok(workflow)
19}
20
21#[cfg(test)]
22mod tests {
23 use super::*;
24
25 #[test]
26 fn parse_minimal_workflow() {
27 let yaml = r#"
28name: test
29steps:
30 - name: hello
31 type: cmd
32 run: "echo hello"
33"#;
34 let wf = parse_str(yaml).unwrap();
35 assert_eq!(wf.name, "test");
36 assert_eq!(wf.steps.len(), 1);
37 assert_eq!(wf.steps[0].name, "hello");
38 }
39
40 #[test]
41 fn parse_workflow_with_scopes() {
42 let yaml = r#"
43name: test
44scopes:
45 my_scope:
46 steps:
47 - name: inner
48 type: cmd
49 run: "echo inner"
50 outputs: "{{ steps.inner.stdout }}"
51steps:
52 - name: outer
53 type: repeat
54 scope: my_scope
55 max_iterations: 3
56"#;
57 let wf = parse_str(yaml).unwrap();
58 assert_eq!(wf.scopes.len(), 1);
59 assert!(wf.scopes.contains_key("my_scope"));
60 assert_eq!(wf.scopes["my_scope"].steps.len(), 1);
61 }
62
63 #[test]
64 fn parse_invalid_yaml_fails() {
65 let yaml = "this is not: [valid yaml: {";
66 assert!(parse_str(yaml).is_err());
67 }
68
69 #[test]
70 fn parse_missing_required_fields_fails() {
71 let yaml = r#"
72description: "missing name and steps"
73"#;
74 assert!(parse_str(yaml).is_err());
75 }
76
77 #[test]
78 fn parse_fix_issue_yaml() {
79 let path = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/workflows/fix-issue.yaml"));
80 let wf = parse_file(path).expect("fix-issue.yaml should parse without errors");
81 assert_eq!(wf.name, "fix-github-issue");
82 assert!(wf.scopes.contains_key("lint_fix"), "lint_fix scope missing");
83 assert!(wf.scopes.contains_key("test_fix"), "test_fix scope missing");
84 assert!(!wf.steps.is_empty(), "steps should not be empty");
85 let lint_fix = &wf.scopes["lint_fix"];
87 assert_eq!(lint_fix.steps.len(), 3);
88 let test_fix = &wf.scopes["test_fix"];
89 assert_eq!(test_fix.steps.len(), 3);
90 }
91}