unimock 0.4.9

A versatile and developer-friendly trait mocking library
Documentation
use crate::cell::{Cell, CloneCell, FactoryCell};
use crate::debug;
use crate::macro_api::MismatchReporter;
use crate::output::Respond;
use crate::*;

use std::any::Any;
use std::sync::Mutex;

#[derive(Clone, Copy)]
pub(crate) struct PatIndex(pub usize);

#[derive(Clone, Copy)]
pub(crate) struct InputIndex(pub usize);

impl std::fmt::Display for PatIndex {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "#{}", self.0)
    }
}

pub enum PatternError {
    Downcast,
    NoMatcherFunction,
}

pub type PatternResult<T> = Result<T, PatternError>;

pub(crate) type AnyBox = Box<dyn Any + Send + Sync + 'static>;

fn downcast_box<T: 'static>(any_box: &AnyBox) -> PatternResult<&T> {
    any_box.downcast_ref().ok_or(PatternError::Downcast)
}

pub(crate) struct CallPattern {
    pub input_matcher: DynInputMatcher,
    pub responders: Vec<DynCallOrderResponder>,
    pub ordered_call_index_range: std::ops::Range<usize>,
    pub call_counter: counter::CallCounter,
}

impl CallPattern {
    pub fn match_inputs<F: MockFn>(
        &self,
        inputs: &F::Inputs<'_>,
        mismatch_reporter: Option<&mut MismatchReporter>,
    ) -> PatternResult<bool> {
        match (&self.input_matcher.dyn_matching_fn, mismatch_reporter) {
            (DynMatchingFn::MatchingDebug(f), Some(reporter)) => {
                Ok((downcast_box::<MatchingFnDebug<F>>(f)?.0)(inputs, reporter))
            }
            (DynMatchingFn::MatchingDebug(f), None) => Ok((downcast_box::<MatchingFnDebug<F>>(f)?
                .0)(
                inputs,
                &mut MismatchReporter::new_disabled(),
            )),
            (DynMatchingFn::Matching(f), _) => Ok((downcast_box::<MatchingFn<F>>(f)?.0)(inputs)),
            (DynMatchingFn::None, _) => Err(PatternError::NoMatcherFunction),
        }
    }

    pub fn debug_location(&self, pat_index: PatIndex) -> debug::CallPatternLocation {
        if let Some(debug) = self.input_matcher.matcher_debug {
            debug::CallPatternLocation::Debug(debug)
        } else {
            debug::CallPatternLocation::PatIndex(pat_index)
        }
    }

    pub fn next_responder(&self) -> Option<&DynResponder> {
        find_responder_by_call_index(&self.responders, self.call_counter.fetch_add())
    }
}

pub(crate) struct DynInputMatcher {
    dyn_matching_fn: DynMatchingFn,
    pub(crate) matcher_debug: Option<debug::InputMatcherDebug>,
}

impl DynInputMatcher {
    pub fn from_matching_fn<F: MockFn>(matching_fn: &dyn Fn(&mut Matching<F>)) -> Self {
        let mut builder = Matching::new();
        matching_fn(&mut builder);

        Self {
            dyn_matching_fn: match (builder.matching_fn, builder.matching_fn_debug) {
                (_, Some(f)) => DynMatchingFn::MatchingDebug(Box::new(f)),
                (Some(f), None) => DynMatchingFn::Matching(Box::new(f)),
                _ => DynMatchingFn::None,
            },
            matcher_debug: builder.matcher_debug,
        }
    }
}

enum DynMatchingFn {
    MatchingDebug(AnyBox),
    Matching(AnyBox),
    None,
}

pub(crate) struct MatchingFn<F: MockFn>(
    #[allow(clippy::type_complexity)]
    pub  Box<dyn (for<'i> Fn(&F::Inputs<'i>) -> bool) + Send + Sync>,
);

pub(crate) struct MatchingFnDebug<F: MockFn>(
    #[allow(clippy::type_complexity)]
    pub  Box<dyn (for<'i> Fn(&F::Inputs<'i>, &mut MismatchReporter) -> bool) + Send + Sync>,
);

pub(crate) struct DynCallOrderResponder {
    pub response_index: usize,
    pub responder: DynResponder,
}

pub(crate) enum DynResponder {
    Cell(DynCellResponder),
    Borrow(DynBorrowResponder),
    Function(DynFunctionResponder),
    Panic(String),
    Unmock,
}

impl DynResponder {
    pub fn new_cell<F: MockFn>(response: <F::Response as Respond>::Type) -> Self
    where
        <F::Response as Respond>::Type: Send + Sync + 'static,
    {
        let response = Mutex::new(Some(response));
        CellResponder::<F> {
            cell: Box::new(FactoryCell::new(move || {
                let mut lock = response.lock().unwrap();
                lock.take()
            })),
        }
        .into_dyn_responder()
    }

    pub fn new_clone_cell<F: MockFn>(response: <F::Response as Respond>::Type) -> Self
    where
        <F::Response as Respond>::Type: Clone + Send + Sync + 'static,
    {
        CellResponder::<F> {
            cell: Box::new(CloneCell(response)),
        }
        .into_dyn_responder()
    }

    pub fn new_clone_factory_cell<F: MockFn>(
        clone_fn: impl Fn() -> Option<<F::Response as Respond>::Type> + Send + Sync + 'static,
    ) -> Self
    where
        <F::Response as Respond>::Type: Send + Sync + 'static,
    {
        CellResponder::<F> {
            cell: Box::new(FactoryCell::new(clone_fn)),
        }
        .into_dyn_responder()
    }

    pub fn new_borrow<F: MockFn>(response: <F::Response as Respond>::Type) -> Self
    where
        <F::Response as Respond>::Type: Send + Sync,
    {
        BorrowResponder::<F> {
            borrowable: response,
        }
        .into_dyn_responder()
    }
}

pub(crate) struct DynCellResponder(AnyBox);
pub(crate) struct DynBorrowResponder(AnyBox);
pub(crate) struct DynFunctionResponder(AnyBox);

pub trait DowncastResponder<F: MockFn> {
    type Downcasted;

    fn downcast(&self) -> PatternResult<&Self::Downcasted>;
}

impl<F: MockFn> DowncastResponder<F> for DynCellResponder {
    type Downcasted = CellResponder<F>;

    fn downcast(&self) -> PatternResult<&Self::Downcasted> {
        downcast_box(&self.0)
    }
}

impl<F: MockFn> DowncastResponder<F> for DynBorrowResponder {
    type Downcasted = BorrowResponder<F>;

    fn downcast(&self) -> PatternResult<&Self::Downcasted> {
        downcast_box(&self.0)
    }
}

impl<F: MockFn> DowncastResponder<F> for DynFunctionResponder {
    type Downcasted = FunctionResponder<F>;

    fn downcast(&self) -> PatternResult<&Self::Downcasted> {
        downcast_box(&self.0)
    }
}

pub(crate) struct CellResponder<F: MockFn> {
    pub cell: Box<dyn Cell<<F::Response as Respond>::Type>>,
}

pub(crate) struct BorrowResponder<F: MockFn> {
    pub borrowable: <F::Response as Respond>::Type,
}

pub(crate) struct FunctionResponder<F: MockFn> {
    #[allow(clippy::type_complexity)]
    pub func: Box<dyn (for<'i> Fn(F::Inputs<'i>) -> <F::Response as Respond>::Type) + Send + Sync>,
}

impl<F: MockFn> CellResponder<F> {
    pub fn into_dyn_responder(self) -> DynResponder {
        DynResponder::Cell(DynCellResponder(Box::new(self)))
    }
}

impl<F: MockFn> BorrowResponder<F>
where
    <F::Response as Respond>::Type: Send + Sync,
{
    pub fn into_dyn_responder(self) -> DynResponder {
        DynResponder::Borrow(DynBorrowResponder(Box::new(self)))
    }
}

impl<F: MockFn> FunctionResponder<F> {
    pub fn into_dyn_responder(self) -> DynResponder {
        DynResponder::Function(DynFunctionResponder(Box::new(self)))
    }
}

fn find_responder_by_call_index(
    responders: &[DynCallOrderResponder],
    call_index: usize,
) -> Option<&DynResponder> {
    if responders.is_empty() {
        return None;
    }

    let index_result =
        responders.binary_search_by(|responder| responder.response_index.cmp(&call_index));

    Some(match index_result {
        Ok(index) => &responders[index].responder,
        Err(insert_index) => &responders[insert_index - 1].responder,
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn should_select_responder_with_lower_call_index() {
        let responders = vec![
            DynCallOrderResponder {
                response_index: 0,
                responder: DynResponder::Panic("0".to_string()),
            },
            DynCallOrderResponder {
                response_index: 5,
                responder: DynResponder::Panic("5".to_string()),
            },
        ];

        fn find_msg(responders: &[DynCallOrderResponder], call_index: usize) -> Option<&str> {
            find_responder_by_call_index(responders, call_index).map(|responder| match responder {
                DynResponder::Panic(msg) => msg.as_str(),
                _ => panic!(),
            })
        }

        assert_eq!(find_msg(&[], 42), None);
        assert_eq!(find_msg(&responders, 0), Some("0"));
        assert_eq!(find_msg(&responders, 4), Some("0"));
        assert_eq!(find_msg(&responders, 5), Some("5"));
        assert_eq!(find_msg(&responders, 7), Some("5"));
    }
}