use darling::{ast::NestedMeta, Error, FromMeta};
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::spanned::Spanned;
use syn::ItemFn;
#[derive(Debug, FromMeta)]
struct RouteArgs {
path: String,
#[darling(default = "default_method")]
method: String,
#[darling(default = "default_module_name")]
module: String,
}
fn default_method() -> String {
"GET".to_string()
}
fn default_module_name() -> String {
"__lambda_lw_http_router_core_default_router".to_string()
}
#[proc_macro_attribute]
pub fn route(args: TokenStream, input: TokenStream) -> TokenStream {
let attr_args = match NestedMeta::parse_meta_list(args.into()) {
Ok(v) => v,
Err(e) => {
return TokenStream::from(Error::from(e).write_errors());
}
};
let input = syn::parse_macro_input!(input as ItemFn);
let output: TokenStream = impl_router(attr_args, input).into();
output
}
fn impl_router(args: Vec<NestedMeta>, input: ItemFn) -> proc_macro2::TokenStream {
let route_args = match RouteArgs::from_list(&args) {
Ok(v) => v,
Err(e) => {
return TokenStream::from(e.write_errors()).into();
}
};
let fn_name = &input.sig.ident;
let method = &route_args.method;
let path = &route_args.path;
let module = format_ident!("{}", route_args.module);
let register_fn = format_ident!("__register_{}", fn_name);
if input.sig.inputs.len() != 1 {
return syn::Error::new(
input.sig.span(),
"Route handler must have exactly one parameter of type RouteContext",
)
.to_compile_error();
}
let param = input.sig.inputs.first().unwrap();
match param {
syn::FnArg::Typed(pat_type) => match &*pat_type.ty {
syn::Type::Path(type_path) => {
let last_segment = type_path
.path
.segments
.last()
.ok_or_else(|| syn::Error::new(type_path.span(), "Invalid parameter type"))
.unwrap();
if last_segment.ident != "RouteContext" {
return syn::Error::new(
type_path.span(),
"Parameter must be of type RouteContext",
)
.to_compile_error();
}
}
_ => {
return syn::Error::new(
pat_type.ty.span(),
"Parameter must be of type RouteContext",
)
.to_compile_error();
}
},
_ => {
return syn::Error::new(param.span(), "Invalid parameter declaration")
.to_compile_error();
}
}
let output = quote! {
#[::lambda_lw_http_router::ctor::ctor]
fn #register_fn() {
::lambda_lw_http_router::register_route::<#module::State, #module::Event>(
#method,
#path,
|ctx| Box::pin(async move {
#fn_name(ctx).await
})
);
}
#input
};
output
}