1 // Copyright 2024 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 /// A vector of tuples representing wildcard patterns. 16 /// Each tuple contains a prefix string and a suffix string. 17 pub struct PatternVec { 18 patterns: Vec<(String, String)>, 19 } 20 21 impl PatternVec { 22 /// Creates a new PatternVec from a string containing semicolon (;) or comma (,) 23 /// separated wildcard patterns. new(pattern_list: impl Into<String>) -> PatternVec24 pub fn new(pattern_list: impl Into<String>) -> PatternVec { 25 let pattern_list = pattern_list.into(); 26 let patterns = if pattern_list.trim().is_empty() { 27 Vec::new() 28 } else { 29 pattern_list 30 .split([';', ',']) 31 // Splits a string at the first occurrence of '*', returning a tuple 32 // containing the prefix (before *) and suffix (after *). 33 // If no '*' is found, returns the entire string as prefix. 34 .map(|s| match s.find('*') { 35 Some(i) => (String::from(&s[..i]), String::from(&s[i + 1..])), 36 None => (String::from(s), String::new()), 37 }) 38 .collect() 39 }; 40 PatternVec { patterns } 41 } 42 43 /// Checks if a given string matches any of the patterns in the PatternVec. 44 /// A match occurs if the string starts with a pattern's prefix and ends with its suffix. matches(&self, s: &str) -> bool45 pub fn matches(&self, s: &str) -> bool { 46 self.patterns.iter().any(|(prefix, suffix)| s.starts_with(prefix) && s.ends_with(suffix)) 47 } 48 } 49 50 #[cfg(test)] 51 mod tests { 52 use super::*; 53 54 macro_rules! tuple_str { 55 ($a:expr, $b:expr) => { 56 (String::from($a), String::from($b)) 57 }; 58 } 59 60 #[test] test_new_empty_string()61 fn test_new_empty_string() { 62 let pattern_vec = PatternVec::new(""); 63 assert_eq!(pattern_vec.patterns.len(), 0); 64 } 65 66 #[test] test_new_single_pattern()67 fn test_new_single_pattern() { 68 let pattern_vec = PatternVec::new("*.example.com"); 69 assert_eq!(pattern_vec.patterns.len(), 1); 70 assert_eq!(pattern_vec.patterns[0], tuple_str!("", ".example.com")); 71 } 72 73 #[test] test_new_multiple_patterns()74 fn test_new_multiple_patterns() { 75 let pattern_vec = PatternVec::new("*.example.com;*.org"); 76 assert_eq!(pattern_vec.patterns.len(), 2); 77 assert_eq!(pattern_vec.patterns[0], tuple_str!("", ".example.com")); 78 assert_eq!(pattern_vec.patterns[1], tuple_str!("", ".org")); 79 } 80 81 #[test] test_matches_exact_match()82 fn test_matches_exact_match() { 83 let pattern_vec = PatternVec::new("example.com"); 84 assert!(pattern_vec.matches("example.com")); 85 } 86 87 #[test] test_matches_prefix_match()88 fn test_matches_prefix_match() { 89 let pattern_vec = PatternVec::new("*.google.com"); 90 assert!(pattern_vec.matches("foo.google.com")); 91 } 92 93 #[test] test_matches_suffix_match()94 fn test_matches_suffix_match() { 95 let pattern_vec = PatternVec::new("*.com"); 96 assert!(pattern_vec.matches("example.com")); 97 } 98 99 #[test] test_matches_no_match()100 fn test_matches_no_match() { 101 let pattern_vec = PatternVec::new("*.google.com"); 102 assert!(!pattern_vec.matches("example.org")); 103 } 104 105 #[test] test_matches_multiple_patterns()106 fn test_matches_multiple_patterns() { 107 let pattern_vec = PatternVec::new("*.example.com;*.org"); 108 assert!(pattern_vec.matches("some.example.com")); 109 assert!(pattern_vec.matches("another.org")); 110 } 111 112 #[test] test_matches_middle_wildcard()113 fn test_matches_middle_wildcard() { 114 let pattern_vec = PatternVec::new("some*.com"); 115 assert!(pattern_vec.matches("somemiddle.com")); 116 assert!(pattern_vec.matches("some.middle.com")); 117 assert!(pattern_vec.matches("some.middle.example.com")); 118 } 119 } 120