Skip to content

Commit

Permalink
proc macro: support camelCase & snake_case for object params (#921)
Browse files Browse the repository at this point in the history
* proc macro: support camelCase & snake_case for object params

* switch to heck for converting case

* fix ui test
  • Loading branch information
niklasad1 authored Nov 4, 2022
1 parent c4fafff commit bc11e9d
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 13 deletions.
1 change: 1 addition & 0 deletions proc-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "1.0", default-features = false, features = ["extra-traits", "full", "visit", "parsing"] }
proc-macro-crate = "1"
heck = "0.4.0"

[dev-dependencies]
jsonrpsee = { path = "../jsonrpsee", features = ["full"] }
Expand Down
33 changes: 31 additions & 2 deletions proc-macros/src/render_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@
// DEALINGS IN THE SOFTWARE.

use std::collections::HashSet;
use std::str::FromStr;

use super::RpcDescription;
use crate::attributes::Resource;
use crate::helpers::{generate_where_clause, is_option};
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, quote_spanned};
use syn::punctuated::Punctuated;
use syn::{parse_quote, ReturnType, Token};
use syn::{parse_quote, token, AttrStyle, Attribute, Path, PathSegment, ReturnType, Token};

impl RpcDescription {
pub(super) fn render_server(&self) -> Result<TokenStream2, syn::Error> {
Expand Down Expand Up @@ -387,8 +388,36 @@ impl RpcDescription {

let serde = self.jrps_server_item(quote! { core::__reexports::serde });
let serde_crate = serde.to_string();

let fields = params.iter().zip(generics.clone()).map(|((name, _), ty)| {
quote! { #name: #ty, }
let mut alias_vals = String::new();
alias_vals.push_str(&format!(
r#"alias = "{}""#,
heck::ToSnakeCase::to_snake_case(name.ident.to_string().as_str())
));
alias_vals.push(',');
alias_vals.push_str(&format!(
r#"alias = "{}""#,
heck::ToLowerCamelCase::to_lower_camel_case(name.ident.to_string().as_str())
));

let mut punc_attr = Punctuated::new();

punc_attr
.push_value(PathSegment { ident: quote::format_ident!("serde"), arguments: Default::default() });

let serde_alias = Attribute {
pound_token: token::Pound::default(),
style: AttrStyle::Outer,
bracket_token: Default::default(),
path: Path { leading_colon: None, segments: punc_attr },
tokens: TokenStream2::from_str(&format!("({})", alias_vals.as_str())).unwrap(),
};

quote! {
#serde_alias
#name: #ty,
}
});
let destruct = params.iter().map(|(name, _)| quote! { parsed.#name });
let types = params.iter().map(|(_, ty)| ty);
Expand Down
22 changes: 11 additions & 11 deletions proc-macros/tests/ui/incorrect/rpc/rpc_empty_bounds.stderr
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
error[E0277]: the trait bound `<Conf as Config>::Hash: Serialize` is not satisfied
--> tests/ui/incorrect/rpc/rpc_empty_bounds.rs:10:1
|
10 | #[rpc(server, client, namespace = "foo", client_bounds(), server_bounds())]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Serialize` is not implemented for `<Conf as Config>::Hash`
|
--> tests/ui/incorrect/rpc/rpc_empty_bounds.rs:10:1
|
10 | #[rpc(server, client, namespace = "foo", client_bounds(), server_bounds())]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Serialize` is not implemented for `<Conf as Config>::Hash`
|
note: required by a bound in `RpcModule::<Context>::register_method`
--> $WORKSPACE/core/src/server/rpc_module.rs
|
| R: Serialize,
| ^^^^^^^^^ required by this bound in `RpcModule::<Context>::register_method`
= note: this error originates in the attribute macro `rpc` (in Nightly builds, run with -Z macro-backtrace for more info)
--> $WORKSPACE/core/src/server/rpc_module.rs
|
| R: Serialize,
| ^^^^^^^^^ required by this bound in `RpcModule::<Context>::register_method`
= note: this error originates in the attribute macro `rpc` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: the trait bound `for<'de> <Conf as Config>::Hash: Deserialize<'de>` is not satisfied
--> tests/ui/incorrect/rpc/rpc_empty_bounds.rs:10:1
|
10 | #[rpc(server, client, namespace = "foo", client_bounds(), server_bounds())]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `for<'de> Deserialize<'de>` is not implemented for `<Conf as Config>::Hash`
|
= note: required because of the requirements on the impl of `DeserializeOwned` for `<Conf as Config>::Hash`
= note: required for `<Conf as Config>::Hash` to implement `DeserializeOwned`
note: required by a bound in `request`
--> $WORKSPACE/core/src/client/mod.rs
|
Expand Down
23 changes: 23 additions & 0 deletions tests/tests/proc_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,3 +375,26 @@ async fn calls_with_bad_params() {
matches!(err, Error::Call(CallError::Custom (err)) if err.message().contains("invalid type: integer `2`, expected a string") && err.code() == ErrorCode::InvalidParams.code())
);
}

#[tokio::test]
async fn calls_with_object_params_works() {
let server = ServerBuilder::default().build("127.0.0.1:0").await.unwrap();
let addr = server.local_addr().unwrap();
let server_url = format!("ws://{}", addr);
let _handle = server.start(RpcServerImpl.into_rpc()).unwrap();
let client = WsClientBuilder::default().build(&server_url).await.unwrap();

// snake_case params
let mut params = ObjectParams::new();
params.insert("param_a", 0).unwrap();
params.insert("param_b", "0x0").unwrap();

assert_eq!(client.request::<u64, ObjectParams>("foo_foo", params).await.unwrap(), 42);

// camelCase params.
let mut params = ObjectParams::new();
params.insert("paramA", 0).unwrap();
params.insert("paramB", "0x0").unwrap();

assert_eq!(client.request::<u64, ObjectParams>("foo_foo", params).await.unwrap(), 42);
}

0 comments on commit bc11e9d

Please sign in to comment.