use proc_macro::TokenStream;
use darling::{Error, FromMeta};
use darling::ast::NestedMeta;
use quote::{quote, format_ident};
use syn::ItemFn;
use syn::spanned::Spanned;
#[derive(Debug, FromMeta)]
struct RouteArgs {
path: String,
#[darling(default = "default_method")]
method: String,
#[darling(default = "default_module_name")]
module: String,
#[darling(default)]
set_span_name: Option<bool>,
}
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 set_span_name = route_args.set_span_name.unwrap_or(true);
let span_setting = if set_span_name {
quote! {
ctx.set_otel_span_name();
}
} else {
quote! {}
};
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 {
#span_setting
#fn_name(ctx).await
})
);
}
#input
};
output
}