rtest 0.2.2

integration test building framework
Documentation
#[cfg(feature = "async")]
use crate::config::BuilderGenerator;
use crate::{
    context::{Resource, ResourceId},
    error::TestError,
};
#[cfg(feature = "async")]
use core::future::Future;
#[cfg(feature = "async")]
use core::panic::AssertUnwindSafe;
use core::panic::{RefUnwindSafe, UnwindSafe};
#[cfg(feature = "async")] use paste::paste;
use std::result::Result;

pub struct HandlerParams {
    #[cfg(feature = "async")]
    pub(crate) runtime_builder: BuilderGenerator,
}

pub enum HandlerError {
    NotInContext(ResourceId),
    Panic(Box<dyn core::any::Any + Send>),
}

// Ok -> Execution worked, contains Result of Test
// Err, couldn't execute due to framework issues
type TestResult = Result<(), Box<dyn TestError>>;
pub type HandlerResult = Result<TestResult, HandlerError>;

pub trait Handler<R, I, O, C, const ASYNC: bool> {
    fn call(&mut self, context: C, params: &HandlerParams) -> HandlerResult;

    /// (reference, input, output)
    fn get_resource_ids(&self) -> (Vec<ResourceId>, Vec<ResourceId>, Vec<ResourceId>);
}

macro_rules! int_implement_get_resource {
    ( $( $r:ident ),* ,0, $( $i:ident ),* ,1, $( $o:ident ),* ) => {
                #[allow(unused_mut)]
                fn get_resource_ids(&self) -> (Vec<ResourceId>, Vec<ResourceId>, Vec<ResourceId>) {
                    let reference = vec![$($r::get_resource_id()),*];
                    let inputs = vec![$($i::get_resource_id()),*];
                    let outputs = vec![$($o::get_resource_id()),*];
                    (reference, inputs, outputs)
                }
    }
}

macro_rules! implement_handler {
    ( $( $r:ident ),* ,0, $( $i:ident ),* ,1, $( $o:ident ),* ) => {
        #[allow(non_snake_case, unused_parens)]
        impl<F,$($r,)* $($i,)* $($o,)* C, E> Handler<($($r,)*), ($($i,)*), ($($o,)*), C, false> for F
            where
                F: Fn($(& $r,)* $($i),* ) -> Result<($($o),*) , E>,
                F: RefUnwindSafe,
                $(
                    $r: Resource<Context = C> + UnwindSafe,
                    $r: RefUnwindSafe,
                )*
                $(
                    $i:  Resource<Context = C> + UnwindSafe,
                )*
                $(
                    $o:  Resource<Context = C>,
                )*
                E: TestError + 'static,
            {
                fn call(&mut self, #[allow(unused_variables)] context: C, _params: &HandlerParams) -> HandlerResult {
                    $(
                        let $r = $r::from_context(&context).ok_or(HandlerError::NotInContext($r::get_resource_id()))?;
                    )*
                    $(
                        let $i = $i::from_context(&context).ok_or(HandlerError::NotInContext($i::get_resource_id()))?;
                    )*
                    let result = std::panic::catch_unwind(|| (self)($(& $r,)*  $($i),*));
                    $(
                        $r::into_context(&context, $r);
                    )*
                    match result {
                        Ok(Ok(($($o),*))) => {
                            $(
                                $o::into_context(&context, $o);
                            )*
                            Ok(Ok(()))
                        }
                        Ok(Err(e)) => Ok(Err(Box::new(e))),
                        Err(panic_e) => {
                            Err(HandlerError::Panic(panic_e))
                        },
                    }
                }

                int_implement_get_resource!($($r),* ,0, $($i),* ,1, $($o),*);
            }

        #[cfg(feature = "async")]
        paste!{

        // https://github.com/rust-lang/rust/issues/113495#issuecomment-1627640952
        // https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=504f2163e4836493588cc7da1b5f165e
        trait [<AsyncBorrowFn $($r)* $($i)* $($o)*>]<'a, $($r: ?Sized + 'a,)* $($i),*>: Fn($(&'a $r,)* $($i),*) -> Self::Fut {
            type Out;
            type Fut: Future<Output = Self::Out> + 'a;
        }

        impl<'a, $($r,)* $($i,)* F, Fut> [<AsyncBorrowFn $($r)* $($i)* $($o)*>]<'a, $($r,)* $($i),*> for F
            where
                $(
                    $r: ?Sized + 'a,
                )*
                F: Fn($(&'a $r,)* $($i),*) -> Fut,
                Fut: Future + 'a,
        {
            type Out = Fut::Output;
            type Fut = Fut;
        }

        #[allow(non_snake_case, unused_parens)]
        impl<F,$($r,)* $($i,)* $($o,)* C, E> Handler<($($r,)*), ($($i,)*), ($($o,)*), C, true> for F
            where
                F: for<'a> [<AsyncBorrowFn $($r)* $($i)* $($o)*>] <'a, $($r,)* $($i,)* Out = Result<($($o),*), E>>,
                F: RefUnwindSafe,
                $(
                    $r: Resource<Context = C> + UnwindSafe,
                    $r: RefUnwindSafe,
                )*
                $(
                    $i:  Resource<Context = C> + UnwindSafe,
                )*
                $(
                    $o:  Resource<Context = C>,
                )*
                E: TestError + 'static,
            {
                fn call(&mut self, #[allow(unused_variables)] context: C, params: &HandlerParams) -> HandlerResult {
                    $(
                        let $r = $r::from_context(&context).ok_or(HandlerError::NotInContext($r::get_resource_id()))?;
                        let [<ref_ $r>] = & $r;
                    )*
                    $(
                        let $i = $i::from_context(&context).ok_or(HandlerError::NotInContext($i::get_resource_id()))?;
                    )*
                    use futures_util::future::FutureExt;
                    let rt = (params.runtime_builder)().build().unwrap();
                    let fut = (self)($([<ref_ $r>],)*  $($i),*);
                    let result = rt.block_on(AssertUnwindSafe(fut).catch_unwind());
                    $(
                        $r::into_context(&context, $r);
                    )*
                    match result {
                        Ok(Ok(($($o),*))) => {
                            $(
                                $o::into_context(&context, $o);
                            )*
                            Ok(Ok(()))
                        }
                        Ok(Err(e)) => Ok(Err(Box::new(e))),
                        Err(panic_e) => {
                            Err(HandlerError::Panic(panic_e))
                        },
                    }
                }

                int_implement_get_resource!($($r),* ,0, $($i),* ,1, $($o),*);
            }
        }

    };
}

// 0 is just the delimiter between REFERENCE and INPUT
// 1 is just the delimiter between INPUT and OUTPUT
implement_handler!(, 0,, 1,);
implement_handler!(, 0,, 1, O1);
implement_handler!(, 0,, 1, O1, O2);
implement_handler!(, 0,, 1, O1, O2, O3);
implement_handler!(, 0, I1, 1,);
implement_handler!(, 0, I1, 1, O1);
implement_handler!(, 0, I1, 1, O1, O2);
implement_handler!(, 0, I1, 1, O1, O2, O3);
implement_handler!(, 0, I1, 1, O1, O2, O3, O4);
implement_handler!(, 0, I1, I2, 1,);
implement_handler!(, 0, I1, I2, 1, O1);
implement_handler!(, 0, I1, I2, 1, O1, O2);
implement_handler!(, 0, I1, I2, 1, O1, O2, O3);
implement_handler!(, 0, I1, I2, 1, O1, O2, O3, O4);
implement_handler!(, 0, I1, I2, 1, O1, O2, O3, O4, O5);
implement_handler!(, 0, I1, I2, I3, 1,);
implement_handler!(, 0, I1, I2, I3, 1, O1);
implement_handler!(, 0, I1, I2, I3, 1, O1, O2);
implement_handler!(, 0, I1, I2, I3, 1, O1, O2, O3);
implement_handler!(, 0, I1, I2, I3, 1, O1, O2, O3, O4);
implement_handler!(, 0, I1, I2, I3, 1, O1, O2, O3, O4, O5);
implement_handler!(, 0, I1, I2, I3, 1, O1, O2, O3, O4, O5, O6);
implement_handler!(, 0, I1, I2, I3, I4, 1,);
implement_handler!(, 0, I1, I2, I3, I4, 1, O1);
implement_handler!(, 0, I1, I2, I3, I4, 1, O1, O2);
implement_handler!(, 0, I1, I2, I3, I4, 1, O1, O2, O3);
implement_handler!(, 0, I1, I2, I3, I4, 1, O1, O2, O3, O4);
implement_handler!(, 0, I1, I2, I3, I4, 1, O1, O2, O3, O4, O5);
implement_handler!(, 0, I1, I2, I3, I4, 1, O1, O2, O3, O4, O5, O6);
implement_handler!(, 0, I1, I2, I3, I4, I5, 1,);
implement_handler!(, 0, I1, I2, I3, I4, I5, 1, O1);
implement_handler!(, 0, I1, I2, I3, I4, I5, 1, O1, O2);
implement_handler!(, 0, I1, I2, I3, I4, I5, 1, O1, O2, O3);
implement_handler!(, 0, I1, I2, I3, I4, I5, 1, O1, O2, O3, O4);
implement_handler!(, 0, I1, I2, I3, I4, I5, 1, O1, O2, O3, O4, O5);
implement_handler!(, 0, I1, I2, I3, I4, I5, 1, O1, O2, O3, O4, O5, O6);
implement_handler!(R1, 0,, 1,);
implement_handler!(R1, 0,, 1, O1);
implement_handler!(R1, 0,, 1, O1, O2);
implement_handler!(R1, 0,, 1, O1, O2, O3);
implement_handler!(R1, 0, I1, 1,);
implement_handler!(R1, 0, I1, 1, O1);
implement_handler!(R1, 0, I1, 1, O1, O2);
implement_handler!(R1, 0, I1, 1, O1, O2, O3);
implement_handler!(R1, 0, I1, 1, O1, O2, O3, O4);
implement_handler!(R1, 0, I1, I2, 1,);
implement_handler!(R1, 0, I1, I2, 1, O1);
implement_handler!(R1, 0, I1, I2, 1, O1, O2);
implement_handler!(R1, 0, I1, I2, 1, O1, O2, O3);
implement_handler!(R1, 0, I1, I2, 1, O1, O2, O3, O4);
implement_handler!(R1, 0, I1, I2, 1, O1, O2, O3, O4, O5);
implement_handler!(R1, 0, I1, I2, I3, 1,);
implement_handler!(R1, 0, I1, I2, I3, 1, O1);
implement_handler!(R1, 0, I1, I2, I3, 1, O1, O2);
implement_handler!(R1, 0, I1, I2, I3, 1, O1, O2, O3);
implement_handler!(R1, 0, I1, I2, I3, 1, O1, O2, O3, O4);
implement_handler!(R1, 0, I1, I2, I3, 1, O1, O2, O3, O4, O5);
implement_handler!(R1, 0, I1, I2, I3, 1, O1, O2, O3, O4, O5, O6);
implement_handler!(R1, 0, I1, I2, I3, I4, 1,);
implement_handler!(R1, 0, I1, I2, I3, I4, 1, O1);
implement_handler!(R1, 0, I1, I2, I3, I4, 1, O1, O2);
implement_handler!(R1, 0, I1, I2, I3, I4, 1, O1, O2, O3);
implement_handler!(R1, 0, I1, I2, I3, I4, 1, O1, O2, O3, O4);
implement_handler!(R1, 0, I1, I2, I3, I4, 1, O1, O2, O3, O4, O5);
implement_handler!(R1, 0, I1, I2, I3, I4, 1, O1, O2, O3, O4, O5, O6);
implement_handler!(R1, R2, 0,, 1,);
implement_handler!(R1, R2, 0,, 1, O1);
implement_handler!(R1, R2, 0,, 1, O1, O2);
implement_handler!(R1, R2, 0,, 1, O1, O2, O3);
implement_handler!(R1, R2, 0, I1, 1,);
implement_handler!(R1, R2, 0, I1, 1, O1);
implement_handler!(R1, R2, 0, I1, 1, O1, O2);
implement_handler!(R1, R2, 0, I1, 1, O1, O2, O3);
implement_handler!(R1, R2, 0, I1, 1, O1, O2, O3, O4);
implement_handler!(R1, R2, 0, I1, I2, 1,);
implement_handler!(R1, R2, 0, I1, I2, 1, O1);
implement_handler!(R1, R2, 0, I1, I2, 1, O1, O2);
implement_handler!(R1, R2, 0, I1, I2, 1, O1, O2, O3);
implement_handler!(R1, R2, 0, I1, I2, 1, O1, O2, O3, O4);
implement_handler!(R1, R2, 0, I1, I2, 1, O1, O2, O3, O4, O5);
implement_handler!(R1, R2, 0, I1, I2, I3, 1,);
implement_handler!(R1, R2, 0, I1, I2, I3, 1, O1);
implement_handler!(R1, R2, 0, I1, I2, I3, 1, O1, O2);
implement_handler!(R1, R2, 0, I1, I2, I3, 1, O1, O2, O3);
implement_handler!(R1, R2, 0, I1, I2, I3, 1, O1, O2, O3, O4);
implement_handler!(R1, R2, 0, I1, I2, I3, 1, O1, O2, O3, O4, O5);
implement_handler!(R1, R2, 0, I1, I2, I3, 1, O1, O2, O3, O4, O5, O6);

pub fn call_handler<R, I, O, C, const ASYNC: bool>(
    context: C,
    handler: &mut dyn Handler<R, I, O, C, ASYNC>,
    params: &HandlerParams,
) -> HandlerResult {
    handler.call(context, params)
}

pub fn describe_handler<R, I, O, C, const ASYNC: bool>(
    handler: &dyn Handler<R, I, O, C, ASYNC>,
) -> (Vec<ResourceId>, Vec<ResourceId>, Vec<ResourceId>) {
    handler.get_resource_ids()
}