use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{FnArg, ItemFn, ReturnType, parse_macro_input};
#[proc_macro_attribute]
pub fn rpc_fn(_attr: TokenStream, input: TokenStream) -> TokenStream {
let func = parse_macro_input!(input as ItemFn);
let fn_ident = &func.sig.ident;
let fn_name_str = fn_ident.to_string();
let rpc_ident = syn::Ident::new(&format!("__CORGI_RPC_{}", fn_ident), Span::call_site());
let params: Vec<(syn::Ident, &syn::Type)> = func
.sig
.inputs
.iter()
.map(|arg| match arg {
FnArg::Typed(pat) => {
let ident = match &*pat.pat {
syn::Pat::Ident(pat_ident) => pat_ident.ident.clone(),
_ => panic!("rpc_fn only supports simple identifiers"),
};
(ident, &*pat.ty)
}
FnArg::Receiver(_) => panic!("rpc_fn does not support self"),
})
.collect();
let param_descriptors = params.iter().map(|(ident, ty)| {
let name_str = ident.to_string();
quote! {
corgi::container::Param {
name: #name_str,
type_id: std::any::TypeId::of::<#ty>(),
}
}
});
let param_types: Vec<_> = params.iter().map(|(_, ty)| ty).collect();
let arg_idents: Vec<_> = params.iter().map(|(ident, _)| ident.clone()).collect();
let has_return = match &func.sig.output {
ReturnType::Default => false,
ReturnType::Type(_, _) => true,
};
let decoders = param_types.iter().enumerate().map(|(i, ty)| {
let ident = &arg_idents[i];
quote! {
let #ident: #ty = codec.decode(&args[#i])?;
}
});
let return_type_expr = if has_return {
if let ReturnType::Type(_, ty) = &func.sig.output {
quote! { Some(std::any::TypeId::of::<#ty>()) }
} else {
unreachable!()
}
} else {
quote! { None }
};
let handler_body = if has_return {
quote! {
let result = #fn_ident( #(#arg_idents),* ).await;
codec.encode(&result)
}
} else {
quote! {
#fn_ident( #(#arg_idents),* ).await;
Ok(bytes::Bytes::new())
}
};
let expanded = quote! {
#func
#[allow(non_upper_case_globals)]
pub static #rpc_ident: std::sync::LazyLock<corgi::container::RpcFunction> =
std::sync::LazyLock::new(|| {
corgi::container::RpcFunction {
name: #fn_name_str,
params: vec![ #(#param_descriptors),* ],
return_type: #return_type_expr,
handler: std::sync::Arc::new(
|args: Vec<bytes::Bytes>, codec: corgi::protocol::codec::ProtobufCodec| {
use futures::FutureExt;
async move {
#(#decoders)*
#handler_body
}.boxed()
}
),
}
});
};
expanded.into()
}