karyon_jsonrpc_macro/
lib.rs1use 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}