1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
use regex::Error;
use crate::utils::build_regex;

/// Core regular expression match methods
pub trait PatternMatch {
  /// Apply a regular expression match on the current string
  /// If the regex doesn't compile it will return an error
  fn pattern_match_result(&self, pattern: &str, case_insensitive: bool) -> Result<bool, Error>;

  /// Apply a regular expression match on the current string with a boolean case_insensitive flag
  /// NB: If the regex doesn't compile it will return false
  fn pattern_match(&self, pattern: &str, case_insensitive: bool) -> bool {
    if let Ok(matched) = self.pattern_match_result(pattern, case_insensitive){
      matched
    } else {
      false
    }
  }

  /// if the pattern does not match the source string or the regex fails
  fn pattern_match_ci(&self, pattern: &str) -> bool {
    self.pattern_match(pattern, true)
  }

  /// Simple case-sensitive regex-compatible match method that will return false 
  /// if the pattern does not match the source string or the regex fails
  fn pattern_match_cs(&self, pattern: &str) -> bool {
    self.pattern_match(pattern, false)
  }

}

/// Implement regular expression match and replace methods for str and owned String
impl PatternMatch for str {

  ///
  /// Simple regex-compatible match method that will return an optional boolean 
  /// - Some(true) means the regex is valid and the string matches
  /// - Some(false) means the regex is valid and the string does not match
  /// - None means the regex is not valid and can this not be evaluated
  /// Only the pattern_match_result needs to be implemented
  fn pattern_match_result(&self, pattern: &str, case_insensitive: bool) -> Result<bool, Error> {
    match build_regex(pattern, case_insensitive) {
      Ok(re)  => Ok(re.is_match(self)),
      Err(error) => Err(error)
    }
  }
}

/// Boolean methods to match a pattern within an array of strings
impl PatternMatch for [&str] {
  /// The regex is only compiled when validating an array of strings
  fn pattern_match_result(&self, pattern: &str, case_insensitive: bool) -> Result<bool, Error> {
    match build_regex(pattern, case_insensitive) {
      Ok(re) => Ok(self.into_iter().any(|segment| re.is_match(*segment))),
      Err(error) => Err(error)
    }
  }
}

/// Boolean methods to match a pattern within an array of strings
impl PatternMatch for [String] {
  /// The regex is only compiled when validating an array of strings
  fn pattern_match_result(&self, pattern: &str, case_insensitive: bool) -> Result<bool, Error> {
    match build_regex(pattern, case_insensitive) {
      Ok(re) => Ok(self.into_iter().any(|segment| re.is_match(segment))),
      Err(error) => Err(error)
    }
  }
}

/// Pattern methods for arrays or vectors only, return vectors of booleans matching each input string
pub trait PatternMatches {

  /// Returns result with a vector of tuples with matched status and string slice
  /// for an array or vector of strings with a case-insensitive flag
  /// or an error if the regex does not compile
  fn pattern_matched_pairs_result(&self, pattern: &str, case_insensitive: bool) -> Result<Vec<(bool, &str)>, Error>;


  /// Return a default vector of paired tuples if the regular expression fails in pattern_matched_pairs or pattern_matches
  /// This has to implemented separately for all other derived methods returning vectors to work correctly
  fn pattern_matched_pairs_default(&self) -> Vec<(bool, &str)>;

  /// Returns a vector of tuples with matched status and string slice
  /// for an array or vector of strings with a case-insensitive flag
  /// If the regular expression fails all items will be false
  fn pattern_matched_pairs(&self, pattern: &str, case_insensitive: bool) -> Vec<(bool, &str)> {
    match self.pattern_matched_pairs_result(pattern, case_insensitive) {
      Ok(results) => results,
      Err(_error) => self.pattern_matched_pairs_default()
    }
  }

  /// Returns result with a vector of boolean matches for an array or vector of strings with case-insensitive flag
  /// or an error if the regex does not compile
  fn pattern_matches_result(&self, pattern: &str, case_insensitive: bool) -> Result<Vec<bool>, Error> {
    match self.pattern_matched_pairs_result(pattern, case_insensitive) {
      Ok(items) => Ok(items.into_iter().map(|(result, _item)| result).collect::<Vec<bool>>()),
      Err(error) => Err(error)
    }
  }

  /// Returns a filtered vector of matched string slices (&str) with case-insensitive flag
  fn pattern_matches_filtered(&self, pattern: &str, case_insensitive: bool) -> Vec<&str> {
    self.pattern_matched_pairs(pattern, case_insensitive).into_iter().filter(|(is_matched, _item)| *is_matched).map(|(_is_matched, item)| item).collect()
  }

  /// Returns a filtered vector of matched string slices (&str) in case-insensitive mode
  fn pattern_matches_filtered_ci(&self, pattern: &str) -> Vec<&str> {
    self.pattern_matched_pairs(pattern, true).into_iter().filter(|(is_matched, _item)| *is_matched).map(|(_is_matched, item)| item).collect()
  }

  /// Returns a filtered vector of matched string slices (&str) in case-sensitive mode
  fn pattern_matches_filtered_cs(&self, pattern: &str) -> Vec<&str> {
    self.pattern_matched_pairs(pattern, false).into_iter().filter(|(is_matched, _item)| *is_matched).map(|(_is_matched, item)| item).collect()
  }


  /// Returns vector of boolean matches for an array or vector of strings with case-insensitive flag
  /// must be reimplemented from pattern_matches_result owing to trait bound constraints on unsized arrays
  fn pattern_matches(&self, pattern: &str, case_insensitive: bool) -> Vec<bool> {
    self.pattern_matched_pairs(pattern, case_insensitive).into_iter().map(|(matched, _item)| matched).collect()
  }
  
  /// Returns vector of boolean matches for an array or vector of strings in case-insensitive mode
  fn pattern_matches_ci(&self, pattern: &str) -> Vec<bool> {
    self.pattern_matches(pattern, true)
  }

  /// Returns vector of boolean matches for an array or vector of strings in case-sensitive mode
  fn pattern_matches_cs(&self, pattern: &str) -> Vec<bool> {
    self.pattern_matches(pattern, false)
  }
}

/// Multiple match methods for arrays or vectors of &str values
impl PatternMatches for [&str] {

  /// Returns an Ok result with a vector of boolean matches for an array or vector of strings with a case-insensitive flag
  /// and an error only if the regex fails to compile.
  fn pattern_matched_pairs_result(&self, pattern: &str, case_insensitive: bool) -> Result<Vec<(bool, &str)>, Error> {
    match build_regex(pattern, case_insensitive) {
      Ok(re) => Ok(self.into_iter().map(|segment| (re.is_match(*segment), *segment)).collect::<Vec<(bool, &str)>>()),
      Err(error) => Err(error)
    }
  }

  // Implement default vector of (bool, &str) results for [&str]
  fn pattern_matched_pairs_default(&self) -> Vec<(bool, &str)> {
    self.into_iter().map(|item| (false, *item)).collect()
  }

}

/// Multiple match methods for arrays or vectors of strings
/// Implemented separately because of irresolvable Iterator trait bounds rules in [Rust 2018](https://doc.rust-lang.org/stable/edition-guide/rust-2021/IntoIterator-for-arrays.html)
/// and because both String and &str are normalised to vectors with string references in the return types
impl PatternMatches for [String] {

  /// Returns an Ok result with a vector of boolean matches for an array or vector of strings with a case-insensitive flag
  /// and an error only if the regex fails to compile.
  fn pattern_matched_pairs_result(&self, pattern: &str, case_insensitive: bool) -> Result<Vec<(bool, &str)>, Error> {
    match build_regex(pattern, case_insensitive) {
      Ok(re) => Ok(self.into_iter().map(|segment| (re.is_match(segment), segment.as_str())).collect::<Vec<(bool, &str)>>()),
      Err(error) => Err(error)
    }
  }

  // Implement default vector of (bool, &str) results for [String]
  fn pattern_matched_pairs_default(&self) -> Vec<(bool, &str)> {
    self.into_iter().map(|item| (false, item.as_str())).collect()
  }
  
}