runkon_flow/
workflow_resolver_directory.rs1use 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}