Skip to content

Commit

Permalink
feat: alias attribute for proc macros (#442)
Browse files Browse the repository at this point in the history
* feat: alias attribute for proc macros

* implement David's suggested no more unsub field

* keep aliases outside namespace

* revert example

* fix build

* Update proc-macros/src/attributes.rs

Co-authored-by: David <dvdplm@gmail.com>

* grumbles: alias -> aliases

* grumbles v2: alias -> aliases

Co-authored-by: David <dvdplm@gmail.com>
  • Loading branch information
niklasad1 and dvdplm authored Sep 1, 2021
1 parent bf211cb commit 3ad1cc2
Show file tree
Hide file tree
Showing 29 changed files with 177 additions and 67 deletions.
2 changes: 1 addition & 1 deletion examples/proc_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ where
async fn storage_keys(&self, storage_key: StorageKey, hash: Option<Hash>) -> Result<Vec<StorageKey>, Error>;

/// Subscription that takes a `StorageKey` as input and produces a `Vec<Hash>`.
#[subscription(name = "subscribeStorage", unsub = "unsubscribeStorage", item = Vec<Hash>)]
#[subscription(name = "subscribeStorage", item = Vec<Hash>)]
fn subscribe_storage(&self, keys: Option<Vec<StorageKey>>);
}

Expand Down
8 changes: 6 additions & 2 deletions proc-macros/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,19 @@ impl Rpc {
pub(crate) struct Method {
/// Method name
pub name: syn::LitStr,
/// Aliases for the method.
pub aliases: Option<syn::LitStr>,
}

/// Input for the `#[subscription(...)]` attribute.
#[derive(Debug, Clone, FromAttributes)]
pub(crate) struct Subscription {
/// Subscription name
pub name: syn::LitStr,
/// Name of the method to unsubscribe.
pub unsub: syn::LitStr,
/// Type yielded by the subscription.
pub item: syn::Type,
/// Aliases for the subscribe method.
pub aliases: Option<syn::LitStr>,
/// Aliases for the unsubscribe method.
pub unsubscribe_aliases: Option<syn::LitStr>,
}
2 changes: 1 addition & 1 deletion proc-macros/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ fn find_jsonrpsee_crate(http_name: &str, ws_name: &str) -> Result<proc_macro2::T
/// #[method(name = "call")]
/// fn call(&self, a: A) -> JsonRpcResult<B>;
///
/// #[subscription(name = "sub", unsub = "unsub", item = Vec<C>)]
/// #[subscription(name = "sub", item = Vec<C>)]
/// fn sub(&self);
/// }
/// ```
Expand Down
2 changes: 1 addition & 1 deletion proc-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ pub(crate) mod visitor;
/// #[method(name = "bar")]
/// fn sync_method(&self) -> JsonRpcResult<u16>;
///
/// #[subscription(name = "sub", unsub = "unsub", item = String)]
/// #[subscription(name = "sub", item = String)]
/// fn sub(&self);
/// }
///
Expand Down
2 changes: 1 addition & 1 deletion proc-macros/src/render_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ impl RpcDescription {
// Name of the RPC subscription (e.g. `foo_sub`).
let rpc_sub_name = self.rpc_identifier(&sub.name);
// Name of the RPC method to unsubscribe (e.g. `foo_unsub`).
let rpc_unsub_name = self.rpc_identifier(&sub.unsub_method);
let rpc_unsub_name = self.rpc_identifier(&sub.unsubscribe);

// `returns` represent the return type of the *rust method*, which is wrapped
// into the `Subscription` object.
Expand Down
78 changes: 71 additions & 7 deletions proc-macros/src/render_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ impl RpcDescription {

let mut registered = HashSet::new();
let mut errors = Vec::new();
let mut check_name = |name: String, span: Span| {
if registered.contains(&name) {
let mut check_name = |name: &str, span: Span| {
if registered.contains(name) {
let message = format!("{:?} is already defined", name);
errors.push(quote_spanned!(span => compile_error!(#message);));
} else {
registered.insert(name);
registered.insert(name.to_string());
}
};

Expand Down Expand Up @@ -113,7 +113,7 @@ impl RpcDescription {
// `params_seq` is the comma-delimited sequence of parameters.
let (parsing, params_seq) = self.render_params_decoding(&method.params);

check_name(rpc_method_name.clone(), rust_method_name.span());
check_name(&rpc_method_name, rust_method_name.span());

if method.signature.sig.asyncness.is_some() {
handle_register_result(quote! {
Expand Down Expand Up @@ -145,14 +145,14 @@ impl RpcDescription {
// Name of the RPC method to subscribe to (e.g. `foo_sub`).
let rpc_sub_name = self.rpc_identifier(&sub.name);
// Name of the RPC method to unsubscribe (e.g. `foo_sub`).
let rpc_unsub_name = self.rpc_identifier(&sub.unsub_method);
let rpc_unsub_name = self.rpc_identifier(&sub.unsubscribe);
// `parsing` is the code associated with parsing structure from the
// provided `RpcParams` object.
// `params_seq` is the comma-delimited sequence of parameters.
let (parsing, params_seq) = self.render_params_decoding(&sub.params);

check_name(rpc_sub_name.clone(), rust_method_name.span());
check_name(rpc_unsub_name.clone(), rust_method_name.span());
check_name(&rpc_sub_name, rust_method_name.span());
check_name(&rpc_unsub_name, rust_method_name.span());

handle_register_result(quote! {
rpc.register_subscription(#rpc_sub_name, #rpc_unsub_name, |params, sink, context| {
Expand All @@ -163,6 +163,68 @@ impl RpcDescription {
})
.collect::<Vec<_>>();

let method_aliases = self
.methods
.iter()
.map(|method| {
let rpc_name = self.rpc_identifier(&method.name);
let rust_method_name = &method.signature.sig.ident;

// Rust method to invoke (e.g. `self.<foo>(...)`).
let aliases: Vec<TokenStream2> = method
.aliases
.iter()
.map(|alias| {
let alias = alias.trim().to_string();
check_name(&alias, rust_method_name.span());
handle_register_result(quote! {
rpc.register_alias(#alias, #rpc_name)
})
})
.collect();

quote!( #(#aliases)* )
})
.collect::<Vec<_>>();

let subscription_aliases = self
.subscriptions
.iter()
.map(|method| {
let sub_name = self.rpc_identifier(&method.name);
let unsub_name = self.rpc_identifier(&method.unsubscribe);
let rust_method_name = &method.signature.sig.ident;

let sub: Vec<TokenStream2> = method
.aliases
.iter()
.map(|alias| {
let alias = alias.trim().to_string();
check_name(&alias, rust_method_name.span());
handle_register_result(quote! {
rpc.register_alias(#alias, #sub_name)
})
})
.collect();
let unsub: Vec<TokenStream2> = method
.unsubscribe_aliases
.iter()
.map(|alias| {
let alias = alias.trim().to_string();
check_name(&alias, rust_method_name.span());
handle_register_result(quote! {
rpc.register_alias(#alias, #unsub_name)
})
})
.collect();

quote! (
#(#sub)*
#(#unsub)*
)
})
.collect::<Vec<_>>();

let doc_comment = "Collects all the methods and subscriptions defined in the trait \
and adds them into a single `RpcModule`.";

Expand All @@ -178,6 +240,8 @@ impl RpcDescription {
#(#errors)*
#(#methods)*
#(#subscriptions)*
#(#method_aliases)*
#(#subscription_aliases)*

rpc
}
Expand Down
41 changes: 29 additions & 12 deletions proc-macros/src/rpc_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,19 @@ use syn::Attribute;

#[derive(Debug, Clone)]
pub struct RpcMethod {
pub name: syn::LitStr,
pub name: String,
pub params: Vec<(syn::PatIdent, syn::Type)>,
pub returns: Option<syn::Type>,
pub signature: syn::TraitItemMethod,
pub aliases: Vec<String>,
}

impl RpcMethod {
pub fn from_item(mut method: syn::TraitItemMethod) -> Result<Self, syn::Error> {
let attributes = attributes::Method::from_attributes(&method.attrs).respan(&method.attrs.first())?;
let sig = method.sig.clone();
let name = attributes.name;
let name = attributes.name.value();
let aliases = attributes.aliases.map(|a| a.value().split(',').map(Into::into).collect()).unwrap_or_default();
let params: Vec<_> = sig
.inputs
.into_iter()
Expand All @@ -65,27 +67,31 @@ impl RpcMethod {
// We've analyzed attributes and don't need them anymore.
method.attrs.clear();

Ok(Self { name, params, returns, signature: method })
Ok(Self { aliases, name, params, returns, signature: method })
}
}

#[derive(Debug, Clone)]
pub struct RpcSubscription {
pub name: syn::LitStr,
pub unsub_method: syn::LitStr,
pub name: String,
pub unsubscribe: String,
pub params: Vec<(syn::PatIdent, syn::Type)>,
pub item: syn::Type,
pub signature: syn::TraitItemMethod,
pub aliases: Vec<String>,
pub unsubscribe_aliases: Vec<String>,
}

impl RpcSubscription {
pub fn from_item(mut sub: syn::TraitItemMethod) -> Result<Self, syn::Error> {
let attributes = attributes::Subscription::from_attributes(&sub.attrs).respan(&sub.attrs.first())?;
let sig = sub.sig.clone();
let name = attributes.name;
let unsub_method = attributes.unsub;
let name = attributes.name.value();
let unsubscribe = build_unsubscribe_method(&name);
let item = attributes.item;

let aliases = attributes.aliases.map(|a| a.value().split(',').map(Into::into).collect()).unwrap_or_default();
let unsubscribe_aliases =
attributes.unsubscribe_aliases.map(|a| a.value().split(',').map(Into::into).collect()).unwrap_or_default();
let params: Vec<_> = sig
.inputs
.into_iter()
Expand All @@ -101,7 +107,7 @@ impl RpcSubscription {
// We've analyzed attributes and don't need them anymore.
sub.attrs.clear();

Ok(Self { name, unsub_method, params, item, signature: sub })
Ok(Self { name, unsubscribe, unsubscribe_aliases, params, item, signature: sub, aliases })
}
}

Expand Down Expand Up @@ -224,11 +230,11 @@ impl RpcDescription {
/// Examples:
/// For namespace `foo` and method `makeSpam`, result will be `foo_makeSpam`.
/// For no namespace and method `makeSpam` it will be just `makeSpam.
pub(crate) fn rpc_identifier(&self, method: &syn::LitStr) -> String {
pub(crate) fn rpc_identifier(&self, method: &str) -> String {
if let Some(ns) = &self.attrs.namespace {
format!("{}_{}", ns.value(), method.value())
format!("{}_{}", ns.value(), method.trim())
} else {
method.value()
method.to_string()
}
}
}
Expand All @@ -241,3 +247,14 @@ fn has_attr(attrs: &[Attribute], ident: &str) -> bool {
}
false
}

fn build_unsubscribe_method(existing_method: &str) -> String {
let method = existing_method.trim();
let mut new_method = String::from("unsubscribe");
if method.starts_with("subscribe") {
new_method.extend(method.chars().skip(9));
} else {
new_method.push_str(method);
}
new_method
}
13 changes: 13 additions & 0 deletions proc-macros/tests/ui/correct/alias_doesnt_use_namespace.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use jsonrpsee::{proc_macros::rpc, types::JsonRpcResult};

#[rpc(client, server, namespace = "myapi")]
pub trait Rpc {
/// Alias doesn't use the namespace so not duplicated.
#[method(name = "getTemp", aliases = "getTemp")]
async fn async_method(&self, param_a: u8, param_b: String) -> JsonRpcResult<u16>;

#[subscription(name = "getFood", item = String, aliases = "getFood", unsubscribe_aliases = "unsubscribegetFood")]
fn sub(&self);
}

fn main() {}
6 changes: 3 additions & 3 deletions proc-macros/tests/ui/correct/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::{net::SocketAddr, sync::mpsc::channel};

#[rpc(client, server, namespace = "foo")]
pub trait Rpc {
#[method(name = "foo")]
#[method(name = "foo", aliases = "fooAlias, Other")]
async fn async_method(&self, param_a: u8, param_b: String) -> JsonRpcResult<u16>;

#[method(name = "optional_params")]
Expand All @@ -25,10 +25,10 @@ pub trait Rpc {
#[method(name = "bar")]
fn sync_method(&self) -> JsonRpcResult<u16>;

#[subscription(name = "sub", unsub = "unsub", item = String)]
#[subscription(name = "sub", item = String)]
fn sub(&self);

#[subscription(name = "echo", unsub = "no_more_echo", item = u32)]
#[subscription(name = "echo", aliases = "ECHO", item = u32, unsubscribe_aliases = "NotInterested, listenNoMore")]
fn sub_with_params(&self, val: u32);
}

Expand Down
2 changes: 1 addition & 1 deletion proc-macros/tests/ui/correct/only_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub trait Rpc {
#[method(name = "bar")]
fn sync_method(&self) -> JsonRpcResult<u16>;

#[subscription(name = "sub", unsub = "unsub", item = String)]
#[subscription(name = "sub", item = String)]
fn sub(&self);
}

Expand Down
2 changes: 1 addition & 1 deletion proc-macros/tests/ui/correct/only_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub trait Rpc {
#[method(name = "bar")]
fn sync_method(&self) -> JsonRpcResult<u16>;

#[subscription(name = "sub", unsub = "unsub", item = String)]
#[subscription(name = "sub", item = String)]
fn sub(&self);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: `#[method]` got unknown `magic` argument. Supported arguments are `name`
error: `#[method]` got unknown `magic` argument. Supported arguments are `aliases`, `name`
--> $DIR/method_unexpected_field.rs:6:2
|
6 | #[method(name = "foo", magic = false)]
Expand Down
9 changes: 9 additions & 0 deletions proc-macros/tests/ui/incorrect/rpc/rpc_conflicting_alias.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use jsonrpsee::proc_macros::rpc;

#[rpc(client, server)]
pub trait DuplicatedAlias {
#[method(name = "foo", aliases = "foo_dup, foo_dup")]
async fn async_method(&self) -> jsonrpsee::types::JsonRpcResult<u8>;
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: "foo_dup" is already defined
--> $DIR/rpc_conflicting_alias.rs:6:11
|
6 | async fn async_method(&self) -> jsonrpsee::types::JsonRpcResult<u8>;
| ^^^^^^^^^^^^
2 changes: 1 addition & 1 deletion proc-macros/tests/ui/incorrect/sub/sub_async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use jsonrpsee::proc_macros::rpc;
// Subscription method must not be async.
#[rpc(client, server)]
pub trait AsyncSub {
#[subscription(name = "sub", unsub = "unsub", item = u8)]
#[subscription(name = "sub", item = u8)]
async fn sub(&self);
}

Expand Down
2 changes: 1 addition & 1 deletion proc-macros/tests/ui/incorrect/sub/sub_async.stderr
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
error: Subscription methods must not be `async`
--> $DIR/sub_async.rs:6:2
|
6 | / #[subscription(name = "sub", unsub = "unsub", item = u8)]
6 | / #[subscription(name = "sub", item = u8)]
7 | | async fn sub(&self);
| |________________________^
9 changes: 9 additions & 0 deletions proc-macros/tests/ui/incorrect/sub/sub_conflicting_alias.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use jsonrpsee::proc_macros::rpc;

#[rpc(client, server)]
pub trait DuplicatedSubAlias {
#[subscription(name = "alias", item = String, aliases = "hello_is_goodbye", unsubscribe_aliases = "hello_is_goodbye")]
fn async_method(&self);
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: "hello_is_goodbye" is already defined
--> $DIR/sub_conflicting_alias.rs:6:5
|
6 | fn async_method(&self);
| ^^^^^^^^^^^^
2 changes: 1 addition & 1 deletion proc-macros/tests/ui/incorrect/sub/sub_no_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use jsonrpsee::proc_macros::rpc;
// Missing mandatory `item` field.
#[rpc(client, server)]
pub trait NoSubItem {
#[subscription(name = "sub", unsub = "unsub")]
#[subscription(name = "sub")]
fn sub(&self);
}

Expand Down
Loading

0 comments on commit 3ad1cc2

Please sign in to comment.