use crate::attributes::ParamKind;
use crate::helpers::generate_where_clause;
use crate::rpc_macro::{RpcDescription, RpcMethod, RpcSubscription};
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{FnArg, Pat, PatIdent, PatType, TypeParam};
impl RpcDescription {
pub(super) fn render_client(&self) -> Result<TokenStream2, syn::Error> {
let jsonrpsee = self.jsonrpsee_client_path.as_ref().unwrap();
let sub_tys: Vec<syn::Type> = self.subscriptions.clone().into_iter().map(|s| s.item).collect();
let trait_name = quote::format_ident!("{}Client", &self.trait_def.ident);
let where_clause = generate_where_clause(&self.trait_def, &sub_tys, true, self.client_bounds.as_ref());
let type_idents = self.trait_def.generics.type_params().collect::<Vec<&TypeParam>>();
let (impl_generics, type_generics, _) = self.trait_def.generics.split_for_impl();
let super_trait = if self.subscriptions.is_empty() {
quote! { #jsonrpsee::core::client::ClientT }
} else {
quote! { #jsonrpsee::core::client::SubscriptionClientT }
};
let method_impls =
self.methods.iter().map(|method| self.render_method(method)).collect::<Result<Vec<_>, _>>()?;
let sub_impls = self.subscriptions.iter().map(|sub| self.render_sub(sub)).collect::<Result<Vec<_>, _>>()?;
let async_trait = self.jrps_client_item(quote! { core::__reexports::async_trait });
let doc_comment = format!("Client implementation for the `{}` RPC API.", &self.trait_def.ident);
let trait_impl = quote! {
#[#async_trait]
#[doc = #doc_comment]
pub trait #trait_name #impl_generics: #super_trait where #(#where_clause,)* {
#(#method_impls)*
#(#sub_impls)*
}
impl<TypeJsonRpseeInteral #(,#type_idents)*> #trait_name #type_generics for TypeJsonRpseeInteral where TypeJsonRpseeInteral: #super_trait #(,#where_clause)* {}
};
Ok(trait_impl)
}
fn render_method(&self, method: &RpcMethod) -> Result<TokenStream2, syn::Error> {
let jrps_error = self.jrps_client_item(quote! { core::Error });
let rust_method_name = &method.signature.sig.ident;
let rust_method_params = &method.signature.sig.inputs;
let rpc_method_name = self.rpc_identifier(&method.name);
let (called_method, returns) = if let Some(returns) = &method.returns {
let called_method = quote::format_ident!("request");
let returns = quote! { #returns };
(called_method, returns)
} else {
let called_method = quote::format_ident!("notification");
let returns = quote! { Result<(), #jrps_error> };
(called_method, returns)
};
let parameter_builder = self.encode_params(&method.params, &method.param_kind, &method.signature);
let docs = &method.docs;
let deprecated = &method.deprecated;
let method = quote! {
#docs
#deprecated
async fn #rust_method_name(#rust_method_params) -> #returns {
let params = { #parameter_builder };
self.#called_method(#rpc_method_name, params).await
}
};
Ok(method)
}
fn render_sub(&self, sub: &RpcSubscription) -> Result<TokenStream2, syn::Error> {
let jrps_error = self.jrps_client_item(quote! { core::Error });
let rust_method_name = &sub.signature.sig.ident;
let rust_method_params = &sub.signature.sig.inputs;
let rpc_sub_name = self.rpc_identifier(&sub.name);
let rpc_unsub_name = self.rpc_identifier(&sub.unsubscribe);
let sub_type = self.jrps_client_item(quote! { core::client::Subscription });
let item = &sub.item;
let returns = quote! { Result<#sub_type<#item>, #jrps_error> };
let parameter_builder = self.encode_params(&sub.params, &sub.param_kind, &sub.signature);
let docs = &sub.docs;
let method = quote! {
#docs
async fn #rust_method_name(#rust_method_params) -> #returns {
let params = #parameter_builder;
self.subscribe(#rpc_sub_name, params, #rpc_unsub_name).await
}
};
Ok(method)
}
fn encode_params(
&self,
params: &[(syn::PatIdent, syn::Type)],
param_kind: &ParamKind,
signature: &syn::TraitItemMethod,
) -> TokenStream2 {
let jsonrpsee = self.jsonrpsee_client_path.as_ref().unwrap();
if params.is_empty() {
return quote!({
#jsonrpsee::core::params::ArrayParams::new()
});
}
match param_kind {
ParamKind::Map => {
let param_names = extract_param_names(&signature.sig);
let params_insert = param_names.iter().zip(params).map(|pair| {
let name = pair.0;
let (value, _value_type) = pair.1;
quote!(#name, #value)
});
quote!({
let mut params = #jsonrpsee::core::params::ObjectParams::new();
#(
if let Err(err) = params.insert( #params_insert ) {
panic!("Parameter `{}` cannot be serialized: {:?}", stringify!( #params_insert ), err);
}
)*
params
})
}
ParamKind::Array => {
let params = params.iter().map(|(param, _param_type)| param);
quote!({
let mut params = #jsonrpsee::core::params::ArrayParams::new();
#(
if let Err(err) = params.insert( #params ) {
panic!("Parameter `{}` cannot be serialized: {:?}", stringify!( #params ), err);
}
)*
params
})
}
}
}
}
fn extract_param_names(sig: &syn::Signature) -> Vec<String> {
sig.inputs
.iter()
.filter_map(|param| match param {
FnArg::Typed(PatType { pat, .. }) => match &**pat {
Pat::Ident(PatIdent { ident, .. }) => Some(ident.to_string()),
_ => None,
},
_ => None,
})
.collect()
}