mockdown 0.4.1

Mockdown is a single file and macro/dependency free mock library for Rust
Documentation
// https://github.com/fmierlo/mockdown

use std::any::{type_name, Any};
use std::cell::RefCell;
use std::default::Default;
use std::error::Error;
use std::fmt::Debug;
use std::thread::LocalKey;

use expect::ExpectList;

thread_local! {
    static MOCKDOWN: RefCell<Mockdown> = Default::default();
}

pub fn mockdown() -> &'static LocalKey<RefCell<Mockdown>> {
    &MOCKDOWN
}

pub trait Mock {
    fn clear(&'static self) -> &'static Self;
    fn expect<T: Any, U: Any>(&'static self, expect: fn(T) -> U) -> &'static Self;
    fn mock<T: Any + Debug, U: Any>(&'static self, args: T) -> Result<U, Box<dyn Error>>;
}

impl Mock for LocalKey<RefCell<Mockdown>> {
    fn clear(&'static self) -> &'static Self {
        self.with_borrow_mut(|mock| mock.clear());
        self
    }

    fn expect<T: Any, U: Any>(&'static self, expect: fn(T) -> U) -> &'static Self {
        self.with_borrow_mut(|mock| mock.add(expect));
        self
    }

    fn mock<T: Any + Debug, U: Any>(&'static self, args: T) -> Result<U, Box<dyn Error>> {
        self.with_borrow_mut(|mock| mock.mock::<T, U>(args))
    }
}

#[derive(Default)]
pub struct Mockdown {
    expects: ExpectList,
}

impl Mockdown {
    pub fn clear(&mut self) {
        self.expects.clear();
    }

    pub fn add<T: Any, U: Any>(&mut self, expect: fn(T) -> U) {
        self.expects.add(expect);
    }

    pub fn mock<T: Any + Debug, U: Any>(&mut self, args: T) -> Result<U, Box<dyn Error>> {
        let expect = self.expects.next().ok_or_else(|| {
            self.expects.clear();
            Self::type_error::<T, U>("nothing")
        })?;

        let result = expect.mock(args).map_err(|expect| {
            self.expects.clear();
            Self::type_error::<T, U>(expect)
        })?;

        Ok(result)
    }

    fn type_error<T: Any + Debug, U: Any>(expect: &str) -> String {
        let received = type_name::<fn(T) -> U>();
        format!("Mockdown error, expect type mismatch: expecting {expect:?}, received {received:?}")
    }
}

pub mod expect {
    use std::any::Any;
    use std::fmt::Debug;

    pub trait IntoAny {
        fn into_any(self) -> Box<dyn Any>;
    }

    impl<T: Any> IntoAny for T {
        fn into_any(self) -> Box<dyn Any> {
            Box::new(self)
        }
    }

    pub trait IntoType {
        fn into_type<T: Any>(self, expect: &dyn Expect) -> Result<T, &'static str>;
    }

    impl IntoType for Box<dyn Any> {
        fn into_type<T: Any>(self, expect: &dyn Expect) -> Result<T, &'static str> {
            self.downcast::<T>()
                .map_err(|_| expect.type_name())
                .map(|value| *value)
        }
    }

    pub trait Expect: Send {
        fn call(&self, when: Box<dyn Any>) -> Result<Box<dyn Any>, &'static str>;
        fn type_name(&self) -> &'static str;
    }

    impl Debug for dyn Expect {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(f, "{:?}", self.type_name())
        }
    }

    impl dyn Expect {
        pub fn mock<T: Any, U: Any>(&self, when: T) -> Result<U, &'static str> {
            let then = self.call(when.into_any())?;
            then.into_type(self)
        }
    }

    impl<T: Any, U: Any> Expect for fn(T) -> U {
        fn call(&self, when: Box<dyn Any>) -> Result<Box<dyn Any>, &'static str> {
            let then = self(when.into_type(self)?);
            Ok(then.into_any())
        }

        fn type_name(&self) -> &'static str {
            std::any::type_name::<fn(T) -> U>()
        }
    }

    #[derive(Debug, Default)]
    pub struct ExpectList {
        list: Vec<Box<dyn Expect>>,
    }

    impl ExpectList {
        pub fn clear(&mut self) {
            self.list.clear();
        }

        pub fn add<T: Any, U: Any>(&mut self, expect: fn(T) -> U) {
            self.list.insert(0, Box::new(expect));
        }

        #[allow(clippy::should_implement_trait)]
        pub fn next(&mut self) -> Option<Box<dyn Expect>> {
            self.list.pop()
        }

        pub fn is_empty(&self) -> bool {
            self.list.is_empty()
        }
    }

    impl Drop for ExpectList {
        fn drop(&mut self) {
            if !self.is_empty() {
                panic!("Mockdown error, pending expects: {:?}", self.list)
            }
        }
    }
}