narrative-macros 0.12.2

Procedural macros for the narrative crate
Documentation
use proc_macro2::TokenStream;
use quote::{format_ident, quote};

use crate::{item_story::ItemStory, output::step_fn, Asyncness};

pub(crate) fn generate(input: &ItemStory, asyncness: Asyncness) -> TokenStream {
    let ident = match asyncness {
        Asyncness::Sync => input.ident.clone(),
        Asyncness::Async => format_ident!("Async{}", input.ident),
    };
    let generics = match asyncness {
        Asyncness::Sync => quote!(<E>),
        Asyncness::Async => quote!(<E: Send>),
    };
    let steps = input.steps().map(|step| {
        let step_fn = step_fn::generate(step, asyncness);
        let body = if step.has_sub_story() {
            quote!(Ok(
                narrative::environment::DummyEnvironment::<Self::Error>::default()
            ))
        } else {
            match asyncness {
                Asyncness::Sync => quote!(Ok(())),
                Asyncness::Async => quote!(async { Ok(()) }),
            }
        };
        quote! {
            #[inline]
            #[allow(clippy::manual_async_fn)]
            #step_fn {
                #body
            }
        }
    });

    quote! {
        #[allow(unused_variables)]
        impl #generics #ident for narrative::environment::DummyEnvironment<E> {
            type Error = E;
            #(#steps)*
        }
    }
}

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

    #[test]
    fn test_empty() {
        let story_syntax = syn::parse_quote! {
            trait UserStory {
                #[step("Step 1")]
                fn step1();
                #[step("Step 2")]
                fn step2();
            }
        };
        let actual = generate(&story_syntax, Asyncness::Sync);
        let expected = quote! {
            #[allow(unused_variables)]
            impl<E> UserStory for narrative::environment::DummyEnvironment<E> {
                type Error = E;
                #[inline]
                #[allow(clippy::manual_async_fn)]
                fn step1(&mut self) -> Result<(), Self::Error> {
                    Ok(())
                }
                #[inline]
                #[allow(clippy::manual_async_fn)]
                fn step2(&mut self) -> Result<(), Self::Error> {
                    Ok(())
                }
            }
        };
        assert_eq!(actual.to_string(), expected.to_string());
    }

    #[test]
    fn test_async() {
        let story_syntax = syn::parse_quote! {
            trait UserStory {
                #[step("Step 1")]
                fn step1();
                #[step("Step 2")]
                fn step2();
            }
        };
        let actual = generate(&story_syntax, Asyncness::Async);
        let expected = quote! {
            #[allow(unused_variables)]
            impl<E: Send> AsyncUserStory for narrative::environment::DummyEnvironment<E> {
                type Error = E;
                #[inline]
                #[allow(clippy::manual_async_fn)]
                fn step1(&mut self) -> impl std::future::Future<Output = Result<(), Self::Error>> + Send {
                    async { Ok(()) }
                }
                #[inline]
                #[allow(clippy::manual_async_fn)]
                fn step2(&mut self) -> impl std::future::Future<Output = Result<(), Self::Error>> + Send {
                    async { Ok(()) }
                }
            }
        };
        assert_eq!(actual.to_string(), expected.to_string());
    }

    #[test]
    fn test_with_sub_story() {
        let story_syntax = syn::parse_quote! {
            trait StoryDef {
                #[step(story: OtherStory, "Sub Step 1")]
                fn sub_step_1();
            }
        };
        let actual = generate(&story_syntax, Asyncness::Sync);
        let expected = quote! {
            #[allow(unused_variables)]
            impl<E> StoryDef for narrative::environment::DummyEnvironment<E> {
                type Error = E;
                #[inline]
                #[allow(clippy::manual_async_fn)]
                fn sub_step_1(&mut self) -> Result<impl OtherStory<Error = Self::Error>, Self::Error> {
                    Ok(narrative::environment::DummyEnvironment::<Self::Error>::default())
                }
            }
        };
        assert_eq!(actual.to_string(), expected.to_string());
    }

    #[test]
    fn test_async_with_sub_story() {
        let story_syntax = syn::parse_quote! {
            trait StoryDef {
                #[step(story: OtherStory, "Sub Step 1")]
                fn sub_step_1();
            }
        };
        let actual = generate(&story_syntax, Asyncness::Async);
        let expected = quote! {
            #[allow(unused_variables)]
            impl<E: Send> AsyncStoryDef for narrative::environment::DummyEnvironment<E> {
                type Error = E;
                #[inline]
                #[allow(clippy::manual_async_fn)]
                fn sub_step_1(&mut self) -> Result<impl AsyncOtherStory<Error = Self::Error> + Send, Self::Error> {
                    Ok(narrative::environment::DummyEnvironment::<Self::Error>::default())
                }
            }
        };
        assert_eq!(actual.to_string(), expected.to_string());
    }
}