Skip to main content

minion_engine/workflow/
parser.rs

1use std::path::Path;
2
3use anyhow::{Context, Result};
4
5use super::schema::WorkflowDef;
6
7/// Parse a YAML file into a WorkflowDef
8pub 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
14/// Parse a YAML string into a WorkflowDef
15pub 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        // Verify scopes have expected steps
86        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}