autobahn_client_macros/
lib.rs1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::{parse_macro_input, FnArg, ItemFn, Pat, ReturnType, Type};
4
5fn get_crate_token() -> proc_macro2::TokenStream {
6 match std::env::var("CARGO_PKG_NAME") {
7 Ok(ref name) if name == "autobahn-client" || name == "autobahn_client" => {
8 quote::quote!(crate)
9 }
10 _ => quote::quote!(autobahn_client),
11 }
12}
13
14#[proc_macro_attribute]
18pub fn server_function(_args: TokenStream, input: TokenStream) -> TokenStream {
19 let input_fn = parse_macro_input!(input as ItemFn);
20 let fn_name = &input_fn.sig.ident;
21 let _vis = &input_fn.vis;
22 let inputs = &input_fn.sig.inputs;
23 let output = &input_fn.sig.output;
24
25 let mut arg_types: Vec<syn::Type> = Vec::new();
27 for arg in inputs.iter() {
28 match arg {
29 FnArg::Typed(pat_type) => {
30 if is_primitive_type(&pat_type.ty) {
31 panic!("Primitive types are not allowed as RPC parameters. Type '{}' is a primitive type. Only protobuf message types are supported.",
32 quote::quote!(#pat_type.ty));
33 }
34 arg_types.push((*pat_type.ty).clone());
35 }
36 FnArg::Receiver(_) => panic!("Methods with `self` are not supported"),
37 }
38 }
39
40 let crate_token = get_crate_token();
42
43 let input_type = if arg_types.is_empty() {
45 quote! { #crate_token::proto::autobahn::RpcEmpty }
46 } else {
47 let ty = &arg_types[0];
48 quote! { #ty }
49 };
50 let output_type = match output {
51 ReturnType::Default => quote! { #crate_token::proto::autobahn::RpcEmpty },
52 ReturnType::Type(_, ty) => quote! { #ty },
53 };
54
55 let rpc_name = from_function_to_rpc_name(&input_fn);
57 let topic = format!("RPC/FUNCTIONAL_SERVICE/{}", rpc_name);
58
59 let register_fn_name = format_ident!("__register_{}", fn_name);
60
61 let wrapper_fn_name = format_ident!("{}_wrapper", fn_name);
63
64 let (wrapper_params, fn_call) = if arg_types.is_empty() {
66 (quote! { _input: #input_type }, quote! { #fn_name().await })
67 } else {
68 (
69 quote! { input: #input_type },
70 quote! { #fn_name(input).await },
71 )
72 };
73
74 let return_handling = match output {
76 ReturnType::Default => {
77 quote! {
78 #fn_call;
79 Ok(#crate_token::proto::autobahn::RpcEmpty::default())
80 }
81 }
82 ReturnType::Type(_, _) => {
83 quote! {
84 Ok(#fn_call)
85 }
86 }
87 };
88
89 let gen = quote! {
90 #input_fn
92
93 async fn #wrapper_fn_name(
95 #wrapper_params
96 ) -> ::std::result::Result<#output_type, Box<dyn ::std::error::Error + Send + Sync>> {
97 #return_handling
98 }
99
100 #[ctor::ctor]
101 fn #register_fn_name() {
102 #crate_token::rpc::server::register_server_function::<#input_type, #output_type, _, _>(
103 #topic.to_string(),
104 #wrapper_fn_name,
105 );
106 }
107 };
108
109 gen.into()
110}
111
112#[proc_macro_attribute]
113pub fn client_function(_args: TokenStream, input: TokenStream) -> TokenStream {
114 let input_fn = parse_macro_input!(input as ItemFn);
115
116 let original_fn_name = &input_fn.sig.ident;
117 let vis = &input_fn.vis;
118 let inputs = &input_fn.sig.inputs;
119 let output = &input_fn.sig.output;
120
121 let mut arg_idents: Vec<syn::Ident> = Vec::new();
123 let mut arg_types: Vec<syn::Type> = Vec::new();
124
125 for arg in inputs.iter() {
126 match arg {
127 FnArg::Typed(pat_type) => match &*pat_type.pat {
128 Pat::Ident(pat_ident) => {
129 if is_primitive_type(&pat_type.ty) {
131 panic!("Primitive types are not allowed as RPC parameters. Type '{}' is a primitive type. Only protobuf message types are supported.",
132 quote::quote!(#pat_type.ty));
133 }
134
135 arg_idents.push(pat_ident.ident.clone());
136 arg_types.push((*pat_type.ty).clone());
137 }
138 _ => panic!("Parameters must be simple identifiers (e.g., `x: T`)"),
139 },
140 FnArg::Receiver(_) => panic!("Methods with `self` are not supported"),
141 }
142 }
143
144 let crate_token = get_crate_token();
146
147 let output_type = match output {
148 ReturnType::Default => quote! { #crate_token::proto::autobahn::RpcEmpty },
149 ReturnType::Type(_, ty) => quote! { #ty },
150 };
151
152 let impl_fn_name = format_ident!("{}_impl", original_fn_name);
154 let mut impl_fn = input_fn.clone();
155 let mut impl_sig = impl_fn.sig.clone();
156 impl_sig.ident = impl_fn_name.clone();
157 impl_fn.sig = impl_sig;
158
159 let wrapper_params = if inputs.is_empty() {
161 quote! { client: &::std::sync::Arc<#crate_token::autobahn::Autobahn>, timeout_ms: u64 }
162 } else {
163 quote! { client: &::std::sync::Arc<#crate_token::autobahn::Autobahn>, timeout_ms: u64, #inputs }
164 };
165
166 let rpc_name = from_function_to_rpc_name(&input_fn);
168
169 let (args_handling, input_type) = if arg_idents.is_empty() {
171 (
172 quote! { #crate_token::proto::autobahn::RpcEmpty::default() },
173 quote! { #crate_token::proto::autobahn::RpcEmpty },
174 )
175 } else if arg_idents.len() == 1 {
176 let first_arg = &arg_idents[0];
177 let first_type = &arg_types[0];
178 (quote! { #first_arg }, quote! { #first_type })
179 } else {
180 panic!("Multiple arguments not yet supported")
182 };
183
184 let output_assert = quote! {
186 const _: fn() = || {
188 fn assert_prost_message<T: ::prost::Message + ::std::default::Default>() {}
189 let _ = assert_prost_message::<#output_type>;
190 };
191 };
192
193 let input_assert = if arg_types.is_empty() {
195 quote! {} } else {
197 quote! {
198 const _: fn() = || {
199 #(
200 fn assert_valid_rpc_type<T: ::prost::Message + ::std::default::Default + 'static>() {}
202 let _ = assert_valid_rpc_type::<#arg_types>;
203 )*
204 };
205 }
206 };
207
208 let gen = quote! {
209 #output_assert
210 #input_assert
211
212 #impl_fn
214
215 #vis async fn #original_fn_name(
217 #wrapper_params
218 ) -> ::std::result::Result<#output_type, #crate_token::rpc::RPCError> {
219 let args = #args_handling;
220
221 client.call_rpc::<#output_type, #input_type>(
222 #rpc_name.to_string(),
223 args,
224 timeout_ms,
225 ).await
226 }
227 };
228
229 gen.into()
230}
231
232fn from_function_to_rpc_name(func: &syn::ItemFn) -> String {
234 use syn::{FnArg, ReturnType, Type};
235
236 let fn_name = func.sig.ident.to_string();
237
238 let mut param_types = Vec::new();
240 for input in &func.sig.inputs {
241 match input {
242 FnArg::Typed(pat_type) => {
243 let ty = &*pat_type.ty;
244 let type_name = match ty {
245 Type::Path(type_path) => type_path
246 .path
247 .segments
248 .last()
249 .map(|seg| seg.ident.to_string())
250 .unwrap_or_else(|| format!("{:?}", ty)),
251 _ => format!("{:?}", ty),
252 };
253 param_types.push(type_name);
254 }
255 FnArg::Receiver(_) => {}
256 }
257 }
258
259 if func.sig.inputs.is_empty() {
260 param_types.push("None".to_string());
261 }
262
263 let return_type = match &func.sig.output {
265 ReturnType::Default => "None".to_string(),
266 ReturnType::Type(_, ty) => match &**ty {
267 Type::Path(type_path) => type_path
268 .path
269 .segments
270 .last()
271 .map(|seg| seg.ident.to_string())
272 .unwrap_or_else(|| format!("{:?}", ty)),
273 _ => format!("{:?}", ty),
274 },
275 };
276
277 format!("{}{}{}", fn_name, param_types.join(""), return_type)
279}
280
281fn is_primitive_type(ty: &syn::Type) -> bool {
283 match ty {
284 Type::Path(type_path) => {
285 if let Some(segment) = type_path.path.segments.last() {
286 let type_name = segment.ident.to_string();
287 matches!(
288 type_name.as_str(),
289 "bool"
290 | "i8"
291 | "i16"
292 | "i32"
293 | "i64"
294 | "i128"
295 | "u8"
296 | "u16"
297 | "u32"
298 | "u64"
299 | "u128"
300 | "f32"
301 | "f64"
302 | "char"
303 | "str"
304 | "String"
305 )
306 } else {
307 false
308 }
309 }
310 Type::Reference(type_ref) => {
311 is_primitive_type(&type_ref.elem)
313 }
314 _ => false,
315 }
316}