karyon_jsonrpc_macro/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::TokenStream as TokenStream2;
3use quote::quote;
4use syn::{
5    parse_macro_input, spanned::Spanned, FnArg, ImplItem, ItemFn, ItemImpl, LitStr, ReturnType,
6    Signature, Type, TypePath,
7};
8
9#[proc_macro_attribute]
10pub fn rpc_method(_attr: TokenStream, item: TokenStream) -> TokenStream {
11    let item_fn = parse_macro_input!(item as ItemFn);
12    TokenStream::from(quote! {
13        #item_fn
14    })
15}
16
17macro_rules! err {
18    ($($tt:tt)*) => {
19        return syn::Error::new($($tt)*).to_compile_error().into()
20    };
21}
22
23#[proc_macro_attribute]
24pub fn rpc_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
25    let item2 = item.clone();
26    let parsed_input = parse_macro_input!(item2 as ItemImpl);
27
28    let self_ty = match *parsed_input.self_ty {
29        Type::Path(p) => p,
30        _ => err!(
31            parsed_input.span(),
32            "Implementing the trait `RPCService` on this type is unsupported"
33        ),
34    };
35
36    let mut service_name = None;
37    if !attr.is_empty() {
38        let parsed_attr = syn::parse_macro_input!(attr as syn::Meta);
39        service_name = match parse_service_name(parsed_attr) {
40            Ok(res) => res,
41            Err(err) => return err.to_compile_error().into(),
42        };
43    }
44
45    let default_sn = match self_ty.path.require_ident() {
46        Ok(res) => res.to_string(),
47        Err(err) => return err.to_compile_error().into(),
48    };
49    let service_name = service_name.unwrap_or(default_sn);
50
51    let methods = match parse_struct_methods(&self_ty, parsed_input.items) {
52        Ok(res) => res,
53        Err(err) => return err.to_compile_error().into(),
54    };
55
56    let mut method_idents = vec![];
57    for (mn, method) in methods.iter() {
58        if method.inputs.len() != 2 {
59            err!(
60                method.span(),
61                "requires `&self` and a parameter of type `serde_json::Value`"
62            );
63        }
64
65        method_idents.push((
66            mn.clone().unwrap_or(method.ident.to_string()),
67            method.ident.clone(),
68        ));
69    }
70
71    let impl_methods: Vec<TokenStream2> = method_idents
72        .iter()
73        .map(|(n, m)| {
74            quote! {
75                #n => Some(Box::new(move |params: serde_json::Value| Box::pin(self.#m(params)))),
76            }
77        })
78        .collect();
79
80    let item: TokenStream2 = item.into();
81    quote! {
82        impl karyon_jsonrpc::server::RPCService for #self_ty {
83            fn get_method(
84                &self,
85                name: &str
86            ) -> Option<karyon_jsonrpc::server::RPCMethod> {
87                match name {
88                #(#impl_methods)*
89                    _ => None,
90                }
91            }
92            fn name(&self) -> String{
93                #service_name.to_string()
94            }
95        }
96        #item
97    }
98    .into()
99}
100
101#[proc_macro_attribute]
102pub fn rpc_pubsub_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
103    let item2 = item.clone();
104    let parsed_input = parse_macro_input!(item2 as ItemImpl);
105
106    let self_ty = match *parsed_input.self_ty {
107        Type::Path(p) => p,
108        _ => err!(
109            parsed_input.span(),
110            "Implementing the trait `PubSubRPCService` on this type is unsupported"
111        ),
112    };
113
114    let mut service_name = None;
115    if !attr.is_empty() {
116        let parsed_attr = syn::parse_macro_input!(attr as syn::Meta);
117        service_name = match parse_service_name(parsed_attr) {
118            Ok(res) => res,
119            Err(err) => return err.to_compile_error().into(),
120        };
121    }
122
123    let default_sn = match self_ty.path.require_ident() {
124        Ok(res) => res.to_string(),
125        Err(err) => return err.to_compile_error().into(),
126    };
127    let service_name = service_name.unwrap_or(default_sn);
128
129    let methods = match parse_struct_methods(&self_ty, parsed_input.items) {
130        Ok(res) => res,
131        Err(err) => return err.to_compile_error().into(),
132    };
133
134    let mut method_idents = vec![];
135    for (mn, method) in methods.iter() {
136        if method.inputs.len() != 4 {
137            err!(method.span(), "requires `&self` and three parameters: `Arc<Channel>`, method: `String`, and `serde_json::Value`");
138        }
139
140        method_idents.push((
141            mn.clone().unwrap_or(method.ident.to_string()),
142            method.ident.clone(),
143        ));
144    }
145
146    let impl_methods: Vec<TokenStream2> = method_idents.iter().map(
147        |(n, m)| quote! {
148            #n => {
149                Some(Box::new(
150                    move |chan: std::sync::Arc<karyon_jsonrpc::server::channel::Channel>, method: String, params: serde_json::Value| {
151                    Box::pin(self.#m(chan, method, params))
152                }))
153            },
154        },
155    ).collect();
156
157    let item: TokenStream2 = item.into();
158    quote! {
159        impl karyon_jsonrpc::server::PubSubRPCService for #self_ty {
160            fn get_pubsub_method(
161                &self,
162                name: &str
163            ) -> Option<karyon_jsonrpc::server::PubSubRPCMethod> {
164                match name {
165                #(#impl_methods)*
166                    _ => None,
167                }
168            }
169
170            fn name(&self) -> String{
171                #service_name.to_string()
172            }
173        }
174        #item
175    }
176    .into()
177}
178
179fn parse_struct_methods(
180    self_ty: &TypePath,
181    items: Vec<ImplItem>,
182) -> Result<Vec<(Option<String>, Signature)>, syn::Error> {
183    let mut methods: Vec<(Option<String>, Signature)> = vec![];
184
185    if items.is_empty() {
186        return Err(syn::Error::new(
187            self_ty.span(),
188            "At least one method should be implemented",
189        ));
190    }
191
192    for item in items {
193        match item {
194            ImplItem::Fn(method) => {
195                let mut rpc_method_name = None;
196                validate_method(&method.sig)?;
197
198                for attr in method.attrs {
199                    if attr.path().is_ident("rpc_method") {
200                        attr.parse_nested_meta(|meta| {
201                            if meta.path.is_ident("name") {
202                                let value = meta.value()?;
203                                let s: LitStr = value.parse()?;
204                                if s.value().is_empty() {
205                                    return Err(syn::Error::new(attr.span(), "Empty string"));
206                                }
207                                rpc_method_name = Some(s.value().clone());
208                                Ok(())
209                            } else {
210                                Err(syn::Error::new(attr.span(), "Unexpected attribute"))
211                            }
212                        })?;
213                        break;
214                    }
215                }
216
217                methods.push((rpc_method_name, method.sig));
218            }
219            _ => return Err(syn::Error::new(item.span(), "Unexpected item!")),
220        }
221    }
222
223    Ok(methods)
224}
225
226fn validate_method(method: &Signature) -> Result<(), syn::Error> {
227    if let FnArg::Typed(_) = method.inputs[0] {
228        return Err(syn::Error::new(method.span(), "requires `&self` parameter"));
229    }
230
231    if let ReturnType::Default = method.output {
232        return Err(syn::Error::new(
233            method.span(),
234            "requires `Result<serde_json::Value, RPCError>` as return type",
235        ));
236    }
237    Ok(())
238}
239
240fn parse_service_name(attr: syn::Meta) -> Result<Option<String>, syn::Error> {
241    if let syn::Meta::NameValue(ref n) = attr {
242        if n.path.is_ident("name") {
243            if let syn::Expr::Lit(lit) = &n.value {
244                if let syn::Lit::Str(lit_str) = &lit.lit {
245                    if lit_str.value().is_empty() {
246                        return Err(syn::Error::new(attr.span(), "Empty string"));
247                    }
248                    return Ok(Some(lit_str.value().to_string()));
249                }
250            }
251        } else {
252            return Err(syn::Error::new(attr.span(), "Unexpected attribute"));
253        }
254    }
255    Ok(None)
256}