1use std::borrow::Cow;
30
31use crate::{
32 attributes::{optional, Aliases, Argument, AttributeMeta, MissingArgument, NameMapping},
33 helpers::extract_doc_comments,
34};
35use proc_macro2::TokenStream as TokenStream2;
36use quote::quote;
37use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Token};
38
39#[derive(Debug, Clone)]
43pub struct RpcFnArg {
44 pub(crate) arg_pat: syn::PatIdent,
45 pub(crate) ty: syn::Type,
46}
47
48impl RpcFnArg {
49 pub fn arg_pat(&self) -> &syn::PatIdent {
51 &self.arg_pat
52 }
53}
54
55#[derive(Debug, Clone)]
56pub struct RpcMethod {
57 pub name: String,
58 pub blocking: bool,
59 pub docs: TokenStream2,
60 pub params: Vec<RpcFnArg>,
61 pub signature: syn::TraitItemFn,
62 pub aliases: Vec<String>,
63}
64
65impl RpcMethod {
66 pub fn from_item(attr: Attribute, mut method: syn::TraitItemFn) -> syn::Result<Self> {
67 let [aliases, blocking, name] =
68 AttributeMeta::parse(attr)?.retain(["aliases", "blocking", "name"])?;
69
70 let aliases = parse_aliases(aliases)?;
71 let blocking = optional(blocking, Argument::flag)?.is_some();
72 let name = name?.string()?;
73
74 let docs = extract_doc_comments(&method.attrs);
75
76 if blocking && method.sig.asyncness.is_some() {
77 return Err(syn::Error::new(method.sig.span(), "Blocking method must be synchronous"));
78 }
79
80 let params: Vec<_> = method
81 .sig
82 .inputs
83 .iter()
84 .filter_map(|arg| match arg {
85 syn::FnArg::Receiver(_) => None,
86 syn::FnArg::Typed(arg) => match &*arg.pat {
87 syn::Pat::Ident(name) => {
88 Some(Ok(RpcFnArg { arg_pat: name.clone(), ty: (*arg.ty).clone() }))
89 }
90 syn::Pat::Wild(wild) => Some(Err(syn::Error::new(
91 wild.underscore_token.span(),
92 "Method argument names must be valid Rust identifiers; got `_` instead",
93 ))),
94 _ => Some(Err(syn::Error::new(
95 arg.span(),
96 format!("Unexpected method signature input; got {:?} ", *arg.pat),
97 ))),
98 },
99 })
100 .collect::<Result<_, _>>()?;
101
102 method.attrs.clear();
104
105 Ok(Self { aliases, blocking, name, params, signature: method, docs })
106 }
107}
108
109#[derive(Debug, Clone)]
110pub struct RpcSubscription {
111 pub name: String,
112 pub notif_name_override: Option<String>,
119 pub docs: TokenStream2,
120 pub unsubscribe: String,
121 pub params: Vec<RpcFnArg>,
122 pub item: syn::Type,
123 pub signature: syn::TraitItemFn,
124 pub aliases: Vec<String>,
125 pub unsubscribe_aliases: Vec<String>,
126}
127
128impl RpcSubscription {
129 pub fn from_item(attr: syn::Attribute, mut sub: syn::TraitItemFn) -> syn::Result<Self> {
130 let [aliases, item, name, unsubscribe, unsubscribe_aliases] = AttributeMeta::parse(attr)?
131 .retain([
132 "aliases",
133 "item",
134 "name",
135 "unsubscribe",
136 "unsubscribe_aliases",
137 ])?;
138
139 let aliases = parse_aliases(aliases)?;
140 let map = name?.value::<NameMapping>()?;
141 let name = map.name;
142 let notif_name_override = map.mapped;
143 let item = item?.value()?;
144 let unsubscribe_aliases = parse_aliases(unsubscribe_aliases)?;
145
146 let docs = extract_doc_comments(&sub.attrs);
147 let unsubscribe = match parse_subscribe(unsubscribe)? {
148 Some(unsub) => unsub,
149 None => build_unsubscribe_method(&name).unwrap_or_else(||
150 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"),
151 ),
152 };
153
154 let params: Vec<_> = sub
155 .sig
156 .inputs
157 .iter()
158 .filter_map(|arg| match arg {
159 syn::FnArg::Receiver(_) => None,
160 syn::FnArg::Typed(arg) => match &*arg.pat {
161 syn::Pat::Ident(name) => {
162 Some(RpcFnArg { arg_pat: name.clone(), ty: (*arg.ty).clone() })
163 }
164 _ => panic!("Identifier in signature must be an ident"),
165 },
166 })
167 .collect();
168
169 sub.attrs.clear();
171
172 Ok(Self {
173 name,
174 notif_name_override,
175 unsubscribe,
176 unsubscribe_aliases,
177 params,
178 item,
179 signature: sub,
180 aliases,
181 docs,
182 })
183 }
184}
185
186#[derive(Debug)]
187pub struct RpcDescription {
188 pub(crate) jsonrpsee_server_path: TokenStream2,
190 pub(crate) namespace: Option<String>,
192 pub(crate) namespace_separator: Option<String>,
194 pub(crate) trait_def: syn::ItemTrait,
196 pub(crate) methods: Vec<RpcMethod>,
198 pub(crate) subscriptions: Vec<RpcSubscription>,
200 pub(crate) server_bounds: Option<Punctuated<syn::WherePredicate, Token![,]>>,
202}
203
204impl RpcDescription {
205 pub fn from_item(attr: Attribute, mut item: syn::ItemTrait) -> syn::Result<Self> {
206 let [server, namespace, namespace_separator, server_bounds] = AttributeMeta::parse(attr)?
207 .retain([
208 "server",
209 "namespace",
210 "namespace_separator",
211 "server_bounds",
212 ])?;
213
214 if optional(server, Argument::flag)?.is_none() {
217 return Err(syn::Error::new_spanned(
218 &item.ident,
219 "The 'server' attribute must be applied",
220 ));
221 }
222 let namespace = optional(namespace, Argument::string)?;
223 let namespace_separator = optional(namespace_separator, Argument::string)?;
224 let server_bounds = optional(server_bounds, Argument::group)?;
225
226 let Ok(jsonrpsee_server_path) = crate::helpers::find_jsonrpsee_server_crate() else {
227 return Err(syn::Error::new_spanned(
228 &item.ident,
229 "Unable to locate 'jsonrpsee' server dependency",
230 ));
231 };
232
233 item.attrs.clear(); let mut methods = Vec::new();
236 let mut subscriptions = Vec::new();
237
238 for entry in item.items.iter() {
241 if let syn::TraitItem::Fn(method) = entry {
242 if method.sig.receiver().is_none() {
243 return Err(syn::Error::new_spanned(
244 &method.sig,
245 "First argument of the trait must be '&self'",
246 ));
247 }
248
249 let mut is_method = false;
250 let mut is_sub = false;
251 if let Some(attr) = find_attr(&method.attrs, "method") {
252 is_method = true;
253
254 let method_data = RpcMethod::from_item(attr.clone(), method.clone())?;
255
256 methods.push(method_data);
257 }
258 if let Some(attr) = find_attr(&method.attrs, "subscription") {
259 is_sub = true;
260 if is_method {
261 return Err(syn::Error::new_spanned(
262 method,
263 "Element cannot be both subscription and method at the same time",
264 ));
265 }
266
267 let sub_data = RpcSubscription::from_item(attr.clone(), method.clone())?;
268 subscriptions.push(sub_data);
269 }
270
271 if !is_method && !is_sub {
272 return Err(syn::Error::new_spanned(
273 method,
274 "Methods must have either 'method' or 'subscription' attribute",
275 ));
276 }
277 } else {
278 return Err(syn::Error::new_spanned(entry, "Only methods allowed in RPC traits"));
279 }
280 }
281
282 if methods.is_empty() && subscriptions.is_empty() {
283 return Err(syn::Error::new_spanned(&item, "RPC cannot be empty"));
284 }
285
286 Ok(Self {
287 jsonrpsee_server_path,
288 namespace,
289 namespace_separator,
290 trait_def: item,
291 methods,
292 subscriptions,
293 server_bounds,
294 })
295 }
296
297 pub fn render(self) -> Result<TokenStream2, syn::Error> {
298 self.render_server()
299 }
300
301 pub(crate) fn jrps_server_item(&self, item: impl quote::ToTokens) -> TokenStream2 {
304 let jsonrpsee = &self.jsonrpsee_server_path;
305 quote! { #jsonrpsee::#item }
306 }
307
308 pub(crate) fn rpc_identifier<'a>(&self, method: &'a str) -> Cow<'a, str> {
314 if let Some(ns) = &self.namespace {
315 let sep = self.namespace_separator.as_deref().unwrap_or("_");
316 format!("{ns}{sep}{method}").into()
317 } else {
318 Cow::Borrowed(method)
319 }
320 }
321}
322
323fn parse_aliases(arg: Result<Argument, MissingArgument>) -> syn::Result<Vec<String>> {
324 let aliases = optional(arg, Argument::value::<Aliases>)?;
325
326 Ok(aliases.map(|a| a.list.into_iter().map(|lit| lit.value()).collect()).unwrap_or_default())
327}
328
329fn parse_subscribe(arg: Result<Argument, MissingArgument>) -> syn::Result<Option<String>> {
330 let unsub = optional(arg, Argument::string)?;
331
332 Ok(unsub)
333}
334
335fn find_attr<'a>(attrs: &'a [Attribute], ident: &str) -> Option<&'a Attribute> {
336 attrs.iter().find(|a| a.path().is_ident(ident))
337}
338
339fn build_unsubscribe_method(method: &str) -> Option<String> {
340 method.strip_prefix("subscribe").map(|s| format!("unsubscribe{s}"))
341}