summer-macros 0.5.2

summer-rs Procedural Macros implementation
Documentation
use crate::input_and_compile_error;
use proc_macro::TokenStream;
use quote::quote;
use quote::ToTokens;
use syn::{ItemFn, LitStr, Token};

struct PubsubListenerArgs {
    subscription: LitStr,
}

impl syn::parse::Parse for PubsubListenerArgs {
    fn parse(args: syn::parse::ParseStream) -> syn::Result<Self> {
        let subscription = args.parse::<LitStr>().map_err(|mut err| {
            err.combine(syn::Error::new(
                err.span(),
                r#"invalid pubsub_listener, expected #[pubsub_listener("subscription-id")]"#,
            ));
            err
        })?;
        if args.peek(Token![,]) {
            args.parse::<Token![,]>()?;
        }
        if !args.is_empty() {
            return Err(syn::Error::new(
                args.span(),
                "pubsub_listener only accepts a single subscription literal for now",
            ));
        }
        Ok(Self { subscription })
    }
}

pub(crate) struct PubsubListener {
    name: syn::Ident,
    args: PubsubListenerArgs,
    ast: syn::ItemFn,
    doc_attributes: Vec<syn::Attribute>,
}

impl PubsubListener {
    fn new(args: PubsubListenerArgs, ast: ItemFn) -> syn::Result<Self> {
        let name = ast.sig.ident.clone();
        let doc_attributes = ast
            .attrs
            .iter()
            .filter(|attr| attr.path().is_ident("doc"))
            .cloned()
            .collect();

        if ast.sig.asyncness.is_none() {
            return Err(syn::Error::new_spanned(
                ast.sig.fn_token,
                "only support async fn",
            ));
        }

        Ok(Self {
            name,
            args,
            ast,
            doc_attributes,
        })
    }
}

impl ToTokens for PubsubListener {
    fn to_tokens(&self, output: &mut proc_macro2::TokenStream) {
        let Self {
            name,
            ast,
            args,
            doc_attributes,
        } = self;
        #[allow(unused_variables)]
        let vis = &ast.vis;
        let subscription = &args.subscription;

        let stream = quote! {
            #(#doc_attributes)*
            #[allow(non_camel_case_types, missing_docs)]
            #vis struct #name;

            impl ::summer_pubsub::handler::TypedHandlerRegistrar for #name {
                fn install_consumer(&self, mut consumers: ::summer_pubsub::Consumers) -> ::summer_pubsub::Consumers {
                    use ::summer_pubsub::PubSubConfigurator;
                    #ast

                    consumers.add_consumer(
                        ::summer_pubsub::consumer::ConsumerOpts::default()
                            .consume(#subscription, #name)
                    )
                }
            }

            ::summer_pubsub::submit_typed_handler!(#name);
        };

        output.extend(stream);
    }
}

pub(crate) fn listener(args: TokenStream, input: TokenStream) -> TokenStream {
    let args: PubsubListenerArgs = match syn::parse(args) {
        Ok(config) => config,
        Err(e) => return input_and_compile_error(input, e),
    };
    let ast = match syn::parse::<syn::ItemFn>(input.clone()) {
        Ok(ast) => ast,
        Err(err) => return input_and_compile_error(input, err),
    };
    match PubsubListener::new(args, ast) {
        Ok(job) => job.into_token_stream().into(),
        Err(err) => input_and_compile_error(input, err),
    }
}