1use proc_macro::TokenStream;
12use proc_macro2::Span;
13use quote::quote;
14use syn::{FnArg, ItemFn, ReturnType, parse_macro_input};
15
16#[proc_macro_attribute]
41pub fn rpc_fn(_attr: TokenStream, input: TokenStream) -> TokenStream {
42 let func = parse_macro_input!(input as ItemFn);
43 let fn_ident = &func.sig.ident;
44 let fn_name_str = fn_ident.to_string();
45
46 let rpc_ident = syn::Ident::new(&format!("__CORGI_RPC_{}", fn_ident), Span::call_site());
47
48 let params: Vec<(syn::Ident, &syn::Type)> = func
49 .sig
50 .inputs
51 .iter()
52 .map(|arg| match arg {
53 FnArg::Typed(pat) => {
54 let ident = match &*pat.pat {
55 syn::Pat::Ident(pat_ident) => pat_ident.ident.clone(),
56 _ => panic!("rpc_fn only supports simple identifiers"),
57 };
58 (ident, &*pat.ty)
59 }
60 FnArg::Receiver(_) => panic!("rpc_fn does not support self"),
61 })
62 .collect();
63
64 let param_descriptors = params.iter().map(|(ident, ty)| {
65 let name_str = ident.to_string();
66 quote! {
67 corgi::container::Param {
68 name: #name_str,
69 type_id: std::any::TypeId::of::<#ty>(),
70 }
71 }
72 });
73
74 let param_types: Vec<_> = params.iter().map(|(_, ty)| ty).collect();
75 let arg_idents: Vec<_> = params.iter().map(|(ident, _)| ident.clone()).collect();
76
77 let has_return = match &func.sig.output {
78 ReturnType::Default => false,
79 ReturnType::Type(_, _) => true,
80 };
81
82 let decoders = param_types.iter().enumerate().map(|(i, ty)| {
83 let ident = &arg_idents[i];
84 quote! {
85 let #ident: #ty = codec.decode(&args[#i])?;
86 }
87 });
88
89 let return_type_expr = if has_return {
90 if let ReturnType::Type(_, ty) = &func.sig.output {
91 quote! { Some(std::any::TypeId::of::<#ty>()) }
92 } else {
93 unreachable!()
94 }
95 } else {
96 quote! { None }
97 };
98
99 let handler_body = if has_return {
100 quote! {
101 let result = #fn_ident( #(#arg_idents),* ).await;
102 codec.encode(&result)
103 }
104 } else {
105 quote! {
106 #fn_ident( #(#arg_idents),* ).await;
107 Ok(bytes::Bytes::new())
108 }
109 };
110
111 let expanded = quote! {
112 #func
113
114 #[allow(non_upper_case_globals)]
115 pub static #rpc_ident: std::sync::LazyLock<corgi::container::RpcFunction> =
116 std::sync::LazyLock::new(|| {
117 corgi::container::RpcFunction {
118 name: #fn_name_str,
119 params: vec![ #(#param_descriptors),* ],
120 return_type: #return_type_expr,
121 handler: std::sync::Arc::new(
122 |args: Vec<bytes::Bytes>, codec: corgi::protocol::codec::ProtobufCodec| {
123 use futures::FutureExt;
124
125 async move {
126 #(#decoders)*
127 #handler_body
128 }.boxed()
129 }
130 ),
131 }
132 });
133 };
134
135 expanded.into()
136}