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 // There are no visible documentation elements in this module; the declarative
16 // macro is documented in the matchers module.
17 #![doc(hidden)]
18 
19 /// Matches a structure or enum with a given field which is matched by a given
20 /// matcher.
21 ///
22 /// This takes two arguments:
23 ///
24 ///  * a specification of the field against which to match, and
25 ///  * an inner [`Matcher`][crate::matcher::Matcher] to apply to that field.
26 ///
27 /// For example:
28 ///
29 /// ```
30 /// # use googletest::prelude::*;
31 /// #[derive(Debug)]
32 /// struct IntField {
33 ///   int: i32
34 /// }
35 /// # fn should_pass() -> Result<()> {
36 /// verify_that!(IntField{int: 32}, field!(IntField.int, eq(32)))?;
37 /// #     Ok(())
38 /// # }
39 /// # should_pass().unwrap();
40 /// ```
41 ///
42 /// Tuple structs are also supported via the index syntax:
43 ///
44 /// ```
45 /// # use googletest::prelude::*;
46 /// #[derive(Debug)]
47 /// struct IntField(i32);
48 /// # fn should_pass() -> Result<()> {
49 /// verify_that!(IntField(32), field!(IntField.0, eq(32)))?;
50 /// #     Ok(())
51 /// # }
52 /// # should_pass().unwrap();
53 /// ```
54 ///
55 /// Enums are also supported, in which case only the specified variant is
56 /// matched:
57 ///
58 /// ```
59 /// # use googletest::prelude::*;
60 /// #[derive(Debug)]
61 /// enum MyEnum {
62 ///     A(i32),
63 ///     B,
64 /// }
65 /// # fn should_pass() -> Result<()> {
66 /// verify_that!(MyEnum::A(32), field!(MyEnum::A.0, eq(32)))?; // Passes
67 /// #     Ok(())
68 /// # }
69 /// # fn should_fail() -> Result<()> {
70 /// verify_that!(MyEnum::B, field!(MyEnum::A.0, eq(32)))?; // Fails: wrong enum variant
71 /// #     Ok(())
72 /// # }
73 /// # should_pass().unwrap();
74 /// # should_fail().unwrap_err();
75 /// ```
76 ///
77 /// The structure or enum may also be referenced from a separate module:
78 ///
79 /// ```
80 /// # use googletest::prelude::*;
81 /// mod a_module {
82 ///     #[derive(Debug)]
83 ///     pub struct AStruct(pub i32);
84 /// }
85 /// # fn should_pass() -> Result<()> {
86 /// verify_that!(a_module::AStruct(32), field!(a_module::AStruct.0, eq(32)))?;
87 /// #     Ok(())
88 /// # }
89 /// # should_pass().unwrap();
90 /// ```
91 ///
92 /// Nested structures are *not supported*, however:
93 ///
94 /// ```compile_fail
95 /// # use googletest::prelude::*;
96 /// #[derive(Debug)]
97 /// struct InnerStruct(i32);
98 /// #[derive(Debug)]
99 /// struct OuterStruct {
100 ///     inner: InnerStruct,
101 /// }
102 /// # fn should_not_compile() -> Result<()> {
103 /// verify_that!(value, field!(OuterStruct.inner.0, eq(32)))?; // Does not compile
104 /// #     Ok(())
105 /// # }
106 /// ```
107 ///
108 /// See also the macro [`property`][crate::matchers::property] for an analogous
109 /// mechanism to extract a datum by invoking a method.
110 #[macro_export]
111 #[doc(hidden)]
112 macro_rules! __field {
113     ($($t:tt)*) => { $crate::field_internal!($($t)*) }
114 }
115 
116 // Internal-only macro created so that the macro definition does not appear in
117 // generated documentation.
118 #[doc(hidden)]
119 #[macro_export]
120 macro_rules! field_internal {
121     ($($t:ident)::+.$field:tt, $m:expr) => {{
122         use $crate::matchers::__internal_unstable_do_not_depend_on_these::field_matcher;
123         field_matcher(
124             |o| {
125                 match o {
126                     $($t)::* { $field: value, .. } => Some(value),
127                     // The pattern below is unreachable if the type is a struct (as opposed to an
128                     // enum). Since the macro can't know which it is, we always include it and just
129                     // tell the compiler not to complain.
130                     #[allow(unreachable_patterns)]
131                     _ => None,
132                 }
133             },
134             &stringify!($field),
135             $m)
136     }};
137 }
138 
139 /// Functions for use only by the declarative macros in this module.
140 ///
141 /// **For internal use only. API stablility is not guaranteed!**
142 #[doc(hidden)]
143 pub mod internal {
144     use crate::{
145         description::Description,
146         matcher::{Matcher, MatcherResult},
147     };
148     use std::fmt::Debug;
149 
150     /// Creates a matcher to verify a specific field of the actual struct using
151     /// the provided inner matcher.
152     ///
153     /// **For internal use only. API stablility is not guaranteed!**
154     #[doc(hidden)]
field_matcher<OuterT: Debug, InnerT: Debug, InnerMatcher: Matcher<ActualT = InnerT>>( field_accessor: fn(&OuterT) -> Option<&InnerT>, field_path: &'static str, inner: InnerMatcher, ) -> impl Matcher<ActualT = OuterT>155     pub fn field_matcher<OuterT: Debug, InnerT: Debug, InnerMatcher: Matcher<ActualT = InnerT>>(
156         field_accessor: fn(&OuterT) -> Option<&InnerT>,
157         field_path: &'static str,
158         inner: InnerMatcher,
159     ) -> impl Matcher<ActualT = OuterT> {
160         FieldMatcher { field_accessor, field_path, inner }
161     }
162 
163     struct FieldMatcher<OuterT, InnerT, InnerMatcher> {
164         field_accessor: fn(&OuterT) -> Option<&InnerT>,
165         field_path: &'static str,
166         inner: InnerMatcher,
167     }
168 
169     impl<OuterT: Debug, InnerT: Debug, InnerMatcher: Matcher<ActualT = InnerT>> Matcher
170         for FieldMatcher<OuterT, InnerT, InnerMatcher>
171     {
172         type ActualT = OuterT;
173 
matches(&self, actual: &OuterT) -> MatcherResult174         fn matches(&self, actual: &OuterT) -> MatcherResult {
175             if let Some(value) = (self.field_accessor)(actual) {
176                 self.inner.matches(value)
177             } else {
178                 MatcherResult::NoMatch
179             }
180         }
181 
explain_match(&self, actual: &OuterT) -> Description182         fn explain_match(&self, actual: &OuterT) -> Description {
183             if let Some(actual) = (self.field_accessor)(actual) {
184                 format!(
185                     "which has field `{}`, {}",
186                     self.field_path,
187                     self.inner.explain_match(actual)
188                 )
189                 .into()
190             } else {
191                 let formatted_actual_value = format!("{actual:?}");
192                 let without_fields = formatted_actual_value.split('(').next().unwrap_or("");
193                 let without_fields = without_fields.split('{').next().unwrap_or("").trim_end();
194                 format!("which has the wrong enum variant `{without_fields}`").into()
195             }
196         }
197 
describe(&self, matcher_result: MatcherResult) -> Description198         fn describe(&self, matcher_result: MatcherResult) -> Description {
199             format!(
200                 "has field `{}`, which {}",
201                 self.field_path,
202                 self.inner.describe(matcher_result)
203             )
204             .into()
205         }
206     }
207 }
208