Skip to main content

runkon_flow/
workflow_resolver_directory.rs

1use std::path::PathBuf;
2use std::sync::Arc;
3
4use crate::dsl::parse_workflow_file;
5use crate::dsl::WorkflowDef;
6use crate::engine_error::EngineError;
7use crate::traits::workflow_resolver::WorkflowResolver;
8
9pub struct DirectoryWorkflowResolver {
10    root: PathBuf,
11}
12
13impl DirectoryWorkflowResolver {
14    pub fn new(root: impl Into<PathBuf>) -> Self {
15        Self { root: root.into() }
16    }
17}
18
19impl WorkflowResolver for DirectoryWorkflowResolver {
20    fn resolve(&self, name: &str) -> Result<Arc<WorkflowDef>, EngineError> {
21        if !name
22            .chars()
23            .all(|c| c.is_alphanumeric() || c == '-' || c == '_')
24        {
25            return Err(EngineError::WorkflowNotFound(name.to_string()));
26        }
27        let path = self.root.join(format!("{name}.wf"));
28        if !path.exists() {
29            return Err(EngineError::WorkflowNotFound(name.to_string()));
30        }
31        parse_workflow_file(&path)
32            .map(Arc::new)
33            .map_err(EngineError::Workflow)
34    }
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40
41    fn write_wf(dir: &std::path::Path, name: &str, content: &str) {
42        std::fs::write(dir.join(format!("{name}.wf")), content).unwrap();
43    }
44
45    #[test]
46    fn resolves_valid_workflow() {
47        let dir = tempfile::TempDir::new().unwrap();
48        write_wf(dir.path(), "deploy", "workflow deploy {\n}");
49        let resolver = DirectoryWorkflowResolver::new(dir.path());
50        let def = resolver.resolve("deploy").unwrap();
51        assert_eq!(def.name, "deploy");
52    }
53
54    #[test]
55    fn returns_not_found_for_missing_file() {
56        let dir = tempfile::TempDir::new().unwrap();
57        let resolver = DirectoryWorkflowResolver::new(dir.path());
58        let err = resolver.resolve("missing").unwrap_err();
59        assert!(matches!(err, EngineError::WorkflowNotFound(n) if n == "missing"));
60    }
61
62    #[test]
63    fn returns_not_found_for_invalid_chars_slash() {
64        let dir = tempfile::TempDir::new().unwrap();
65        let resolver = DirectoryWorkflowResolver::new(dir.path());
66        let err = resolver.resolve("foo/bar").unwrap_err();
67        assert!(matches!(err, EngineError::WorkflowNotFound(n) if n == "foo/bar"));
68    }
69
70    #[test]
71    fn returns_not_found_for_dot_dot() {
72        let dir = tempfile::TempDir::new().unwrap();
73        let resolver = DirectoryWorkflowResolver::new(dir.path());
74        let err = resolver.resolve("..").unwrap_err();
75        assert!(matches!(err, EngineError::WorkflowNotFound(_)));
76    }
77
78    #[test]
79    fn returns_not_found_for_name_with_spaces() {
80        let dir = tempfile::TempDir::new().unwrap();
81        let resolver = DirectoryWorkflowResolver::new(dir.path());
82        let err = resolver.resolve("foo bar").unwrap_err();
83        assert!(matches!(err, EngineError::WorkflowNotFound(_)));
84    }
85
86    #[test]
87    fn returns_workflow_error_for_invalid_content() {
88        let dir = tempfile::TempDir::new().unwrap();
89        write_wf(dir.path(), "bad", "this is not valid wf syntax @@@@");
90        let resolver = DirectoryWorkflowResolver::new(dir.path());
91        let err = resolver.resolve("bad").unwrap_err();
92        assert!(matches!(err, EngineError::Workflow(_)));
93    }
94
95    #[test]
96    fn accepts_hyphens_and_underscores_in_name() {
97        let dir = tempfile::TempDir::new().unwrap();
98        write_wf(dir.path(), "my-flow_v2", "workflow my-flow_v2 {\n}");
99        let resolver = DirectoryWorkflowResolver::new(dir.path());
100        let def = resolver.resolve("my-flow_v2").unwrap();
101        assert_eq!(def.name, "my-flow_v2");
102    }
103}