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::{
16     description::Description,
17     matcher::{Matcher, MatcherResult},
18 };
19 use std::{fmt::Debug, marker::PhantomData};
20 
21 /// Matches an `Option` containing a value matched by `inner`.
22 ///
23 /// ```
24 /// # use googletest::prelude::*;
25 /// # fn should_pass() -> Result<()> {
26 /// verify_that!(Some("Some value"), some(eq("Some value")))?;  // Passes
27 /// #     Ok(())
28 /// # }
29 /// # fn should_fail_1() -> Result<()> {
30 /// verify_that!(None::<&str>, some(eq("Some value")))?;   // Fails
31 /// #     Ok(())
32 /// # }
33 /// # fn should_fail_2() -> Result<()> {
34 /// verify_that!(Some("Some value"), some(eq("Some other value")))?;   // Fails
35 /// #     Ok(())
36 /// # }
37 /// # should_pass().unwrap();
38 /// # should_fail_1().unwrap_err();
39 /// # should_fail_2().unwrap_err();
40 /// ```
some<T: Debug>(inner: impl Matcher<ActualT = T>) -> impl Matcher<ActualT = Option<T>>41 pub fn some<T: Debug>(inner: impl Matcher<ActualT = T>) -> impl Matcher<ActualT = Option<T>> {
42     SomeMatcher { inner, phantom: Default::default() }
43 }
44 
45 struct SomeMatcher<T, InnerMatcherT> {
46     inner: InnerMatcherT,
47     phantom: PhantomData<T>,
48 }
49 
50 impl<T: Debug, InnerMatcherT: Matcher<ActualT = T>> Matcher for SomeMatcher<T, InnerMatcherT> {
51     type ActualT = Option<T>;
52 
matches(&self, actual: &Option<T>) -> MatcherResult53     fn matches(&self, actual: &Option<T>) -> MatcherResult {
54         actual.as_ref().map(|v| self.inner.matches(v)).unwrap_or(MatcherResult::NoMatch)
55     }
56 
explain_match(&self, actual: &Option<T>) -> Description57     fn explain_match(&self, actual: &Option<T>) -> Description {
58         match (self.matches(actual), actual) {
59             (_, Some(t)) => {
60                 Description::new().text("which has a value").nested(self.inner.explain_match(t))
61             }
62             (_, None) => "which is None".into(),
63         }
64     }
65 
describe(&self, matcher_result: MatcherResult) -> Description66     fn describe(&self, matcher_result: MatcherResult) -> Description {
67         match matcher_result {
68             MatcherResult::Match => {
69                 format!("has a value which {}", self.inner.describe(MatcherResult::Match)).into()
70             }
71             MatcherResult::NoMatch => format!(
72                 "is None or has a value which {}",
73                 self.inner.describe(MatcherResult::NoMatch)
74             )
75             .into(),
76         }
77     }
78 }
79 
80 #[cfg(test)]
81 mod tests {
82     use super::some;
83     use crate::matcher::{Matcher, MatcherResult};
84     use crate::prelude::*;
85     use indoc::indoc;
86 
87     #[test]
some_matches_option_with_value() -> Result<()>88     fn some_matches_option_with_value() -> Result<()> {
89         let matcher = some(eq(1));
90 
91         let result = matcher.matches(&Some(1));
92 
93         verify_that!(result, eq(MatcherResult::Match))
94     }
95 
96     #[test]
some_does_not_match_option_with_wrong_value() -> Result<()>97     fn some_does_not_match_option_with_wrong_value() -> Result<()> {
98         let matcher = some(eq(1));
99 
100         let result = matcher.matches(&Some(0));
101 
102         verify_that!(result, eq(MatcherResult::NoMatch))
103     }
104 
105     #[test]
some_does_not_match_option_with_none() -> Result<()>106     fn some_does_not_match_option_with_none() -> Result<()> {
107         let matcher = some(eq::<i32, _>(1));
108 
109         let result = matcher.matches(&None);
110 
111         verify_that!(result, eq(MatcherResult::NoMatch))
112     }
113 
114     #[test]
some_full_error_message() -> Result<()>115     fn some_full_error_message() -> Result<()> {
116         let result = verify_that!(Some(2), some(eq(1)));
117         verify_that!(
118             result,
119             err(displays_as(contains_substring(indoc!(
120                 "
121                     Value of: Some(2)
122                     Expected: has a value which is equal to 1
123                     Actual: Some(2),
124                       which has a value
125                         which isn't equal to 1
126                 "
127             ))))
128         )
129     }
130 
131     #[test]
some_describe_matches() -> Result<()>132     fn some_describe_matches() -> Result<()> {
133         verify_that!(
134             some(eq::<i32, _>(1)).describe(MatcherResult::Match),
135             displays_as(eq("has a value which is equal to 1"))
136         )
137     }
138 
139     #[test]
some_describe_does_not_match() -> Result<()>140     fn some_describe_does_not_match() -> Result<()> {
141         verify_that!(
142             some(eq::<i32, _>(1)).describe(MatcherResult::NoMatch),
143             displays_as(eq("is None or has a value which isn't equal to 1"))
144         )
145     }
146 
147     #[test]
some_explain_match_with_none() -> Result<()>148     fn some_explain_match_with_none() -> Result<()> {
149         verify_that!(some(eq::<i32, _>(1)).explain_match(&None), displays_as(eq("which is None")))
150     }
151 
152     #[test]
some_explain_match_with_some_success() -> Result<()>153     fn some_explain_match_with_some_success() -> Result<()> {
154         verify_that!(
155             some(eq(1)).explain_match(&Some(1)),
156             displays_as(eq("which has a value\n  which is equal to 1"))
157         )
158     }
159 
160     #[test]
some_explain_match_with_some_fail() -> Result<()>161     fn some_explain_match_with_some_fail() -> Result<()> {
162         verify_that!(
163             some(eq(1)).explain_match(&Some(2)),
164             displays_as(eq("which has a value\n  which isn't equal to 1"))
165         )
166     }
167 }
168