corgi_macros/
lib.rs

1//! # Corgi Macros
2//!
3//! This crate provides procedural macros for the `corgi` RPC framework.
4//! These macros are designed to work in tandem with the types defined in the
5//! core `corgi` crate.
6//!
7//! ## Overview
8//!
9//! The primary macro is [`rpc_fn`], which automates the creation of RPC
10//! metadata and execution handlers.
11use proc_macro::TokenStream;
12use proc_macro2::Span;
13use quote::quote;
14use syn::{FnArg, ItemFn, ReturnType, parse_macro_input};
15
16/// Marks an async function as an RPC-capable function.
17///
18/// This attribute does two things:
19/// 1. It keeps the original function intact so it can be called locally.
20/// 2. It generates a global `static` variable named `__CORGI_RPC_<fn_name>`
21///    of type [`corgi::container::RpcFunction`].
22///
23/// # Requirements
24/// - All arguments must implement `wincode::SchemaReadOwned`.
25/// - The return type must implement `wincode::SchemaWrite`.
26/// - The function must be `async`.
27///
28/// # Example
29/// ```rust
30/// use corgi_macros::rpc_fn;
31///  
32/// #[rpc_fn]
33/// async fn add(a: i32, b: i32) -> i32 {
34///     a + b
35/// }
36///
37/// // You can now access the metadata:
38/// println!("RPC Name: {}", __CORGI_RPC_add.name);
39/// ```
40#[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}