| // Copyright 2024 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| /// A vector of tuples representing wildcard patterns. |
| /// Each tuple contains a prefix string and a suffix string. |
| pub struct PatternVec { |
| patterns: Vec<(String, String)>, |
| } |
| |
| impl PatternVec { |
| /// Creates a new PatternVec from a string containing semicolon (;) or comma (,) |
| /// separated wildcard patterns. |
| pub fn new(pattern_list: impl Into<String>) -> PatternVec { |
| let pattern_list = pattern_list.into(); |
| let patterns = if pattern_list.trim().is_empty() { |
| Vec::new() |
| } else { |
| pattern_list |
| .split([';', ',']) |
| // Splits a string at the first occurrence of '*', returning a tuple |
| // containing the prefix (before *) and suffix (after *). |
| // If no '*' is found, returns the entire string as prefix. |
| .map(|s| match s.find('*') { |
| Some(i) => (String::from(&s[..i]), String::from(&s[i + 1..])), |
| None => (String::from(s), String::new()), |
| }) |
| .collect() |
| }; |
| PatternVec { patterns } |
| } |
| |
| /// Checks if a given string matches any of the patterns in the PatternVec. |
| /// A match occurs if the string starts with a pattern's prefix and ends with its suffix. |
| pub fn matches(&self, s: &str) -> bool { |
| self.patterns.iter().any(|(prefix, suffix)| s.starts_with(prefix) && s.ends_with(suffix)) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| macro_rules! tuple_str { |
| ($a:expr, $b:expr) => { |
| (String::from($a), String::from($b)) |
| }; |
| } |
| |
| #[test] |
| fn test_new_empty_string() { |
| let pattern_vec = PatternVec::new(""); |
| assert_eq!(pattern_vec.patterns.len(), 0); |
| } |
| |
| #[test] |
| fn test_new_single_pattern() { |
| let pattern_vec = PatternVec::new("*.example.com"); |
| assert_eq!(pattern_vec.patterns.len(), 1); |
| assert_eq!(pattern_vec.patterns[0], tuple_str!("", ".example.com")); |
| } |
| |
| #[test] |
| fn test_new_multiple_patterns() { |
| let pattern_vec = PatternVec::new("*.example.com;*.org"); |
| assert_eq!(pattern_vec.patterns.len(), 2); |
| assert_eq!(pattern_vec.patterns[0], tuple_str!("", ".example.com")); |
| assert_eq!(pattern_vec.patterns[1], tuple_str!("", ".org")); |
| } |
| |
| #[test] |
| fn test_matches_exact_match() { |
| let pattern_vec = PatternVec::new("example.com"); |
| assert!(pattern_vec.matches("example.com")); |
| } |
| |
| #[test] |
| fn test_matches_prefix_match() { |
| let pattern_vec = PatternVec::new("*.google.com"); |
| assert!(pattern_vec.matches("foo.google.com")); |
| } |
| |
| #[test] |
| fn test_matches_suffix_match() { |
| let pattern_vec = PatternVec::new("*.com"); |
| assert!(pattern_vec.matches("example.com")); |
| } |
| |
| #[test] |
| fn test_matches_no_match() { |
| let pattern_vec = PatternVec::new("*.google.com"); |
| assert!(!pattern_vec.matches("example.org")); |
| } |
| |
| #[test] |
| fn test_matches_multiple_patterns() { |
| let pattern_vec = PatternVec::new("*.example.com;*.org"); |
| assert!(pattern_vec.matches("some.example.com")); |
| assert!(pattern_vec.matches("another.org")); |
| } |
| |
| #[test] |
| fn test_matches_middle_wildcard() { |
| let pattern_vec = PatternVec::new("some*.com"); |
| assert!(pattern_vec.matches("somemiddle.com")); |
| assert!(pattern_vec.matches("some.middle.com")); |
| assert!(pattern_vec.matches("some.middle.example.com")); |
| } |
| } |