ngyn_macros 0.2.4

Modular backend framework for web applications
Documentation
use ngyn_shared::{path_enum::Path, HttpMethod};
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn, Signature};

use crate::utils::str_to_ident;

struct Args {
    path: Option<Path>,
    http_method: String,
}

impl syn::parse::Parse for Args {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let http_method = input.parse::<syn::LitStr>()?.value();

        if HttpMethod::from(&http_method) == HttpMethod::Unknown {
            panic!("Unsupported HTTP method: {}", http_method)
        } else {
            input.parse::<syn::Token![,]>()?;
        }

        let path = if input.peek(syn::LitStr) {
            let path = input.parse::<syn::LitStr>()?;
            Some(Path::Single(path.value()))
        } else if input.peek(syn::token::Bracket) {
            let content;
            syn::bracketed!(content in input);
            let mut paths = Vec::new();
            while !content.is_empty() {
                let path = content.parse::<syn::LitStr>()?;
                paths.push(path.value());
                if !content.is_empty() {
                    content.parse::<syn::Token![,]>()?;
                }
            }
            Some(Path::Multiple(paths))
        } else {
            None
        };

        Ok(Args { path, http_method })
    }
}

pub fn route_macro(args: TokenStream, raw_input: TokenStream) -> TokenStream {
    let ItemFn {
        sig,
        block,
        vis,
        attrs,
    } = match syn::parse::<ItemFn>(raw_input) {
        Ok(input) => input,
        Err(err) => {
            let error_message = err.to_string();
            panic!("failed to parse method: {}", error_message)
        }
    };
    let args = parse_macro_input!(args as Args);

    let path = args.path.unwrap();
    let http_method = args.http_method;

    let Signature {
        asyncness,
        ident,
        inputs,
        output,
        fn_token,
        ..
    } = sig;

    let output = match output {
        syn::ReturnType::Default => {
            panic!("Route handler must have a return type of NgynResponse")
        }
        syn::ReturnType::Type(_, ty) => quote! { #ty },
    };

    let mut expanded_methods: Vec<_> = Vec::new();
    match path {
        Path::Multiple(paths) => {
            for path in paths {
                let route_code = quote! {
                    self.routes.push((#path.to_string(), #http_method.to_string(), stringify!(#ident).to_string()));
                };
                expanded_methods.push(route_code);
            }
        }
        Path::Single(path) => {
            let route_code = quote! {
                self.routes.push((#path.to_string(), #http_method.to_string(), stringify!(#ident).to_string()));
            };
            expanded_methods.push(route_code);
        }
    }

    let register_ident = str_to_ident(format!("register_{}", ident));

    let expanded = quote! {
        #(#attrs)*
        #vis #asyncness #fn_token #ident(#inputs) -> #output #block

        fn #register_ident(&mut self) {
            #(#expanded_methods)*
        }
    };

    expanded.into()
}