diff --git a/proc-macros/src/helpers.rs b/proc-macros/src/helpers.rs index 6a22621157..dca992ea5b 100644 --- a/proc-macros/src/helpers.rs +++ b/proc-macros/src/helpers.rs @@ -149,3 +149,33 @@ fn visit_trait(item_trait: &syn::ItemTrait, sub_tys: &[syn::Type]) -> FindAllPar visitor.visit_item_trait(item_trait); visitor } + +/// Checks whether provided type is an `Option<...>`. +pub(crate) fn is_option(ty: &syn::Type) -> bool { + if let syn::Type::Path(path) = ty { + let mut it = path.path.segments.iter().peekable(); + while let Some(seg) = it.next() { + // The leaf segment should be `Option` with or without angled brackets. + if seg.ident == "Option" && it.peek().is_none() { + return true; + } + } + } + + false +} + +#[cfg(test)] +mod tests { + use super::is_option; + use syn::parse_quote; + + #[test] + fn is_option_works() { + assert!(is_option(&parse_quote!(Option))); + // could be a type alias. + assert!(is_option(&parse_quote!(Option))); + assert!(is_option(&parse_quote!(std::option::Option))); + assert!(!is_option(&parse_quote!(foo::bar::Option::Booyah))); + } +} diff --git a/proc-macros/src/render_client.rs b/proc-macros/src/render_client.rs index bd0537412c..1ec17642b9 100644 --- a/proc-macros/src/render_client.rs +++ b/proc-macros/src/render_client.rs @@ -100,7 +100,6 @@ impl RpcDescription { let params = method.params.iter().map(|(param, _param_type)| { quote! { #serde_json::to_value(&#param)? } }); - quote! { vec![ #(#params),* ].into() } @@ -144,7 +143,6 @@ impl RpcDescription { let params = sub.params.iter().map(|(param, _param_type)| { quote! { #serde_json::to_value(&#param)? } }); - quote! { vec![ #(#params),* ].into() } diff --git a/proc-macros/src/render_server.rs b/proc-macros/src/render_server.rs index 956b6d7e89..342363f32d 100644 --- a/proc-macros/src/render_server.rs +++ b/proc-macros/src/render_server.rs @@ -26,7 +26,7 @@ use super::lifetimes::replace_lifetimes; use super::RpcDescription; -use crate::helpers::generate_where_clause; +use crate::helpers::{generate_where_clause, is_option}; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{quote, quote_spanned}; use std::collections::HashSet; @@ -264,15 +264,3 @@ impl RpcDescription { (parsing, params_fields) } } - -/// Checks whether provided type is an `Option<...>`. -fn is_option(ty: &syn::Type) -> bool { - if let syn::Type::Path(path) = ty { - // TODO: Probably not the best way to check whether type is an `Option`. - if path.path.segments.iter().any(|seg| seg.ident == "Option") { - return true; - } - } - - false -} diff --git a/proc-macros/tests/ui/correct/basic.rs b/proc-macros/tests/ui/correct/basic.rs index 2b505b009a..bdc2cead7a 100644 --- a/proc-macros/tests/ui/correct/basic.rs +++ b/proc-macros/tests/ui/correct/basic.rs @@ -2,7 +2,7 @@ use jsonrpsee::{ proc_macros::rpc, - types::{async_trait, JsonRpcResult}, + types::{async_trait, to_json_value, traits::Client, v2::params::JsonRpcParams, JsonRpcResult}, ws_client::*, ws_server::{SubscriptionSink, WsServerBuilder}, }; @@ -13,6 +13,15 @@ pub trait Rpc { #[method(name = "foo")] async fn async_method(&self, param_a: u8, param_b: String) -> JsonRpcResult; + #[method(name = "optional_params")] + async fn optional_params(&self, a: std::option::Option, b: String) -> JsonRpcResult; + + #[method(name = "optional_param")] + async fn optional_param(&self, a: Option) -> JsonRpcResult; + + #[method(name = "array_params")] + async fn array_params(&self, items: Vec) -> JsonRpcResult; + #[method(name = "bar")] fn sync_method(&self) -> JsonRpcResult; @@ -31,6 +40,20 @@ impl RpcServer for RpcServerImpl { Ok(42u16) } + async fn optional_params(&self, a: core::option::Option, _b: String) -> JsonRpcResult { + let res = if a.is_some() { true } else { false }; + Ok(res) + } + + async fn optional_param(&self, a: Option) -> JsonRpcResult { + let res = if a.is_some() { true } else { false }; + Ok(res) + } + + async fn array_params(&self, items: Vec) -> JsonRpcResult { + Ok(items.len() as u64) + } + fn sync_method(&self) -> JsonRpcResult { Ok(10u16) } @@ -71,6 +94,25 @@ async fn main() { assert_eq!(client.async_method(10, "a".into()).await.unwrap(), 42); assert_eq!(client.sync_method().await.unwrap(), 10); + assert_eq!(client.optional_params(None, "a".into()).await.unwrap(), false); + assert_eq!(client.optional_params(Some(1), "a".into()).await.unwrap(), true); + + assert_eq!(client.array_params(vec![1]).await.unwrap(), 1); + assert_eq!( + client + .request::("foo_array_params", vec![to_json_value(Vec::::new()).unwrap()].into()) + .await + .unwrap(), + 0 + ); + + assert_eq!(client.request::("foo_optional_param", vec![].into()).await.unwrap(), false); + assert_eq!(client.request::("foo_optional_param", JsonRpcParams::NoParams).await.unwrap(), false); + assert_eq!( + client.request::("foo_optional_param", vec![to_json_value(Some(1)).unwrap()].into()).await.unwrap(), + true + ); + let mut sub = client.sub().await.unwrap(); let first_recv = sub.next().await.unwrap(); assert_eq!(first_recv, Some("Response_A".to_string())); diff --git a/types/src/v2/params.rs b/types/src/v2/params.rs index 0ca8933e46..6ed267216b 100644 --- a/types/src/v2/params.rs +++ b/types/src/v2/params.rs @@ -95,7 +95,7 @@ pub struct RpcParams<'a>(Option>); impl<'a> RpcParams<'a> { /// Create params pub fn new(raw: Option<&'a str>) -> Self { - Self(raw.map(Into::into)) + Self(raw.map(|r| r.trim().into())) } /// Returns true if the contained JSON is an object @@ -104,8 +104,7 @@ impl<'a> RpcParams<'a> { Some(ref cow) => cow, None => return false, }; - - json.trim_start().starts_with('{') + json.starts_with('{') } /// Obtain a sequence parser, [`RpcParamsSequence`]. @@ -113,7 +112,13 @@ impl<'a> RpcParams<'a> { /// This allows sequential parsing of the incoming params, using an `Iterator`-style API and is useful when the RPC /// request has optional parameters at the tail that may or may not be present. pub fn sequence(&self) -> RpcParamsSequence { - RpcParamsSequence(self.0.as_ref().map(AsRef::as_ref).unwrap_or("")) + let json = match self.0.as_ref() { + // It's assumed that params is `[a,b,c]`, if empty regard as no params. + Some(json) if json == "[]" => "", + Some(json) => json, + None => "", + }; + RpcParamsSequence(json) } /// Attempt to parse all parameters as an array or map into type `T`. @@ -121,6 +126,7 @@ impl<'a> RpcParams<'a> { where T: Deserialize<'a>, { + // NOTE(niklasad1): Option::None is serialized as `null` so we provide that here. let params = self.0.as_ref().map(AsRef::as_ref).unwrap_or("null"); serde_json::from_str(params).map_err(|_| CallError::InvalidParams) } @@ -146,6 +152,8 @@ impl<'a> RpcParams<'a> { /// This will parse the params one at a time, and allows for graceful handling of optional parameters at the tail; other /// use cases are likely better served by [`RpcParams::parse`]. The reason this is not an actual [`Iterator`] is that /// params parsing (often) yields values of different types. +/// +/// Regards empty array `[]` as no parameters provided. #[derive(Debug)] pub struct RpcParamsSequence<'a>(&'a str); @@ -154,7 +162,7 @@ impl<'a> RpcParamsSequence<'a> { where T: Deserialize<'a>, { - let mut json = self.0.trim_start(); + let mut json = self.0; match json.as_bytes().get(0)? { b']' => { @@ -170,7 +178,7 @@ impl<'a> RpcParamsSequence<'a> { match iter.next()? { Ok(value) => { - self.0 = &json[iter.byte_offset()..]; + self.0 = json[iter.byte_offset()..].trim_start(); Some(Ok(value)) } @@ -221,7 +229,7 @@ impl<'a> RpcParamsSequence<'a> { /// seq.optional_next().unwrap(), /// seq.optional_next().unwrap(), /// seq.optional_next().unwrap(), - /// ];; + /// ]; /// /// assert_eq!(params, [Some(1), Some(2), None, None]); /// ``` @@ -406,6 +414,7 @@ mod test { fn params_parse() { let none = RpcParams::new(None); assert!(none.sequence().next::().is_err()); + assert!(none.parse::>().is_ok()); let array_params = RpcParams::new(Some("[1, 2, 3]")); let arr: Result<[u64; 3], _> = array_params.parse(); @@ -427,6 +436,17 @@ mod test { assert!(obj.is_ok()); } + #[test] + fn params_parse_empty_json() { + let array_params = RpcParams::new(Some("[]")); + let arr: Result, _> = array_params.parse(); + assert!(arr.is_ok()); + + let obj_params = RpcParams::new(Some("{}")); + let obj: Result = obj_params.parse(); + assert!(obj.is_ok()); + } + #[test] fn params_sequence_borrows() { let params = RpcParams::new(Some(r#"["foo", "bar"]"#)); @@ -482,4 +502,43 @@ mod test { assert_eq!(dsr.subscription, SubscriptionId::Str("9".into())); assert_eq!(dsr.result, serde_json::json!("offside")); } + + #[test] + fn params_sequence_optional_ignore_empty() { + let params = RpcParams::new(Some(r#"["foo", "bar"]"#)); + let mut seq = params.sequence(); + + assert_eq!(seq.optional_next::<&str>().unwrap(), Some("foo")); + assert_eq!(seq.optional_next::<&str>().unwrap(), Some("bar")); + + let params = RpcParams::new(Some(r#"[]"#)); + let mut seq = params.sequence(); + assert!(seq.optional_next::<&str>().unwrap().is_none()); + + let params = RpcParams::new(Some(r#" [] "#)); + let mut seq = params.sequence(); + assert!(seq.optional_next::<&str>().unwrap().is_none()); + + let params = RpcParams::new(Some(r#"{}"#)); + let mut seq = params.sequence(); + assert!(seq.optional_next::<&str>().is_err(), "JSON object not supported by RpcSequence"); + + let params = RpcParams::new(Some(r#"[12, "[]", [], {}]"#)); + let mut seq = params.sequence(); + assert_eq!(seq.optional_next::().unwrap(), Some(12)); + assert_eq!(seq.optional_next::<&str>().unwrap(), Some("[]")); + assert_eq!(seq.optional_next::>().unwrap(), Some(vec![])); + assert_eq!(seq.optional_next::().unwrap(), Some(serde_json::json!({}))); + } + + #[test] + fn params_sequence_optional_nesting_works() { + let nested = RpcParams::new(Some(r#"[1, [2], [3, 4], [[5], [6,7], []], {"named":7}]"#)); + let mut seq = nested.sequence(); + assert_eq!(seq.optional_next::().unwrap(), Some(1)); + assert_eq!(seq.optional_next::<[i8; 1]>().unwrap(), Some([2])); + assert_eq!(seq.optional_next::>().unwrap(), Some(vec![3, 4])); + assert_eq!(seq.optional_next::>>().unwrap(), Some(vec![vec![5], vec![6, 7], vec![]])); + assert_eq!(seq.optional_next::().unwrap(), Some(serde_json::json!({"named":7}))); + } }