1 // Copyright 2022 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 //      http://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 use crate::description::Description;
16 use crate::matcher::{Matcher, MatcherResult};
17 use regex::Regex;
18 use std::fmt::Debug;
19 use std::marker::PhantomData;
20 use std::ops::Deref;
21 
22 /// Matches a string containing a substring which matches the given regular
23 /// expression.
24 ///
25 /// Both the actual value and the expected regular expression may be either a
26 /// `String` or a string reference.
27 ///
28 /// ```
29 /// # use googletest::prelude::*;
30 /// # fn should_pass_1() -> Result<()> {
31 /// verify_that!("Some value", contains_regex("S.*e"))?;  // Passes
32 /// #     Ok(())
33 /// # }
34 /// # fn should_fail() -> Result<()> {
35 /// verify_that!("Another value", contains_regex("Some"))?;   // Fails
36 /// #     Ok(())
37 /// # }
38 /// # fn should_pass_2() -> Result<()> {
39 /// verify_that!("Some value".to_string(), contains_regex("v.*e"))?;   // Passes
40 /// verify_that!("Some value", contains_regex("v.*e".to_string()))?;   // Passes
41 /// #     Ok(())
42 /// # }
43 /// # should_pass_1().unwrap();
44 /// # should_fail().unwrap_err();
45 /// # should_pass_2().unwrap();
46 /// ```
47 ///
48 /// Panics if the given `pattern` is not a syntactically valid regular
49 /// expression.
50 // N.B. This returns the concrete type rather than an impl Matcher so that it
51 // can act simultaneously as a Matcher<str> and a Matcher<String>. Otherwise the
52 // compiler treats it as a Matcher<str> only and the code
53 //   verify_that!("Some value".to_string(), contains_regex(".*value"))?;
54 // doesn't compile.
contains_regex<ActualT: ?Sized, PatternT: Deref<Target = str>>( pattern: PatternT, ) -> ContainsRegexMatcher<ActualT>55 pub fn contains_regex<ActualT: ?Sized, PatternT: Deref<Target = str>>(
56     pattern: PatternT,
57 ) -> ContainsRegexMatcher<ActualT> {
58     ContainsRegexMatcher {
59         regex: Regex::new(pattern.deref()).unwrap(),
60         phantom: Default::default(),
61     }
62 }
63 
64 /// A matcher matching a string-like type containing a substring matching a
65 /// given regular expression.
66 ///
67 /// Intended only to be used from the function [`contains_regex`] only.
68 /// Should not be referenced by code outside this library.
69 pub struct ContainsRegexMatcher<ActualT: ?Sized> {
70     regex: Regex,
71     phantom: PhantomData<ActualT>,
72 }
73 
74 impl<ActualT: AsRef<str> + Debug + ?Sized> Matcher for ContainsRegexMatcher<ActualT> {
75     type ActualT = ActualT;
76 
matches(&self, actual: &ActualT) -> MatcherResult77     fn matches(&self, actual: &ActualT) -> MatcherResult {
78         self.regex.is_match(actual.as_ref()).into()
79     }
80 
describe(&self, matcher_result: MatcherResult) -> Description81     fn describe(&self, matcher_result: MatcherResult) -> Description {
82         match matcher_result {
83             MatcherResult::Match => {
84                 format!("contains the regular expression {:#?}", self.regex.as_str()).into()
85             }
86             MatcherResult::NoMatch => {
87                 format!("doesn't contain the regular expression {:#?}", self.regex.as_str()).into()
88             }
89         }
90     }
91 }
92 
93 #[cfg(test)]
94 mod tests {
95     use super::{contains_regex, ContainsRegexMatcher};
96     use crate::matcher::{Matcher, MatcherResult};
97     use crate::prelude::*;
98 
99     #[test]
contains_regex_matches_string_reference_with_pattern() -> Result<()>100     fn contains_regex_matches_string_reference_with_pattern() -> Result<()> {
101         let matcher = contains_regex("S.*val");
102 
103         let result = matcher.matches("Some value");
104 
105         verify_that!(result, eq(MatcherResult::Match))
106     }
107 
108     #[test]
contains_regex_does_not_match_string_without_pattern() -> Result<()>109     fn contains_regex_does_not_match_string_without_pattern() -> Result<()> {
110         let matcher = contains_regex("Another");
111 
112         let result = matcher.matches("Some value");
113 
114         verify_that!(result, eq(MatcherResult::NoMatch))
115     }
116 
117     #[test]
contains_regex_matches_owned_string_with_pattern() -> Result<()>118     fn contains_regex_matches_owned_string_with_pattern() -> Result<()> {
119         let matcher = contains_regex("value");
120 
121         let result = matcher.matches(&"Some value".to_string());
122 
123         verify_that!(result, eq(MatcherResult::Match))
124     }
125 
126     #[test]
contains_regex_matches_string_reference_with_owned_string() -> Result<()>127     fn contains_regex_matches_string_reference_with_owned_string() -> Result<()> {
128         let matcher = contains_regex("value");
129 
130         let result = matcher.matches("Some value");
131 
132         verify_that!(result, eq(MatcherResult::Match))
133     }
134 
135     #[test]
verify_that_works_with_owned_string() -> Result<()>136     fn verify_that_works_with_owned_string() -> Result<()> {
137         verify_that!("Some value".to_string(), contains_regex("value"))
138     }
139 
140     #[test]
contains_regex_displays_quoted_debug_of_pattern() -> Result<()>141     fn contains_regex_displays_quoted_debug_of_pattern() -> Result<()> {
142         let matcher: ContainsRegexMatcher<&str> = contains_regex("\n");
143 
144         verify_that!(
145             Matcher::describe(&matcher, MatcherResult::Match),
146             displays_as(eq("contains the regular expression \"\\n\""))
147         )
148     }
149 }
150