From bc11e9de10fad429ae2932ba9ae03eeb268e5033 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 4 Nov 2022 09:29:26 +0100 Subject: [PATCH] proc macro: support camelCase & snake_case for object params (#921) * proc macro: support camelCase & snake_case for object params * switch to heck for converting case * fix ui test --- proc-macros/Cargo.toml | 1 + proc-macros/src/render_server.rs | 33 +++++++++++++++++-- .../ui/incorrect/rpc/rpc_empty_bounds.stderr | 22 ++++++------- tests/tests/proc_macros.rs | 23 +++++++++++++ 4 files changed, 66 insertions(+), 13 deletions(-) diff --git a/proc-macros/Cargo.toml b/proc-macros/Cargo.toml index ac1070658f..c90eb40756 100644 --- a/proc-macros/Cargo.toml +++ b/proc-macros/Cargo.toml @@ -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"] } diff --git a/proc-macros/src/render_server.rs b/proc-macros/src/render_server.rs index e4ff1c4c5e..abb7e6dff0 100644 --- a/proc-macros/src/render_server.rs +++ b/proc-macros/src/render_server.rs @@ -25,6 +25,7 @@ // DEALINGS IN THE SOFTWARE. use std::collections::HashSet; +use std::str::FromStr; use super::RpcDescription; use crate::attributes::Resource; @@ -32,7 +33,7 @@ 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 { @@ -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); diff --git a/proc-macros/tests/ui/incorrect/rpc/rpc_empty_bounds.stderr b/proc-macros/tests/ui/incorrect/rpc/rpc_empty_bounds.stderr index 3ab57c2a92..650d9df973 100644 --- a/proc-macros/tests/ui/incorrect/rpc/rpc_empty_bounds.stderr +++ b/proc-macros/tests/ui/incorrect/rpc/rpc_empty_bounds.stderr @@ -1,15 +1,15 @@ error[E0277]: the trait bound `::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 `::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 `::Hash` + | note: required by a bound in `RpcModule::::register_method` - --> $WORKSPACE/core/src/server/rpc_module.rs - | - | R: Serialize, - | ^^^^^^^^^ required by this bound in `RpcModule::::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::::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> ::Hash: Deserialize<'de>` is not satisfied --> tests/ui/incorrect/rpc/rpc_empty_bounds.rs:10:1 @@ -17,7 +17,7 @@ error[E0277]: the trait bound `for<'de> ::Hash: Deserialize<'de> 10 | #[rpc(server, client, namespace = "foo", client_bounds(), server_bounds())] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `for<'de> Deserialize<'de>` is not implemented for `::Hash` | - = note: required because of the requirements on the impl of `DeserializeOwned` for `::Hash` + = note: required for `::Hash` to implement `DeserializeOwned` note: required by a bound in `request` --> $WORKSPACE/core/src/client/mod.rs | diff --git a/tests/tests/proc_macros.rs b/tests/tests/proc_macros.rs index 4e33989bd0..17d5b66ee7 100644 --- a/tests/tests/proc_macros.rs +++ b/tests/tests/proc_macros.rs @@ -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::("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::("foo_foo", params).await.unwrap(), 42); +}