1use std::borrow::Cow;
30
31use crate::attributes::{
32 optional, parse_param_kind, Aliases, Argument, AttributeMeta, MissingArgument, NameMapping, ParamKind,
33};
34use crate::helpers::extract_doc_comments;
35use proc_macro2::TokenStream as TokenStream2;
36use quote::quote;
37use syn::spanned::Spanned;
38use syn::{punctuated::Punctuated, Attribute, Token};
39
40#[derive(Debug, Clone)]
44pub struct RpcFnArg {
45 pub(crate) arg_pat: syn::PatIdent,
46 rename_to: Option<String>,
47 pub(crate) ty: syn::Type,
48}
49
50impl RpcFnArg {
51 pub fn from_arg_attrs(arg_pat: syn::PatIdent, ty: syn::Type, attrs: &mut Vec<syn::Attribute>) -> syn::Result<Self> {
52 let mut rename_to = None;
53
54 if let Some(attr) = find_attr(attrs, "argument") {
55 let [rename] = AttributeMeta::parse(attr.clone())?.retain(["rename"])?;
56
57 let rename = optional(rename, Argument::string)?;
58
59 if let Some(rename) = rename {
60 rename_to = Some(rename);
61 }
62 }
63
64 attrs.retain(|attr| !attr.meta.path().is_ident("argument"));
66
67 Ok(Self { arg_pat, rename_to, ty })
68 }
69
70 pub fn arg_pat(&self) -> &syn::PatIdent {
72 &self.arg_pat
73 }
74 pub fn name(&self) -> String {
76 self.rename_to.clone().unwrap_or_else(|| self.arg_pat.ident.to_string())
77 }
78 pub fn ty(&self) -> &syn::Type {
80 &self.ty
81 }
82}
83
84#[derive(Debug, Clone)]
85pub struct RpcMethod {
86 pub name: String,
87 pub blocking: bool,
88 pub docs: TokenStream2,
89 pub deprecated: TokenStream2,
90 pub params: Vec<RpcFnArg>,
91 pub param_kind: ParamKind,
92 pub returns: Option<syn::Type>,
93 pub signature: syn::TraitItemFn,
94 pub aliases: Vec<String>,
95 pub with_extensions: bool,
96}
97
98impl RpcMethod {
99 pub fn from_item(attr: Attribute, mut method: syn::TraitItemFn) -> syn::Result<Self> {
100 let [aliases, blocking, name, param_kind, with_extensions] =
101 AttributeMeta::parse(attr)?.retain(["aliases", "blocking", "name", "param_kind", "with_extensions"])?;
102
103 let aliases = parse_aliases(aliases)?;
104 let blocking = optional(blocking, Argument::flag)?.is_some();
105 let name = name?.string()?;
106 let param_kind = parse_param_kind(param_kind)?;
107 let with_extensions = optional(with_extensions, Argument::flag)?.is_some();
108
109 let docs = extract_doc_comments(&method.attrs);
110 let deprecated = match find_attr(&method.attrs, "deprecated") {
111 Some(attr) => quote!(#attr),
112 None => quote!(),
113 };
114
115 if blocking && method.sig.asyncness.is_some() {
116 return Err(syn::Error::new(method.sig.span(), "Blocking method must be synchronous"));
117 }
118
119 let params: Vec<_> = method
120 .sig
121 .inputs
122 .iter_mut()
123 .filter_map(|arg| match arg {
124 syn::FnArg::Receiver(_) => None,
125 syn::FnArg::Typed(arg) => match &*arg.pat {
126 syn::Pat::Ident(name) => {
127 Some(RpcFnArg::from_arg_attrs(name.clone(), (*arg.ty).clone(), &mut arg.attrs))
128 }
129 syn::Pat::Wild(wild) => Some(Err(syn::Error::new(
130 wild.underscore_token.span(),
131 "Method argument names must be valid Rust identifiers; got `_` instead",
132 ))),
133 _ => Some(Err(syn::Error::new(
134 arg.span(),
135 format!("Unexpected method signature input; got {:?} ", *arg.pat),
136 ))),
137 },
138 })
139 .collect::<Result<_, _>>()?;
140
141 let returns = match method.sig.output.clone() {
142 syn::ReturnType::Default => None,
143 syn::ReturnType::Type(_, output) => Some(*output),
144 };
145
146 method.attrs.clear();
148
149 Ok(Self {
150 aliases,
151 blocking,
152 name,
153 params,
154 param_kind,
155 returns,
156 signature: method,
157 docs,
158 deprecated,
159 with_extensions,
160 })
161 }
162}
163
164#[derive(Debug, Clone)]
165pub struct RpcSubscription {
166 pub name: String,
167 pub notif_name_override: Option<String>,
174 pub docs: TokenStream2,
175 pub unsubscribe: String,
176 pub params: Vec<RpcFnArg>,
177 pub param_kind: ParamKind,
178 pub item: syn::Type,
179 pub signature: syn::TraitItemFn,
180 pub aliases: Vec<String>,
181 pub unsubscribe_aliases: Vec<String>,
182 pub with_extensions: bool,
183}
184
185impl RpcSubscription {
186 pub fn from_item(attr: syn::Attribute, mut sub: syn::TraitItemFn) -> syn::Result<Self> {
187 let [aliases, item, name, param_kind, unsubscribe, unsubscribe_aliases, with_extensions] =
188 AttributeMeta::parse(attr)?.retain([
189 "aliases",
190 "item",
191 "name",
192 "param_kind",
193 "unsubscribe",
194 "unsubscribe_aliases",
195 "with_extensions",
196 ])?;
197
198 let aliases = parse_aliases(aliases)?;
199 let map = name?.value::<NameMapping>()?;
200 let name = map.name;
201 let notif_name_override = map.mapped;
202 let item = item?.value()?;
203 let param_kind = parse_param_kind(param_kind)?;
204 let unsubscribe_aliases = parse_aliases(unsubscribe_aliases)?;
205 let with_extensions = optional(with_extensions, Argument::flag)?.is_some();
206
207 let docs = extract_doc_comments(&sub.attrs);
208 let unsubscribe = match parse_subscribe(unsubscribe)? {
209 Some(unsub) => unsub,
210 None => build_unsubscribe_method(&name).unwrap_or_else(||
211 panic!("Could not generate the unsubscribe method with name '{name}'. You need to provide the name manually using the `unsubscribe` attribute in your RPC API definition"),
212 ),
213 };
214
215 let params: Vec<_> = sub
216 .sig
217 .inputs
218 .iter_mut()
219 .filter_map(|arg| match arg {
220 syn::FnArg::Receiver(_) => None,
221 syn::FnArg::Typed(arg) => match &*arg.pat {
222 syn::Pat::Ident(name) => {
223 Some(RpcFnArg::from_arg_attrs(name.clone(), (*arg.ty).clone(), &mut arg.attrs))
224 }
225 _ => panic!("Identifier in signature must be an ident"),
226 },
227 })
228 .collect::<Result<_, _>>()?;
229
230 sub.attrs.clear();
232
233 Ok(Self {
234 name,
235 notif_name_override,
236 unsubscribe,
237 unsubscribe_aliases,
238 params,
239 param_kind,
240 item,
241 signature: sub,
242 aliases,
243 docs,
244 with_extensions,
245 })
246 }
247}
248
249#[derive(Debug)]
250pub struct RpcDescription {
251 pub(crate) jsonrpsee_client_path: Option<TokenStream2>,
253 pub(crate) jsonrpsee_server_path: Option<TokenStream2>,
255 pub(crate) needs_server: bool,
259 pub(crate) needs_client: bool,
263 pub(crate) namespace: Option<String>,
265 pub(crate) trait_def: syn::ItemTrait,
267 pub(crate) methods: Vec<RpcMethod>,
269 pub(crate) subscriptions: Vec<RpcSubscription>,
271 pub(crate) client_bounds: Option<Punctuated<syn::WherePredicate, Token![,]>>,
273 pub(crate) server_bounds: Option<Punctuated<syn::WherePredicate, Token![,]>>,
275}
276
277impl RpcDescription {
278 pub fn from_item(attr: Attribute, mut item: syn::ItemTrait) -> syn::Result<Self> {
279 let [client, server, namespace, client_bounds, server_bounds] =
280 AttributeMeta::parse(attr)?.retain(["client", "server", "namespace", "client_bounds", "server_bounds"])?;
281
282 let needs_server = optional(server, Argument::flag)?.is_some();
283 let needs_client = optional(client, Argument::flag)?.is_some();
284 let namespace = optional(namespace, Argument::string)?;
285 let client_bounds = optional(client_bounds, Argument::group)?;
286 let server_bounds = optional(server_bounds, Argument::group)?;
287 if !needs_server && !needs_client {
288 return Err(syn::Error::new_spanned(&item.ident, "Either 'server' or 'client' attribute must be applied"));
289 }
290
291 if client_bounds.is_some() && !needs_client {
292 return Err(syn::Error::new_spanned(
293 &item.ident,
294 "Attribute 'client' must be specified with 'client_bounds'",
295 ));
296 }
297
298 if server_bounds.is_some() && !needs_server {
299 return Err(syn::Error::new_spanned(
300 &item.ident,
301 "Attribute 'server' must be specified with 'server_bounds'",
302 ));
303 }
304
305 let jsonrpsee_client_path = crate::helpers::find_jsonrpsee_client_crate().ok();
306 let jsonrpsee_server_path = crate::helpers::find_jsonrpsee_server_crate().ok();
307
308 if needs_client && jsonrpsee_client_path.is_none() {
309 return Err(syn::Error::new_spanned(&item.ident, "Unable to locate 'jsonrpsee' client dependency"));
310 }
311 if needs_server && jsonrpsee_server_path.is_none() {
312 return Err(syn::Error::new_spanned(&item.ident, "Unable to locate 'jsonrpsee' server dependency"));
313 }
314
315 item.attrs.clear(); let mut methods = Vec::new();
318 let mut subscriptions = Vec::new();
319
320 for entry in item.items.iter() {
323 if let syn::TraitItem::Fn(method) = entry {
324 if method.sig.receiver().is_none() {
325 return Err(syn::Error::new_spanned(&method.sig, "First argument of the trait must be '&self'"));
326 }
327
328 let mut is_method = false;
329 let mut is_sub = false;
330 if let Some(attr) = find_attr(&method.attrs, "method") {
331 is_method = true;
332
333 let method_data = RpcMethod::from_item(attr.clone(), method.clone())?;
334
335 methods.push(method_data);
336 }
337 if let Some(attr) = find_attr(&method.attrs, "subscription") {
338 is_sub = true;
339 if is_method {
340 return Err(syn::Error::new_spanned(
341 method,
342 "Element cannot be both subscription and method at the same time",
343 ));
344 }
345
346 let sub_data = RpcSubscription::from_item(attr.clone(), method.clone())?;
347 subscriptions.push(sub_data);
348 }
349
350 if !is_method && !is_sub {
351 return Err(syn::Error::new_spanned(
352 method,
353 "Methods must have either 'method' or 'subscription' attribute",
354 ));
355 }
356 } else {
357 return Err(syn::Error::new_spanned(entry, "Only methods allowed in RPC traits"));
358 }
359 }
360
361 if methods.is_empty() && subscriptions.is_empty() {
362 return Err(syn::Error::new_spanned(&item, "RPC cannot be empty"));
363 }
364
365 Ok(Self {
366 jsonrpsee_client_path,
367 jsonrpsee_server_path,
368 needs_server,
369 needs_client,
370 namespace,
371 trait_def: item,
372 methods,
373 subscriptions,
374 client_bounds,
375 server_bounds,
376 })
377 }
378
379 pub fn render(self) -> Result<TokenStream2, syn::Error> {
380 let server_impl = if self.needs_server { self.render_server()? } else { TokenStream2::new() };
381 let client_impl = if self.needs_client { self.render_client()? } else { TokenStream2::new() };
382
383 Ok(quote! {
384 #server_impl
385 #client_impl
386 })
387 }
388
389 pub(crate) fn jrps_client_item(&self, item: impl quote::ToTokens) -> TokenStream2 {
392 let jsonrpsee = self.jsonrpsee_client_path.as_ref().unwrap();
393 quote! { #jsonrpsee::#item }
394 }
395
396 pub(crate) fn jrps_server_item(&self, item: impl quote::ToTokens) -> TokenStream2 {
399 let jsonrpsee = self.jsonrpsee_server_path.as_ref().unwrap();
400 quote! { #jsonrpsee::#item }
401 }
402
403 pub(crate) fn rpc_identifier<'a>(&self, method: &'a str) -> Cow<'a, str> {
408 if let Some(ns) = &self.namespace {
409 format!("{ns}_{method}").into()
410 } else {
411 Cow::Borrowed(method)
412 }
413 }
414}
415
416fn parse_aliases(arg: Result<Argument, MissingArgument>) -> syn::Result<Vec<String>> {
417 let aliases = optional(arg, Argument::value::<Aliases>)?;
418
419 Ok(aliases.map(|a| a.list.into_iter().map(|lit| lit.value()).collect()).unwrap_or_default())
420}
421
422fn parse_subscribe(arg: Result<Argument, MissingArgument>) -> syn::Result<Option<String>> {
423 let unsub = optional(arg, Argument::string)?;
424
425 Ok(unsub)
426}
427
428fn find_attr<'a>(attrs: &'a [Attribute], ident: &str) -> Option<&'a Attribute> {
429 attrs.iter().find(|a| a.path().is_ident(ident))
430}
431
432fn build_unsubscribe_method(method: &str) -> Option<String> {
433 method.strip_prefix("subscribe").map(|s| format!("unsubscribe{s}"))
434}