blob: 58a55c63705bd1179700fcaaf057ae830b6e22f8 [file] [log] [blame]
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// There are no visible documentation elements in this module; the declarative
// macro is documented in the matchers module.
#![doc(hidden)]
/// Matches a structure or enum with a given field which is matched by a given
/// matcher.
///
/// This takes two arguments:
///
/// * a specification of the field against which to match, and
/// * an inner [`Matcher`][crate::matcher::Matcher] to apply to that field.
///
/// For example:
///
/// ```
/// # use googletest::prelude::*;
/// #[derive(Debug)]
/// struct IntField {
/// int: i32
/// }
/// # fn should_pass() -> Result<()> {
/// verify_that!(IntField{int: 32}, field!(&IntField.int, eq(32)))?;
/// # Ok(())
/// # }
/// # should_pass().unwrap();
/// ```
///
/// Tuple structs are also supported via the index syntax:
///
/// ```
/// # use googletest::prelude::*;
/// #[derive(Debug)]
/// struct IntField(i32);
/// # fn should_pass() -> Result<()> {
/// verify_that!(IntField(32), field!(&IntField.0, eq(32)))?;
/// # Ok(())
/// # }
/// # should_pass().unwrap();
/// ```
///
/// Enums are also supported, in which case only the specified variant is
/// matched:
///
/// ```
/// # use googletest::prelude::*;
/// #[derive(Debug)]
/// enum MyEnum {
/// A(i32),
/// B,
/// }
/// # fn should_pass() -> Result<()> {
/// verify_that!(MyEnum::A(32), field!(&MyEnum::A.0, eq(32)))?; // Passes
/// # Ok(())
/// # }
/// # fn should_fail() -> Result<()> {
/// verify_that!(MyEnum::B, field!(&MyEnum::A.0, eq(32)))?; // Fails: wrong enum variant
/// # Ok(())
/// # }
/// # should_pass().unwrap();
/// # should_fail().unwrap_err();
/// ```
///
/// The structure or enum may also be referenced from a separate module:
///
/// ```
/// # use googletest::prelude::*;
/// mod a_module {
/// #[derive(Debug)]
/// pub struct AStruct(pub i32);
/// }
/// # fn should_pass() -> Result<()> {
/// verify_that!(a_module::AStruct(32), field!(&a_module::AStruct.0, eq(32)))?;
/// # Ok(())
/// # }
/// # should_pass().unwrap();
/// ```
///
/// If the inner matcher is `eq(...)`, it can be omitted:
///
/// ```
/// # use googletest::prelude::*;
/// #[derive(Debug)]
/// struct IntField {
/// int: i32
/// }
/// # fn should_pass() -> Result<()> {
/// verify_that!(IntField{int: 32}, field!(&IntField.int, 32))?;
/// # Ok(())
/// # }
/// # should_pass().unwrap();
/// ```
///
/// Nested structures are *not supported*, however:
///
/// ```compile_fail
/// # use googletest::prelude::*;
/// #[derive(Debug)]
/// struct InnerStruct(i32);
/// #[derive(Debug)]
/// struct OuterStruct {
/// inner: InnerStruct,
/// }
/// # fn should_not_compile() -> Result<()> {
/// verify_that!(value, field!(OuterStruct.inner.0, eq(32)))?; // Does not compile
/// # Ok(())
/// # }
/// ```
///
/// # Specification of the field pattern
///
/// The specification of the field follow the syntax: `(ref)? (&)?
/// $TYPE.$FIELD`.
/// The `&` allows to specify whether this matcher matches against an actual of
/// type `$TYPE` (`$TYPE`` must implement `Copy`) or a `&$TYPE`.
///
/// For instance:
///
/// ```
/// # use googletest::prelude::*;
/// #[derive(Debug)]
/// pub struct AStruct{a_field: i32};
/// # fn should_pass() -> Result<()> {
/// verify_that!(AStruct{a_field: 32}, field!(&AStruct.a_field, eq(32)))?;
/// # Ok(())
/// # }
/// # should_pass().unwrap();
/// ```
///
/// ```
/// # use googletest::prelude::*;
/// #[derive(Debug, Clone, Copy)]
/// pub struct AStruct{a_field: i32};
/// # fn should_pass() -> Result<()> {
/// verify_that!(AStruct{a_field: 32}, field!(AStruct.a_field, eq(32)))?;
/// # Ok(())
/// # }
/// # should_pass().unwrap();
/// ```
///
/// The `ref` allows to bind the field value by reference, which is required if
/// the field type does not implement `Copy`.
///
/// For instance:
///
/// ```
/// # use googletest::prelude::*;
/// #[derive(Debug)]
/// pub struct AStruct{a_field: i32};
/// # fn should_pass() -> Result<()> {
/// verify_that!(AStruct{a_field: 32}, field!(&AStruct.a_field, eq(32)))?;
/// # Ok(())
/// # }
/// # should_pass().unwrap();
/// ```
///
/// If `field!` is qualified by both `&` and `ref`, they can both be omitted.
///
/// ```
/// # use googletest::prelude::*;
/// #[derive(Debug)]
/// pub struct AStruct{a_field: String};
/// # fn should_pass() -> Result<()> {
/// verify_that!(AStruct{a_field: "32".into()}, field!(&AStruct.a_field, ref eq("32")))?;
/// verify_that!(AStruct{a_field: "32".into()}, field!(AStruct.a_field, eq("32")))?;
/// # Ok(())
/// # }
/// # should_pass().unwrap();
/// ```
///
/// See also the macro [`property`][crate::matchers::property] for an analogous
/// mechanism to extract a datum by invoking a method.
#[macro_export]
#[doc(hidden)]
macro_rules! __field {
($($t:tt)*) => { $crate::field_internal!($($t)*) }
}
// Internal-only macro created so that the macro definition does not appear in
// generated documentation.
// We cannot use `path` or `ty` to capture the type as we are terminating the
// type with a . (dot).
#[doc(hidden)]
#[macro_export]
macro_rules! field_internal {
(&$($t:ident)::+.$field:tt, ref $m:expr) => {{
$crate::matchers::__internal_unstable_do_not_depend_on_these::field_matcher(
|o: &_| {
match o {
&$($t)::* {$field: ref value, .. } => Some(value),
// The pattern below is unreachable if the type is a struct (as opposed to an
// enum). Since the macro can't know which it is, we always include it and just
// tell the compiler not to complain.
#[allow(unreachable_patterns)]
_ => None,
}
},
&stringify!($field),
$crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq!($m))
}};
(&$($t:ident)::+.$field:tt, $m:expr) => {{
$crate::matchers::__internal_unstable_do_not_depend_on_these::field_matcher(
|o: &&_| {
match o {
&$($t)::* {$field: value, .. } => Some(value),
// The pattern below is unreachable if the type is a struct (as opposed to an
// enum). Since the macro can't know which it is, we always include it and just
// tell the compiler not to complain.
#[allow(unreachable_patterns)]
_ => None,
}
},
&stringify!($field),
$crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq!($m))
}};
($($t:ident)::+.$field:tt, $m:expr) => {{
$crate::matchers::__internal_unstable_do_not_depend_on_these::field_matcher(
|o: &_| {
match o {
$($t)::* {$field: value, .. } => Some(value),
// The pattern below is unreachable if the type is a struct (as opposed to an
// enum). Since the macro can't know which it is, we always include it and just
// tell the compiler not to complain.
#[allow(unreachable_patterns)]
_ => None,
}
},
&stringify!($field),
$crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq!($m))
}};
}
/// Functions for use only by the declarative macros in this module.
///
/// **For internal use only. API stablility is not guaranteed!**
#[doc(hidden)]
pub mod internal {
use crate::{
description::Description,
matcher::{Matcher, MatcherBase, MatcherResult},
};
use std::fmt::Debug;
/// Creates a matcher to verify a specific field of the actual struct using
/// the provided inner matcher.
///
/// **For internal use only. API stablility is not guaranteed!**
#[doc(hidden)]
pub fn field_matcher<OuterT, InnerT, InnerMatcher>(
field_accessor: fn(&OuterT) -> Option<&InnerT>,
field_path: &'static str,
inner: InnerMatcher,
) -> FieldMatcher<OuterT, InnerT, InnerMatcher> {
FieldMatcher { field_accessor, field_path, inner }
}
#[derive(MatcherBase)]
pub struct FieldMatcher<OuterT, InnerT, InnerMatcher> {
field_accessor: fn(&OuterT) -> Option<&InnerT>,
field_path: &'static str,
inner: InnerMatcher,
}
impl<'a, OuterT: Debug + 'a, InnerT: Debug + 'a, InnerMatcher: Matcher<&'a InnerT>>
Matcher<&'a OuterT> for FieldMatcher<OuterT, InnerT, InnerMatcher>
{
fn matches(&self, actual: &'a OuterT) -> MatcherResult {
if let Some(value) = (self.field_accessor)(actual) {
self.inner.matches(value)
} else {
MatcherResult::NoMatch
}
}
fn explain_match(&self, actual: &'a OuterT) -> Description {
if let Some(actual) = (self.field_accessor)(actual) {
format!(
"which has field `{}`, {}",
self.field_path,
self.inner.explain_match(actual)
)
.into()
} else {
let formatted_actual_value = format!("{actual:?}");
let without_fields = formatted_actual_value.split('(').next().unwrap_or("");
let without_fields = without_fields.split('{').next().unwrap_or("").trim_end();
format!("which has the wrong enum variant `{without_fields}`").into()
}
}
fn describe(&self, matcher_result: MatcherResult) -> Description {
format!(
"has field `{}`, which {}",
self.field_path,
self.inner.describe(matcher_result)
)
.into()
}
}
impl<OuterT: Debug + Copy, InnerT: Debug + Copy, InnerMatcher: Matcher<InnerT>> Matcher<OuterT>
for FieldMatcher<OuterT, InnerT, InnerMatcher>
{
fn matches(&self, actual: OuterT) -> MatcherResult {
if let Some(value) = (self.field_accessor)(&actual) {
self.inner.matches(*value)
} else {
MatcherResult::NoMatch
}
}
fn explain_match(&self, actual: OuterT) -> Description {
if let Some(actual) = (self.field_accessor)(&actual) {
format!(
"which has field `{}`, {}",
self.field_path,
self.inner.explain_match(*actual)
)
.into()
} else {
let formatted_actual_value = format!("{actual:?}");
let without_fields = formatted_actual_value.split('(').next().unwrap_or("");
let without_fields = without_fields.split('{').next().unwrap_or("").trim_end();
format!("which has the wrong enum variant `{without_fields}`").into()
}
}
fn describe(&self, matcher_result: MatcherResult) -> Description {
format!(
"has field `{}`, which {}",
self.field_path,
self.inner.describe(matcher_result)
)
.into()
}
}
}