jsonrpsee_proc_macros/
render_client.rs1use crate::attributes::ParamKind;
27use crate::helpers::generate_where_clause;
28use crate::rpc_macro::{RpcDescription, RpcFnArg, RpcMethod, RpcSubscription};
29use proc_macro2::TokenStream as TokenStream2;
30use quote::{quote, quote_spanned};
31use syn::spanned::Spanned;
32use syn::{AngleBracketedGenericArguments, FnArg, Ident, Pat, PatIdent, PatType, PathArguments, TypeParam};
33
34impl RpcDescription {
35 pub(super) fn render_client(&self) -> Result<TokenStream2, syn::Error> {
36 let jsonrpsee = self.jsonrpsee_client_path.as_ref().unwrap();
37 let sub_tys: Vec<syn::Type> = self.subscriptions.clone().into_iter().map(|s| s.item).collect();
38
39 let trait_name = quote::format_ident!("{}Client", &self.trait_def.ident);
40 let where_clause = generate_where_clause(&self.trait_def, &sub_tys, true, self.client_bounds.as_ref());
41 let type_idents = self.trait_def.generics.type_params().collect::<Vec<&TypeParam>>();
42 let (impl_generics, type_generics, _) = self.trait_def.generics.split_for_impl();
43
44 let super_trait = if self.subscriptions.is_empty() {
45 quote! { #jsonrpsee::core::client::ClientT }
46 } else {
47 quote! { #jsonrpsee::core::client::SubscriptionClientT }
48 };
49
50 let method_impls =
51 self.methods.iter().map(|method| self.render_method(method)).collect::<Result<Vec<_>, _>>()?;
52 let sub_impls = self.subscriptions.iter().map(|sub| self.render_sub(sub)).collect::<Result<Vec<_>, _>>()?;
53
54 let async_trait = self.jrps_client_item(quote! { core::__reexports::async_trait });
55
56 let doc_comment = format!("Client implementation for the `{}` RPC API.", &self.trait_def.ident);
58 let trait_impl = quote! {
59 #[#async_trait]
60 #[doc = #doc_comment]
61 pub trait #trait_name #impl_generics: #super_trait where #(#where_clause,)* {
62 #(#method_impls)*
63 #(#sub_impls)*
64 }
65
66 impl<TypeJsonRpseeInteral #(,#type_idents)*> #trait_name #type_generics for TypeJsonRpseeInteral where TypeJsonRpseeInteral: #super_trait #(,#where_clause)* {}
67 };
68
69 Ok(trait_impl)
70 }
71
72 fn return_result_type(&self, mut ty: syn::Type) -> TokenStream2 {
74 let syn::Type::Path(ref mut type_path) = ty else {
76 return quote_spanned!(ty.span() => compile_error!("Expecting something like 'Result<Foo, Err>' here. (1)"));
77 };
78
79 let Some(type_name) = type_path.path.segments.last_mut() else {
81 return quote_spanned!(ty.span() => compile_error!("Expecting this path to end in something like 'Result<Foo, Err>'"));
82 };
83
84 let PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) = &mut type_name.arguments
86 else {
87 return quote_spanned!(ty.span() => compile_error!("Expecting something like 'Result<Foo, Err>' here, but got no generic args (eg no '<Foo,Err>')."));
88 };
89
90 if type_name.ident == "Result" {
91 if args.len() != 2 {
93 return quote_spanned!(args.span() => compile_error!("Result must be have two arguments"));
94 }
95
96 let error_arg = args.last_mut().unwrap();
98 *error_arg =
99 syn::GenericArgument::Type(syn::Type::Verbatim(self.jrps_client_item(quote! { core::client::Error })));
100
101 quote!(#ty)
102 } else if type_name.ident == "RpcResult" {
103 if args.len() != 1 {
105 return quote_spanned!(args.span() => compile_error!("RpcResult must have one argument"));
106 }
107
108 let ret_ty = args.last_mut().unwrap();
110 let err_ty = self.jrps_client_item(quote! { core::client::Error });
111
112 quote! { core::result::Result<#ret_ty, #err_ty> }
113 } else if type_name.ident == "ResponsePayload" {
114 if args.len() != 2 {
116 return quote_spanned!(args.span() => compile_error!("ResponsePayload must have exactly two arguments"));
117 }
118
119 let ret_ty = args.last_mut().unwrap();
121 let err_ty = self.jrps_client_item(quote! { core::client::Error });
122
123 quote! { core::result::Result<#ret_ty, #err_ty> }
124 } else {
125 quote_spanned!(type_name.span() => compile_error!("The return type must be Result<T, Error>, RpcResult<T> or ResponsePayload<'static, T>"))
127 }
128 }
129
130 fn render_method(&self, method: &RpcMethod) -> Result<TokenStream2, syn::Error> {
131 let jrps_error = self.jrps_client_item(quote! { core::client::Error });
133 let rust_method_name = &method.signature.sig.ident;
135 let rust_method_params = &method.signature.sig.inputs;
138 let rpc_method_name = self.rpc_identifier(&method.name);
140
141 let (called_method, returns) = if let Some(returns) = &method.returns {
144 let called_method = quote::format_ident!("request");
145 let returns = self.return_result_type(returns.clone());
146 let returns = quote! { #returns };
147
148 (called_method, returns)
149 } else {
150 let called_method = quote::format_ident!("notification");
151 let returns = quote! { Result<(), #jrps_error> };
152
153 (called_method, returns)
154 };
155
156 let parameter_builder = self.encode_params(&method.params, &method.param_kind, &method.signature);
158 let docs = &method.docs;
160 let deprecated = &method.deprecated;
162
163 let method = quote! {
164 #docs
165 #deprecated
166 #[allow(non_snake_case)]
167 #[allow(clippy::used_underscore_binding)]
168 async fn #rust_method_name(#rust_method_params) -> #returns {
169 let params = { #parameter_builder };
170 self.#called_method(#rpc_method_name, params).await
171 }
172 };
173 Ok(method)
174 }
175
176 fn render_sub(&self, sub: &RpcSubscription) -> Result<TokenStream2, syn::Error> {
177 let jrps_error = self.jrps_client_item(quote! { core::client::Error });
179 let rust_method_name = &sub.signature.sig.ident;
181 let rust_method_params = &sub.signature.sig.inputs;
183 let rpc_sub_name = self.rpc_identifier(&sub.name);
185 let rpc_unsub_name = self.rpc_identifier(&sub.unsubscribe);
187
188 let sub_type = self.jrps_client_item(quote! { core::client::Subscription });
191 let item = &sub.item;
192 let returns = quote! { Result<#sub_type<#item>, #jrps_error> };
193
194 let parameter_builder = self.encode_params(&sub.params, &sub.param_kind, &sub.signature);
196 let docs = &sub.docs;
198
199 let method = quote! {
200 #docs
201 #[allow(non_snake_case)]
202 #[allow(clippy::used_underscore_binding)]
203 async fn #rust_method_name(#rust_method_params) -> #returns {
204 let params = #parameter_builder;
205 self.subscribe(#rpc_sub_name, params, #rpc_unsub_name).await
206 }
207 };
208 Ok(method)
209 }
210
211 fn encode_params(&self, params: &[RpcFnArg], param_kind: &ParamKind, signature: &syn::TraitItemFn) -> TokenStream2 {
212 const ILLEGAL_PARAM_NAME: &str = "__RpcParams__";
213
214 let jsonrpsee = self.jsonrpsee_client_path.as_ref().unwrap();
215 let p = Ident::new(ILLEGAL_PARAM_NAME, proc_macro2::Span::call_site());
216
217 let reexports = self.jrps_client_item(quote! { core::__reexports });
218
219 if params.is_empty() {
220 return quote!({
221 #jsonrpsee::core::params::ArrayParams::new()
222 });
223 }
224
225 if params.iter().any(|arg| arg.arg_pat().ident == p) {
226 panic!(
227 "Cannot use `{}` as a parameter name because it's overlapping with an internal variable in the generated code. Change it something else to make it work", ILLEGAL_PARAM_NAME
228 );
229 }
230
231 match param_kind {
232 ParamKind::Map => {
233 let param_names = extract_param_names(&signature.sig);
235 let params_insert = params.iter().map(|arg| {
237 let value = arg.arg_pat();
239 let name = arg.name();
240 quote!(#name, #value)
241 });
242
243 if param_names.iter().any(|name| name == ILLEGAL_PARAM_NAME) {
248 panic!("Cannot use `{}` as a parameter name", ILLEGAL_PARAM_NAME);
249 }
250
251 quote!({
252 let mut #p = #jsonrpsee::core::params::ObjectParams::new();
253 #(
254 if let Err(err) = #p.insert(#params_insert) {
255 #reexports::panic_fail_serialize(stringify!(#params_insert), err);
256 }
257 )*
258 #p
259 })
260 }
261 ParamKind::Array => {
262 let params = params.iter().map(RpcFnArg::arg_pat);
264
265 quote!({
266 let mut #p = #jsonrpsee::core::params::ArrayParams::new();
267 #(
268 if let Err(err) = #p.insert(#params) {
269 #reexports::panic_fail_serialize(stringify!(#params), err);
270 }
271 )*
272 #p
273 })
274 }
275 }
276 }
277}
278
279fn extract_param_names(sig: &syn::Signature) -> Vec<String> {
280 sig.inputs
281 .iter()
282 .filter_map(|param| match param {
283 FnArg::Typed(PatType { pat, .. }) => match &**pat {
284 Pat::Ident(PatIdent { ident, .. }) => Some(ident.to_string()),
285 _ => None,
286 },
287 _ => None,
288 })
289 .collect()
290}