blob: 3caf22c74a9538891fb3843151cf83458292b1ad [file] [log] [blame]
use crate::options::DeriveOptions;
use crate::rpc_attr::{AttributeKind, PubSubMethodKind, RpcMethodAttribute};
use crate::to_client::generate_client_module;
use crate::to_delegate::{generate_trait_item_method, MethodRegistration, RpcMethod};
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use std::collections::HashMap;
use syn::{
fold::{self, Fold},
parse_quote,
punctuated::Punctuated,
Error, Ident, Result, Token,
};
const METADATA_TYPE: &str = "Metadata";
const MISSING_SUBSCRIBE_METHOD_ERR: &str =
"Can't find subscribe method, expected a method annotated with `subscribe` \
e.g. `#[pubsub(subscription = \"hello\", subscribe, name = \"hello_subscribe\")]`";
const MISSING_UNSUBSCRIBE_METHOD_ERR: &str =
"Can't find unsubscribe method, expected a method annotated with `unsubscribe` \
e.g. `#[pubsub(subscription = \"hello\", unsubscribe, name = \"hello_unsubscribe\")]`";
const RPC_MOD_NAME_PREFIX: &str = "rpc_impl_";
struct RpcTrait {
has_pubsub_methods: bool,
methods: Vec<RpcMethod>,
has_metadata: bool,
}
impl<'a> Fold for RpcTrait {
fn fold_trait_item_method(&mut self, method: syn::TraitItemMethod) -> syn::TraitItemMethod {
let mut foldable_method = method.clone();
// strip rpc attributes
foldable_method.attrs.retain(|a| {
let rpc_method = self.methods.iter().find(|m| m.trait_item == method);
rpc_method.map_or(true, |rpc| rpc.attr.attr != *a)
});
fold::fold_trait_item_method(self, foldable_method)
}
fn fold_trait_item_type(&mut self, ty: syn::TraitItemType) -> syn::TraitItemType {
if ty.ident == METADATA_TYPE {
self.has_metadata = true;
let mut ty = ty.clone();
if self.has_pubsub_methods {
ty.bounds.push(parse_quote!(_jsonrpc_pubsub::PubSubMetadata))
} else {
ty.bounds.push(parse_quote!(_jsonrpc_core::Metadata))
}
return ty;
}
ty
}
}
fn compute_method_registrations(item_trait: &syn::ItemTrait) -> Result<(Vec<MethodRegistration>, Vec<RpcMethod>)> {
let methods_result: Result<Vec<_>> = item_trait
.items
.iter()
.filter_map(|trait_item| {
if let syn::TraitItem::Method(method) = trait_item {
match RpcMethodAttribute::parse_attr(method) {
Ok(Some(attr)) => Some(Ok(RpcMethod::new(attr, method.clone()))),
Ok(None) => None, // non rpc annotated trait method
Err(err) => Some(Err(syn::Error::new_spanned(method, err))),
}
} else {
None
}
})
.collect();
let methods = methods_result?;
let mut pubsub_method_pairs: HashMap<String, (Vec<RpcMethod>, Option<RpcMethod>)> = HashMap::new();
let mut method_registrations: Vec<MethodRegistration> = Vec::new();
for method in methods.iter() {
match &method.attr().kind {
AttributeKind::Rpc {
has_metadata,
is_notification,
..
} => {
if *is_notification {
method_registrations.push(MethodRegistration::Notification {
method: method.clone(),
has_metadata: *has_metadata,
})
} else {
method_registrations.push(MethodRegistration::Standard {
method: method.clone(),
has_metadata: *has_metadata,
})
}
}
AttributeKind::PubSub {
subscription_name,
kind,
} => {
let (ref mut sub, ref mut unsub) = pubsub_method_pairs
.entry(subscription_name.clone())
.or_insert((vec![], None));
match kind {
PubSubMethodKind::Subscribe => sub.push(method.clone()),
PubSubMethodKind::Unsubscribe => {
if unsub.is_none() {
*unsub = Some(method.clone())
} else {
return Err(syn::Error::new_spanned(
&method.trait_item,
format!(
"Subscription '{}' unsubscribe method is already defined",
subscription_name
),
));
}
}
}
}
}
}
for (name, pair) in pubsub_method_pairs {
match pair {
(subscribers, Some(unsubscribe)) => {
if subscribers.is_empty() {
return Err(syn::Error::new_spanned(
&unsubscribe.trait_item,
format!("subscription '{}'. {}", name, MISSING_SUBSCRIBE_METHOD_ERR),
));
}
let mut subscriber_args = subscribers.iter().filter_map(|s| s.subscriber_arg());
if let Some(subscriber_arg) = subscriber_args.next() {
for next_method_arg in subscriber_args {
if next_method_arg != subscriber_arg {
return Err(syn::Error::new_spanned(
&next_method_arg,
format!(
"Inconsistent signature for 'Subscriber' argument: {}, previously defined: {}",
next_method_arg.clone().into_token_stream(),
subscriber_arg.clone().into_token_stream()
),
));
}
}
}
method_registrations.push(MethodRegistration::PubSub {
name: name.clone(),
subscribes: subscribers.clone(),
unsubscribe: unsubscribe.clone(),
});
}
(_, None) => {
return Err(syn::Error::new_spanned(
&item_trait,
format!("subscription '{}'. {}", name, MISSING_UNSUBSCRIBE_METHOD_ERR),
));
}
}
}
Ok((method_registrations, methods))
}
fn generate_server_module(
method_registrations: &[MethodRegistration],
item_trait: &syn::ItemTrait,
methods: &[RpcMethod],
) -> Result<TokenStream> {
let has_pubsub_methods = methods.iter().any(RpcMethod::is_pubsub);
let mut rpc_trait = RpcTrait {
methods: methods.to_owned(),
has_pubsub_methods,
has_metadata: false,
};
let mut rpc_server_trait = fold::fold_item_trait(&mut rpc_trait, item_trait.clone());
let to_delegate_method = generate_trait_item_method(
&method_registrations,
&rpc_server_trait,
rpc_trait.has_metadata,
has_pubsub_methods,
)?;
rpc_server_trait.items.push(syn::TraitItem::Method(to_delegate_method));
let trait_bounds: Punctuated<syn::TypeParamBound, Token![+]> = parse_quote!(Sized + Send + Sync + 'static);
rpc_server_trait.supertraits.extend(trait_bounds);
let optional_pubsub_import = if has_pubsub_methods {
crate_name("jsonrpc-pubsub").map(|pubsub_name| quote!(use #pubsub_name as _jsonrpc_pubsub;))
} else {
Ok(quote!())
}?;
let rpc_server_module = quote! {
/// The generated server module.
pub mod gen_server {
#optional_pubsub_import
use self::_jsonrpc_core::futures as _futures;
use super::*;
#rpc_server_trait
}
};
Ok(rpc_server_module)
}
fn rpc_wrapper_mod_name(rpc_trait: &syn::ItemTrait) -> syn::Ident {
let name = rpc_trait.ident.clone();
let mod_name = format!("{}{}", RPC_MOD_NAME_PREFIX, name.to_string());
syn::Ident::new(&mod_name, proc_macro2::Span::call_site())
}
pub fn crate_name(name: &str) -> Result<Ident> {
proc_macro_crate::crate_name(name)
.map(|name| Ident::new(&name, Span::call_site()))
.map_err(|e| Error::new(Span::call_site(), &e))
}
pub fn rpc_impl(input: syn::Item, options: DeriveOptions) -> Result<proc_macro2::TokenStream> {
let rpc_trait = match input {
syn::Item::Trait(item_trait) => item_trait,
item => {
return Err(syn::Error::new_spanned(
item,
"The #[rpc] custom attribute only works with trait declarations",
));
}
};
let (method_registrations, methods) = compute_method_registrations(&rpc_trait)?;
let name = rpc_trait.ident.clone();
let mod_name_ident = rpc_wrapper_mod_name(&rpc_trait);
let core_name = crate_name("jsonrpc-core")?;
let mut submodules = Vec::new();
let mut exports = Vec::new();
if options.enable_client {
let rpc_client_module = generate_client_module(&method_registrations, &rpc_trait)?;
submodules.push(rpc_client_module);
exports.push(quote! {
pub use self::#mod_name_ident::gen_client;
});
}
if options.enable_server {
let rpc_server_module = generate_server_module(&method_registrations, &rpc_trait, &methods)?;
submodules.push(rpc_server_module);
exports.push(quote! {
pub use self::#mod_name_ident::gen_server::#name;
});
}
Ok(quote!(
mod #mod_name_ident {
use #core_name as _jsonrpc_core;
use super::*;
#(#submodules)*
}
#(#exports)*
))
}