ngyn_macros 0.3.1

Modular backend framework for web applications
Documentation
use proc_macro::TokenStream;
use quote::quote;
use syn::{ItemFn, ItemImpl};

use crate::utils::str_to_ident;

struct CheckArgs {
    gates: Vec<syn::Path>,
}

impl syn::parse::Parse for CheckArgs {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let mut gates = vec![];
        while !input.is_empty() {
            let path: syn::Path = input.parse()?;
            gates.push(path);
            if !input.is_empty() {
                input.parse::<syn::Token![,]>()?;
            }
        }
        if gates.is_empty() {
            return Err(syn::Error::new(input.span(), "expected at least one gate"));
        }
        Ok(Self { gates })
    }
}

pub fn check_impl_macro(impl_item: ItemImpl, args: TokenStream) -> TokenStream {
    let mut new_items = Vec::new();
    let attrs = syn::Attribute {
        pound_token: Default::default(),
        style: syn::AttrStyle::Outer,
        bracket_token: Default::default(),
        meta: syn::Meta::List(syn::MetaList {
            path: syn::Path::from(str_to_ident("check".to_string())),
            delimiter: syn::MacroDelimiter::Paren(Default::default()),
            tokens: args.into(),
        }),
    };

    for item in impl_item.items {
        let new_item = match item {
            syn::ImplItem::Fn(mut method) => {
                if method.attrs.clone().into_iter().any(|attr| {
                    attr.path().is_ident("route")
                        || attr.path().is_ident("get")
                        || attr.path().is_ident("post")
                        || attr.path().is_ident("put")
                        || attr.path().is_ident("delete")
                        || attr.path().is_ident("patch")
                        || attr.path().is_ident("head")
                        || attr.path().is_ident("options")
                }) {
                    method.attrs.push(attrs.clone());
                }
                syn::ImplItem::Fn(method)
            }
            _ => item,
        };
        new_items.push(new_item);
    }
    let new_impl = syn::Item::Impl(syn::ItemImpl {
        items: new_items,
        ..impl_item
    });
    quote::quote!(#new_impl).into()
}

pub fn check_fn_macro(args: TokenStream, input: TokenStream) -> TokenStream {
    let mut input = syn::parse_macro_input!(input as ItemFn);
    let CheckArgs { gates } = syn::parse_macro_input!(args as CheckArgs);

    let req = match input.sig.inputs.iter().nth(1) {
        Some(syn::FnArg::Typed(pat_type)) => &pat_type.pat,
        _ => panic!("Expected a valid request parameter"),
    };

    let res = match input.sig.inputs.iter().nth(2) {
        Some(syn::FnArg::Typed(pat_type)) => &pat_type.pat,
        _ => panic!("Expected a valid response parameter"),
    };

    let gates = gates.iter().map(|gate| {
        quote! {
            {
                use ngyn::prelude::NgynGate;
                let gate: #gate = ngyn::prelude::NgynProvider.provide();
                if !gate.can_activate(#req) {
                    #res.set_status(403);
                    return ngyn::prelude::NgynBody::String("Forbidden".to_string()).parse();
                }
            }
        }
    });

    input.block.stmts.insert(
        0,
        syn::parse2::<syn::Stmt>(quote! {
            #(#gates)*
        })
        .unwrap(),
    );

    let check_fn = quote! {
        #input
    };

    check_fn.into()
}