Skip to content

Commit

Permalink
Implement server side named parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
luc65r committed Aug 30, 2021
1 parent 17883c0 commit d35c973
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 107 deletions.
5 changes: 1 addition & 4 deletions derive/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,7 @@ impl DeriveOptions {
options.enable_client = true;
options.enable_server = true;
}
if options.enable_server && options.params_style == ParamStyle::Named {
// This is not allowed at this time
panic!("Server code generation only supports `params = \"positional\"` (default) or `params = \"raw\" at this time.")
}

Ok(options)
}
}
14 changes: 0 additions & 14 deletions derive/src/rpc_trait.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::options::DeriveOptions;
use crate::params_style::ParamStyle;
use crate::rpc_attr::{AttributeKind, PubSubMethodKind, RpcMethodAttribute};
use crate::to_client::generate_client_module;
use crate::to_delegate::{generate_trait_item_method, MethodRegistration, RpcMethod};
Expand All @@ -22,10 +21,6 @@ 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\")]`";

pub const USING_NAMED_PARAMS_WITH_SERVER_ERR: &str =
"`params = \"named\"` can only be used to generate a client (on a trait annotated with #[rpc(client)]). \
At this time the server does not support named parameters.";

const RPC_MOD_NAME_PREFIX: &str = "rpc_impl_";

struct RpcTrait {
Expand Down Expand Up @@ -222,12 +217,6 @@ fn rpc_wrapper_mod_name(rpc_trait: &syn::ItemTrait) -> syn::Ident {
syn::Ident::new(&mod_name, proc_macro2::Span::call_site())
}

fn has_named_params(methods: &[RpcMethod]) -> bool {
methods
.iter()
.any(|method| method.attr.params_style == Some(ParamStyle::Named))
}

pub fn crate_name(name: &str) -> Result<Ident> {
proc_macro_crate::crate_name(name)
.map(|name| Ident::new(&name, Span::call_site()))
Expand Down Expand Up @@ -264,9 +253,6 @@ pub fn rpc_impl(input: syn::Item, options: &DeriveOptions) -> Result<proc_macro2
});
}
if options.enable_server {
if has_named_params(&methods) {
return Err(syn::Error::new_spanned(rpc_trait, USING_NAMED_PARAMS_WITH_SERVER_ERR));
}
let rpc_server_module = generate_server_module(&method_registrations, &rpc_trait, &methods)?;
submodules.push(rpc_server_module);
exports.push(quote! {
Expand Down
129 changes: 79 additions & 50 deletions derive/src/to_delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,21 +206,39 @@ impl RpcMethod {
}

fn generate_delegate_closure(&self, is_subscribe: bool) -> Result<proc_macro2::TokenStream> {
let mut param_types: Vec<_> = self
let args = self
.trait_item
.sig
.inputs
.iter()
.cloned()
.filter_map(|arg| match arg {
syn::FnArg::Typed(ty) => Some(*ty.ty),
_ => None,
.filter_map(|arg| {
if let syn::FnArg::Typed(pat_type) = arg {
Some(pat_type)
} else {
None
}
})
.collect();
.enumerate();

// special args are those which are not passed directly via rpc params: metadata, subscriber
let special_args = Self::special_args(&param_types);
param_types.retain(|ty| !special_args.iter().any(|(_, sty)| sty == ty));
let mut special_args = vec![];
let mut fn_args = vec![];

for (i, arg) in args {
if let Some(sarg) = Self::special_arg(i, arg.clone()) {
special_args.push(sarg);
} else {
fn_args.push(arg);
}
}

let special_args: Vec<_> = special_args;
let fn_args: Vec<_> = fn_args;

let param_types: Vec<_> = fn_args.iter().cloned().map(|arg| *arg.ty).collect();
let arg_names: Vec<_> = fn_args.iter().cloned().map(|arg| *arg.pat).collect();

if param_types.len() > TUPLE_FIELD_NAMES.len() {
return Err(syn::Error::new_spanned(
&self.trait_item,
Expand All @@ -232,28 +250,38 @@ impl RpcMethod {
.take(param_types.len())
.map(|name| ident(name))
.collect());
let param_types = &param_types;
let parse_params = {
// last arguments that are `Option`-s are optional 'trailing' arguments
let trailing_args_num = param_types.iter().rev().take_while(|t| is_option_type(t)).count();

if trailing_args_num != 0 {
self.params_with_trailing(trailing_args_num, param_types, tuple_fields)
} else if param_types.is_empty() {
quote! { let params = params.expect_no_params(); }
} else if self.attr.params_style == Some(ParamStyle::Raw) {
quote! { let params: _jsonrpc_core::Result<_> = Ok((params,)); }
} else if self.attr.params_style == Some(ParamStyle::Positional) {
quote! { let params = params.parse::<(#(#param_types, )*)>(); }
} else {
unimplemented!("Server side named parameters are not implemented");
let parse_params = if param_types.is_empty() {
quote! { let params = params.expect_no_params(); }
} else {
match self.attr.params_style.as_ref().unwrap() {
ParamStyle::Raw => quote! { let params: _jsonrpc_core::Result<_> = Ok((params,)); },
ParamStyle::Positional => {
// last arguments that are `Option`-s are optional 'trailing' arguments
let trailing_args_num = param_types.iter().rev().take_while(|t| is_option_type(t)).count();
if trailing_args_num != 0 {
self.params_with_trailing(trailing_args_num, &param_types, tuple_fields)
} else {
quote! { let params = params.parse::<(#(#param_types, )*)>(); }
}
}
ParamStyle::Named => quote! {
#[derive(serde::Deserialize)]
#[allow(non_camel_case_types)]
struct __Params {
#(
#fn_args,
)*
}
let params = params.parse::<__Params>()
.map(|__Params { #(#arg_names, )* }| (#(#arg_names, )*));
},
}
};

let method_ident = self.ident();
let result = &self.trait_item.sig.output;
let extra_closure_args: &Vec<_> = &special_args.iter().cloned().map(|arg| arg.0).collect();
let extra_method_types: &Vec<_> = &special_args.iter().cloned().map(|arg| arg.1).collect();
let extra_closure_args: Vec<_> = special_args.iter().cloned().map(|arg| *arg.pat).collect();
let extra_method_types: Vec<_> = special_args.iter().cloned().map(|arg| *arg.ty).collect();

let closure_args = quote! { base, params, #(#extra_closure_args), * };
let method_sig = quote! { fn(&Self, #(#extra_method_types, ) * #(#param_types), *) #result };
Expand Down Expand Up @@ -301,34 +329,35 @@ impl RpcMethod {
})
}

fn special_args(param_types: &[syn::Type]) -> Vec<(syn::Ident, syn::Type)> {
let meta_arg = param_types.first().and_then(|ty| {
if *ty == parse_quote!(Self::Metadata) {
Some(ty.clone())
} else {
None
}
});
let subscriber_arg = param_types.get(1).and_then(|ty| {
if let syn::Type::Path(path) = ty {
if path.path.segments.iter().any(|s| s.ident == SUBSCRIBER_TYPE_IDENT) {
Some(ty.clone())
} else {
None
fn special_arg(index: usize, arg: syn::PatType) -> Option<syn::PatType> {
match index {
0 if arg.ty == parse_quote!(Self::Metadata) => Some(syn::PatType {
pat: Box::new(syn::Pat::Ident(syn::PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: ident(METADATA_CLOSURE_ARG),
subpat: None,
})),
..arg
}),
1 => match *arg.ty {
syn::Type::Path(ref path) if path.path.segments.iter().any(|s| s.ident == SUBSCRIBER_TYPE_IDENT) => {
Some(syn::PatType {
pat: Box::new(syn::Pat::Ident(syn::PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: ident(SUBSCRIBER_CLOSURE_ARG),
subpat: None,
})),
..arg
})
}
} else {
None
}
});

let mut special_args = Vec::new();
if let Some(meta) = meta_arg {
special_args.push((ident(METADATA_CLOSURE_ARG), meta));
}
if let Some(subscriber) = subscriber_arg {
special_args.push((ident(SUBSCRIBER_CLOSURE_ARG), subscriber));
_ => None,
},
_ => None,
}
special_args
}

fn params_with_trailing(
Expand Down
Loading

0 comments on commit d35c973

Please sign in to comment.