use std::fmt;
use crate::core::{MatchOutcome, TransformMatch};
use crate::matchers::combinators::CombinatorMode;
use super::values::FailuresByField;
#[doc(hidden)]
#[derive(Debug)]
pub struct __FieldsSpecParams<T> {
pub actual: T,
pub negated: bool,
}
type FieldsSpecFunc<'a, T> =
Box<dyn FnOnce(__FieldsSpecParams<T>) -> crate::Result<FailuresByField> + 'a>;
pub struct FieldsSpec<'a, T> {
func: FieldsSpecFunc<'a, T>,
}
impl<'a, T> fmt::Debug for FieldsSpec<'a, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FieldsSpec").finish_non_exhaustive()
}
}
impl<'a, T> FieldsSpec<'a, T> {
#[doc(hidden)]
pub fn __new(
func: impl FnOnce(__FieldsSpecParams<T>) -> crate::Result<FailuresByField> + 'a,
) -> Self {
Self {
func: Box::new(func),
}
}
}
#[derive(Debug)]
pub struct FieldMatcher<'a, T> {
mode: CombinatorMode,
spec: FieldsSpec<'a, T>,
}
impl<'a, T> FieldMatcher<'a, T> {
pub fn new(mode: CombinatorMode, spec: FieldsSpec<'a, T>) -> Self {
Self { spec, mode }
}
}
impl<'a, T> TransformMatch for FieldMatcher<'a, T> {
type In = T;
type PosOut = ();
type NegOut = ();
type PosFail = FailuresByField;
type NegFail = FailuresByField;
fn match_pos(
self,
actual: Self::In,
) -> crate::Result<MatchOutcome<Self::PosOut, Self::PosFail>> {
let failures = (self.spec.func)(__FieldsSpecParams {
actual,
negated: false,
})?;
match self.mode {
CombinatorMode::Any => {
if failures.iter().any(|(_, fail)| fail.is_none()) {
Ok(MatchOutcome::Success(()))
} else {
Ok(MatchOutcome::Fail(failures))
}
}
CombinatorMode::All => {
if failures.iter().all(|(_, fail)| fail.is_none()) {
Ok(MatchOutcome::Success(()))
} else {
Ok(MatchOutcome::Fail(failures))
}
}
}
}
fn match_neg(
self,
actual: Self::In,
) -> crate::Result<MatchOutcome<Self::PosOut, Self::PosFail>> {
let failures = (self.spec.func)(__FieldsSpecParams {
actual,
negated: true,
})?;
match self.mode {
CombinatorMode::Any => {
if failures.iter().all(|(_, fail)| fail.is_none()) {
Ok(MatchOutcome::Success(()))
} else {
Ok(MatchOutcome::Fail(failures))
}
}
CombinatorMode::All => {
if failures.iter().any(|(_, fail)| fail.is_none()) {
Ok(MatchOutcome::Success(()))
} else {
Ok(MatchOutcome::Fail(failures))
}
}
}
}
}
#[macro_export]
macro_rules! fields {
(
$struct_type:ty {
$(
$field_name:tt: $matcher:expr
),+
$(,)?
}
) => {
$crate::matchers::fields::FieldsSpec::__new(
|params: $crate::matchers::fields::__FieldsSpecParams<$struct_type>,| -> $crate::Result<::std::vec::Vec<(&::std::primitive::str, ::std::option::Option<$crate::core::FormattedFailure>)>> {
$crate::Result::Ok(vec![$(
(
stringify!($field_name),
if params.negated {
match $crate::core::DynTransformMatch::match_neg(::std::boxed::Box::new($matcher), params.actual.$field_name)? {
$crate::core::MatchOutcome::Success(_) => ::std::option::Option::None,
$crate::core::MatchOutcome::Fail(fail) => ::std::option::Option::Some(fail),
}
} else {
match $crate::core::DynTransformMatch::match_pos(::std::boxed::Box::new($matcher), params.actual.$field_name)? {
$crate::core::MatchOutcome::Success(_) => ::std::option::Option::None,
$crate::core::MatchOutcome::Fail(fail) => ::std::option::Option::Some(fail),
}
},
),
)+])
}
)
};
}