Skip to main content

googletest/matchers/
field_matcher.rs

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/// If the inner matcher is `eq(...)`, it can be omitted:
93///
94/// ```
95/// # use googletest::prelude::*;
96/// #[derive(Debug)]
97/// struct IntField {
98///   int: i32
99/// }
100/// # fn should_pass() -> Result<()> {
101/// verify_that!(IntField{int: 32}, field!(&IntField.int, 32))?;
102/// #     Ok(())
103/// # }
104/// # should_pass().unwrap();
105/// ```
106///
107/// Nested structures are *not supported*, however:
108///
109/// ```compile_fail
110/// # use googletest::prelude::*;
111/// #[derive(Debug)]
112/// struct InnerStruct(i32);
113/// #[derive(Debug)]
114/// struct OuterStruct {
115///     inner: InnerStruct,
116/// }
117/// # fn should_not_compile() -> Result<()> {
118/// verify_that!(value, field!(OuterStruct.inner.0, eq(32)))?; // Does not compile
119/// #     Ok(())
120/// # }
121/// ```
122///
123/// # Specification of the field pattern
124///
125/// The specification of the field follow the syntax: `(ref)? (&)?
126/// $TYPE.$FIELD`.
127/// The `&` allows to specify whether this matcher matches against an actual of
128/// type `$TYPE` (`$TYPE`` must implement `Copy`) or a `&$TYPE`.
129///
130/// For instance:
131///
132/// ```
133/// # use googletest::prelude::*;
134/// #[derive(Debug)]
135/// pub struct AStruct{a_field: i32};
136/// # fn should_pass() -> Result<()> {
137/// verify_that!(AStruct{a_field: 32}, field!(&AStruct.a_field, eq(32)))?;
138/// #     Ok(())
139/// # }
140/// # should_pass().unwrap();
141/// ```
142///
143/// ```
144/// # use googletest::prelude::*;
145/// #[derive(Debug, Clone, Copy)]
146/// pub struct AStruct{a_field: i32};
147/// # fn should_pass() -> Result<()> {
148/// verify_that!(AStruct{a_field: 32}, field!(AStruct.a_field, eq(32)))?;
149/// #     Ok(())
150/// # }
151/// # should_pass().unwrap();
152/// ```
153///
154/// The `ref` allows to bind the field value by reference, which is required if
155/// the field type does not implement `Copy`.
156///
157/// For instance:
158///
159/// ```
160/// # use googletest::prelude::*;
161/// #[derive(Debug)]
162/// pub struct AStruct{a_field: i32};
163/// # fn should_pass() -> Result<()> {
164/// verify_that!(AStruct{a_field: 32}, field!(&AStruct.a_field, eq(32)))?;
165/// #     Ok(())
166/// # }
167/// # should_pass().unwrap();
168/// ```
169///
170/// If `field!` is qualified by both `&` and `ref`, they can both be omitted.
171///
172/// ```
173/// # use googletest::prelude::*;
174/// #[derive(Debug)]
175/// pub struct AStruct{a_field: String};
176/// # fn should_pass() -> Result<()> {
177/// verify_that!(AStruct{a_field: "32".into()}, field!(&AStruct.a_field, ref eq("32")))?;
178/// verify_that!(AStruct{a_field: "32".into()}, field!(AStruct.a_field, eq("32")))?;
179/// #     Ok(())
180/// # }
181/// # should_pass().unwrap();
182/// ```
183///
184/// See also the macro [`property`][crate::matchers::property] for an analogous
185/// mechanism to extract a datum by invoking a method.
186#[macro_export]
187#[doc(hidden)]
188macro_rules! __field {
189    ($($t:tt)*) => { $crate::field_internal!($($t)*) }
190}
191
192// Internal-only macro created so that the macro definition does not appear in
193// generated documentation.
194// We cannot use `path` or `ty` to capture the type as we are terminating the
195// type with a . (dot).
196#[doc(hidden)]
197#[macro_export]
198macro_rules! field_internal {
199    // `ref` variant.
200    (
201        // The 3 cases of `$(& $($_amp:literal)?)?` -> `$(& $($_amp)*)*`
202        // 1. outer group captures nothing => expansion produces nothing
203        // 2. outer group captures just `&` => expansion produces `&`
204        // 3. outer group captures `& <literal>` => disallowed by `@assert_empty` subrule invocation
205        //
206        // `$_amp:literal` works only because the following `$t:ident` or `::` can't be captured by
207        // it.
208        $(& $($_amp:literal)?)? // Optional `&`'s presence is implicitly captured by `_amp`.
209        $(:: $($_cs:literal)?)? // Optional `::`'s presence is implicitly captured by `_cs`.
210        $($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)?  .$field:tt,
211        ref $m:expr) => {{
212        $crate::field_internal!(@assert_empty $($($_amp)*)* $($($_cs)*)*);
213        $crate::field_internal!(@internal
214            struct_type:  [&_]
215            field_prefix: [$(& $($_amp)*)*  $(:: $($_cs)*)*  $($t)::* $(::<$($t_ty_args),*>)*]
216            [$field] [ref] [$m])
217    }};
218
219    // Non-`ref` variant.
220    (
221        // See comment on previous variant above.
222        $(& $($_amp:literal)?)? // Optional `&`'s presence is implicitly captured by `_amp`.
223        $(:: $($_cs:literal)?)? // Optional `::`'s presence is implicitly captured by `_cs`.
224        $($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? .$field:tt, $m:expr) => {{
225        $crate::field_internal!(@assert_empty $($($_amp)*)* $($($_cs)*)*);
226        $crate::field_internal!(@internal
227            struct_type:  [$(& $($_amp)*)*  &_]
228            field_prefix: [$(& $($_amp)*)*  $(:: $($_cs)*)*  $($t)::* $(::<$($t_ty_args),*>)*]
229            [$field] [] [$m])
230    }};
231
232    (@assert_empty) => {};
233    (@assert_empty $($l:literal)+) => {
234        compile_error!("property! argument must start with an optional `&` followed by a path")
235    };
236
237    (@internal struct_type: [$struct_ty:ty]
238               field_prefix: [$($field_prefix:tt)*]
239               [$field:tt] [$($ref:tt)?] [$m:expr]) => {{
240        $crate::matchers::__internal_unstable_do_not_depend_on_these::field_matcher(
241            |o: $struct_ty| {
242                match o {
243                    $($field_prefix)* {$field: $($ref)* value, .. } => Some(value),
244                    // The pattern below is unreachable if the type is a struct (as opposed to an
245                    // enum). Since the macro can't know which it is, we always include it and just
246                    // tell the compiler not to complain.
247                    #[allow(unreachable_patterns)]
248                    _ => None,
249                }
250            },
251            &stringify!($field),
252            $crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq!($m))
253    }}
254}
255
256/// Functions for use only by the declarative macros in this module.
257///
258/// **For internal use only. API stablility is not guaranteed!**
259#[doc(hidden)]
260pub mod internal {
261    use crate::{
262        description::Description,
263        matcher::{Matcher, MatcherBase, MatcherResult},
264    };
265    use std::fmt::Debug;
266
267    /// Creates a matcher to verify a specific field of the actual struct using
268    /// the provided inner matcher.
269    ///
270    /// **For internal use only. API stablility is not guaranteed!**
271    #[doc(hidden)]
272    pub fn field_matcher<OuterT, InnerT, InnerMatcher>(
273        field_accessor: fn(&OuterT) -> Option<&InnerT>,
274        field_path: &'static str,
275        inner: InnerMatcher,
276    ) -> FieldMatcher<OuterT, InnerT, InnerMatcher> {
277        FieldMatcher { field_accessor, field_path, inner }
278    }
279
280    #[derive(MatcherBase)]
281    pub struct FieldMatcher<OuterT, InnerT, InnerMatcher> {
282        field_accessor: fn(&OuterT) -> Option<&InnerT>,
283        field_path: &'static str,
284        inner: InnerMatcher,
285    }
286
287    impl<'a, OuterT: Debug + 'a, InnerT: Debug + 'a, InnerMatcher: Matcher<&'a InnerT>>
288        Matcher<&'a OuterT> for FieldMatcher<OuterT, InnerT, InnerMatcher>
289    {
290        fn matches(&self, actual: &'a OuterT) -> MatcherResult {
291            if let Some(value) = (self.field_accessor)(actual) {
292                self.inner.matches(value)
293            } else {
294                MatcherResult::NoMatch
295            }
296        }
297
298        fn explain_match(&self, actual: &'a OuterT) -> Description {
299            if let Some(actual) = (self.field_accessor)(actual) {
300                format!(
301                    "which has field `{}`, {}",
302                    self.field_path,
303                    self.inner.explain_match(actual)
304                )
305                .into()
306            } else {
307                let formatted_actual_value = format!("{actual:?}");
308                let without_fields = formatted_actual_value.split('(').next().unwrap_or("");
309                let without_fields = without_fields.split('{').next().unwrap_or("").trim_end();
310                format!("which has the wrong enum variant `{without_fields}`").into()
311            }
312        }
313
314        fn describe(&self, matcher_result: MatcherResult) -> Description {
315            format!(
316                "has field `{}`, which {}",
317                self.field_path,
318                self.inner.describe(matcher_result)
319            )
320            .into()
321        }
322    }
323
324    impl<OuterT: Debug + Copy, InnerT: Debug + Copy, InnerMatcher: Matcher<InnerT>> Matcher<OuterT>
325        for FieldMatcher<OuterT, InnerT, InnerMatcher>
326    {
327        fn matches(&self, actual: OuterT) -> MatcherResult {
328            if let Some(value) = (self.field_accessor)(&actual) {
329                self.inner.matches(*value)
330            } else {
331                MatcherResult::NoMatch
332            }
333        }
334
335        fn explain_match(&self, actual: OuterT) -> Description {
336            if let Some(actual) = (self.field_accessor)(&actual) {
337                format!(
338                    "which has field `{}`, {}",
339                    self.field_path,
340                    self.inner.explain_match(*actual)
341                )
342                .into()
343            } else {
344                let formatted_actual_value = format!("{actual:?}");
345                let without_fields = formatted_actual_value.split('(').next().unwrap_or("");
346                let without_fields = without_fields.split('{').next().unwrap_or("").trim_end();
347                format!("which has the wrong enum variant `{without_fields}`").into()
348            }
349        }
350
351        fn describe(&self, matcher_result: MatcherResult) -> Description {
352            format!(
353                "has field `{}`, which {}",
354                self.field_path,
355                self.inner.describe(matcher_result)
356            )
357            .into()
358        }
359    }
360}