Repository: cristianoliveira/funzzy (language: rust)

Issue:
feat: validate glob pattern on load
Today if a glob pattern is invalid the watcher only validates once it is used

Here: https://github.com/cristianoliveira/funzzy/blob/1d6855e2743b2e9ae02669c83a787e02bbdd22b7/src/rules.rs#L62


Discussion / hints:

Fix patch:
diff --git a/src/main.rs b/src/main.rs
index 05f968f..824f678 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -105,6 +105,10 @@ fn main() {
                         Err(err) => error("Failed to get rules from stdin", err),
                     };
 
+                    if let Err(err) = rules::validate_rules(&watch_rules) {
+                        error("Invalid config file.", err);
+                    }
+
                     execute_watch_command(Watches::new(watch_rules), args);
                 }
                 Err(err) => error("Failed to read stdin", err),
@@ -134,6 +138,10 @@ fn main() {
                 }
             };
 
+            if let Err(err) = rules::validate_rules(&rules) {
+                error("Invalid config file.", err);
+            }
+
             if !args.flag_target.is_empty() {
                 let filtered = rules
                     .iter()
diff --git a/src/rules.rs b/src/rules.rs
index 2d93f27..e1c4a23 100644
--- a/src/rules.rs
+++ b/src/rules.rs
@@ -43,9 +43,6 @@ impl Rules {
     }
 
     pub fn from(yaml: &Yaml) -> Self {
-        yaml::validate(yaml, "run");
-        yaml::validate(yaml, "change");
-
         Rules {
             name: yaml::extract_strings(&yaml["name"])[0].clone(),
             commands: yaml::extract_strings(&yaml["run"]),
@@ -140,6 +137,57 @@ impl Rules {
         .collect::<Vec<String>>()
         .join("\n")
     }
+
+    pub fn validate(&self) -> Result<(), String> {
+        let name = if self.name.is_empty() {
+            "_unnamed_".to_owned()
+        } else {
+            self.name.clone()
+        };
+
+        if self.commands().len() == 0 {
+            return Err(format!(
+                "Rule '{}' contains no command to run. Empty 'run' property.",
+                name
+            ));
+        }
+
+        for watch_pattern in self.watch_patterns() {
+            match Pattern::new(&watch_pattern) {
+                Ok(_) => (),
+                Err(err) => {
+                    return Err(vec![
+                        format!(
+                            "Rule '{}' contains an invalid `change` glob pattern '{}'.",
+                            name, watch_pattern
+                        ),
+                        format!("  {}", err),
+                        "  Read more: https://en.wikipedia.org/wiki/Glob_(programming)".to_owned(),
+                    ]
+                    .join("\n"));
+                }
+            }
+        }
+
+        for ignore_pattern in self.ignore_patterns.clone() {
+            match Pattern::new(&ignore_pattern) {
+                Ok(_) => (),
+                Err(err) => {
+                    return Err(vec![
+                        format!(
+                            "Rule '{}' contains an invalid `ignore` glob pattern '{}'.",
+                            name, ignore_pattern
+                        ),
+                        format!("  {}", err),
+                        "  Read more: https://en.wikipedia.org/wiki/Glob_(programming)".to_owned(),
+                    ]
+                    .join("\n"));
+                }
+            }
+        }
+
+        Ok(())
+    }
 }
 
 pub fn commands(rules: Vec<Rules>) -> Vec<String> {
@@ -333,6 +381,16 @@ pub fn format_rules(rule: &Vec<Rules>) -> String {
     formatted_rules
 }
 
+pub fn validate_rules(rule: &Vec<Rules>) -> Result<(), String> {
+    for rule in rule {
+        if let Err(err) = rule.validate() {
+            return Err(err);
+        }
+    }
+
+    Ok(())
+}
+
 #[cfg(test)]
 mod tests {
     extern crate yaml_rust;
@@ -480,30 +538,6 @@ mod tests {
         assert_eq!(vec!["cargo tests"], result);
     }
 
-    #[test]
-    #[should_panic]
-    fn it_validates_the_run_key() {
-        let file_content = "
-        - name: my source
-          change: 'src/**'
-          ignore: ['src/test/**', 'src/tmp/**']
-        ";
-        let content = YamlLoader::load_from_str(file_content).unwrap();
-        Rules::from(&content[0][0]);
-    }
-
-    #[test]
-    #[should_panic]
-    fn it_validates_the_when_change_key() {
-        let file_content = "
-        - name: my source
-          run: make test
-          ignore: ['src/test/**', 'src/tmp/**']
-        ";
-        let content = YamlLoader::load_from_str(file_content).unwrap();
-        Rules::from(&content[0][0]);
-    }
-
     fn get_absolute_path(path: &str) -> String {
         let mut absolute_path = current_dir().unwrap();
         absolute_path.push(path);
@@ -675,4 +709,68 @@ mod tests {
             .join("\n")
         );
     }
+
+    #[test]
+    fn it_validates_the_given_glob_patterns_paths() {
+        let rules_yaml = from_yaml(
+            "
+        - name: this is valid
+          run: 'cargo tests'
+          change: 
+            - '**/*'
+            - '**/*.go'
+          ignore: 
+            - '**/*.log'
+
+        - name: this is an invalid pattern
+          run: 'echo invalid'
+          change: 
+            - '**/foo_**.go'
+          ignore: 
+            - '**/*.log'
+
+        - name: this is an invalid pattern 2
+          run: 'echo invalid'
+          change: 
+            - '**/*.go'
+          ignore: 
+            - '**/**.*'
+
+        - name: rules must have at least one command
+          change: 
+            - '**/*.go'
+        ",
+        );
+        assert!(rules_yaml.is_ok());
+
+        let rules = rules_yaml.unwrap();
+        let first_rule = &rules[0];
+        assert!(first_rule.validate().is_ok());
+
+        // The invalid pattern rules
+        let second_rule = &rules[1];
+        assert!(second_rule.validate().is_err());
+        assert_eq!(
+            second_rule.validate().err().unwrap(),
+            "Rule 'this is an invalid pattern' contains an invalid `change` glob pattern '**/foo_**.go'.
+  Pattern syntax error near position 6: recursive wildcards must form a single path component
+  Read more: https://en.wikipedia.org/wiki/Glob_(programming)"
+        );
+
+        let third_rule = &rules[2];
+        assert!(third_rule.validate().is_err());
+        assert_eq!(
+            third_rule.validate().err().unwrap(),
+            "Rule 'this is an invalid pattern 2' contains an invalid `ignore` glob pattern '**/**.*'.
+  Pattern syntax error near position 5: recursive wildcards must form a single path component
+  Read more: https://en.wikipedia.org/wiki/Glob_(programming)"
+        );
+
+        let fourth_rule = &rules[3];
+        assert!(fourth_rule.validate().is_err());
+        assert_eq!(
+            fourth_rule.validate().err().unwrap(),
+            "Rule 'rules must have at least one command' contains no command to run. Empty 'run' property."
+        );
+    }
 }
diff --git a/src/yaml.rs b/src/yaml.rs
index f6dfbec..bf7e530 100644
--- a/src/yaml.rs
+++ b/src/yaml.rs
@@ -25,13 +25,3 @@ pub fn extract_bool(yaml: &Yaml) -> bool {
         _ => false,
     }
 }
-
-pub fn validate(yaml: &Yaml, key: &str) {
-    if yaml[key].is_badvalue() {
-        println!(
-            "Config file has an invalid format. (Key {} was not found)",
-            key
-        );
-        panic!("Interrupted due the previou error.")
-    }
-}


Test patch: