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