diff --git a/benches/bench.rs b/benches/bench.rs index 7c3fe9dc9b..99fc81e58a 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -6,8 +6,10 @@ use futures_util::future::{join_all, FutureExt}; use futures_util::stream::FuturesUnordered; use helpers::{http_client, ws_client, SUB_METHOD_NAME, UNSUB_METHOD_NAME}; use jsonrpsee::core::client::{ClientT, SubscriptionClientT}; +use jsonrpsee::core::params::{ArrayParams, BatchRequestBuilder, ObjectParams}; +use jsonrpsee::core::traits::ToRpcParams; use jsonrpsee::http_client::HeaderMap; -use jsonrpsee::types::{Id, ParamsSer, RequestSer}; +use jsonrpsee::types::{Id, RequestSer}; use pprof::criterion::{Output, PProfProfiler}; use tokio::runtime::Runtime as TokioRuntime; @@ -63,22 +65,46 @@ fn v2_serialize(req: RequestSer<'_>) -> String { } pub fn jsonrpsee_types_v2(crit: &mut Criterion) { - crit.bench_function("jsonrpsee_types_v2_array_ref", |b| { + // Construct the serialized array request using the `RawValue` directly. + crit.bench_function("jsonrpsee_types_array_params_baseline", |b| { b.iter(|| { - let params = &[1_u64.into(), 2_u32.into()]; - let params = ParamsSer::ArrayRef(params); + let params = serde_json::value::RawValue::from_string("[1, 2]".to_string()).unwrap(); + let request = RequestSer::new(&Id::Number(0), "say_hello", Some(params)); v2_serialize(request); }) }); + // Construct the serialized request using the `ArrayParams`. + crit.bench_function("jsonrpsee_types_array_params", |b| { + b.iter(|| { + let mut builder = ArrayParams::new(); + builder.insert(1u64).unwrap(); + builder.insert(2u32).unwrap(); + let params = builder.to_rpc_params().expect("Valid params"); + let request = RequestSer::new(&Id::Number(0), "say_hello", params); + v2_serialize(request); + }) + }); - crit.bench_function("jsonrpsee_types_v2_vec", |b| { + // Construct the serialized object request using the `RawValue` directly. + crit.bench_function("jsonrpsee_types_object_params_baseline", |b| { b.iter(|| { - let params = ParamsSer::Array(vec![1_u64.into(), 2_u32.into()]); + let params = serde_json::value::RawValue::from_string(r#"{"key": 1}"#.to_string()).unwrap(); + let request = RequestSer::new(&Id::Number(0), "say_hello", Some(params)); v2_serialize(request); }) }); + // Construct the serialized request using the `ObjectParams`. + crit.bench_function("jsonrpsee_types_object_params", |b| { + b.iter(|| { + let mut builder = ObjectParams::new(); + builder.insert("key", 1u32).unwrap(); + let params = builder.to_rpc_params().expect("Valid params"); + let request = RequestSer::new(&Id::Number(0), "say_hello", params); + v2_serialize(request); + }) + }); } trait RequestBencher { @@ -129,7 +155,7 @@ fn round_trip(rt: &TokioRuntime, crit: &mut Criterion, client: Arc let bench_name = format!("{}/{}", name, method); crit.bench_function(&request.group_name(&bench_name), |b| { b.to_async(rt).iter(|| async { - black_box(client.request::(method, None).await.unwrap()); + black_box(client.request::(method, ArrayParams::new()).await.unwrap()); }) }); } @@ -139,7 +165,12 @@ fn sub_round_trip(rt: &TokioRuntime, crit: &mut Criterion, client: Arc(SUB_METHOD_NAME, None, UNSUB_METHOD_NAME).await.unwrap()); + black_box( + client + .subscribe::(SUB_METHOD_NAME, ArrayParams::new(), UNSUB_METHOD_NAME) + .await + .unwrap(), + ); }) }); group.bench_function("subscribe_response", |b| { @@ -149,7 +180,10 @@ fn sub_round_trip(rt: &TokioRuntime, crit: &mut Criterion, client: Arc(SUB_METHOD_NAME, None, UNSUB_METHOD_NAME).await.unwrap() + client + .subscribe::(SUB_METHOD_NAME, ArrayParams::new(), UNSUB_METHOD_NAME) + .await + .unwrap() }) }) }, @@ -166,7 +200,10 @@ fn sub_round_trip(rt: &TokioRuntime, crit: &mut Criterion, client: Arc(SUB_METHOD_NAME, None, UNSUB_METHOD_NAME).await.unwrap() + client + .subscribe::(SUB_METHOD_NAME, ArrayParams::new(), UNSUB_METHOD_NAME) + .await + .unwrap() }) }, |sub| { @@ -191,7 +228,10 @@ fn batch_round_trip( let bench_name = format!("{}/{}", name, method); let mut group = crit.benchmark_group(request.group_name(&bench_name)); for batch_size in [2, 5, 10, 50, 100usize].iter() { - let batch = vec![(method, None); *batch_size]; + let mut batch = BatchRequestBuilder::new(); + for _ in 0..*batch_size { + batch.insert(method, ArrayParams::new()).unwrap(); + } group.throughput(Throughput::Elements(*batch_size as u64)); group.bench_with_input(BenchmarkId::from_parameter(batch_size), batch_size, |b, _| { b.to_async(rt).iter(|| async { client.batch_request::(batch.clone()).await.unwrap() }) @@ -227,7 +267,7 @@ fn ws_concurrent_conn_calls(rt: &TokioRuntime, crit: &mut Criterion, url: &str, let futs = FuturesUnordered::new(); for _ in 0..10 { - futs.push(client.request::(methods[0], None)); + futs.push(client.request::(methods[0], ArrayParams::new())); } join_all(futs).await; @@ -267,13 +307,17 @@ fn ws_concurrent_conn_subs(rt: &TokioRuntime, crit: &mut Criterion, url: &str, n let futs = FuturesUnordered::new(); for _ in 0..10 { - let fut = client.subscribe::(SUB_METHOD_NAME, None, UNSUB_METHOD_NAME).then( - |sub| async move { + let fut = client + .subscribe::( + SUB_METHOD_NAME, + ArrayParams::new(), + UNSUB_METHOD_NAME, + ) + .then(|sub| async move { let mut s = sub.unwrap(); s.next().await.unwrap().unwrap() - }, - ); + }); futs.push(Box::pin(fut)); } @@ -301,7 +345,7 @@ fn http_concurrent_conn_calls(rt: &TokioRuntime, crit: &mut Criterion, url: &str |clients| async { let tasks = clients.map(|client| { rt.spawn(async move { - client.request::(method, None).await.unwrap(); + client.request::(method, ArrayParams::new()).await.unwrap(); }) }); join_all(tasks).await; @@ -333,7 +377,7 @@ fn http_custom_headers_round_trip( crit.bench_function(&request.group_name(&bench_name), |b| { b.to_async(rt).iter(|| async { - black_box(client.request::(method_name, None).await.unwrap()); + black_box(client.request::(method_name, ArrayParams::new()).await.unwrap()); }) }); } diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index 541459c03e..d7feb54176 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -28,11 +28,13 @@ use std::sync::Arc; use std::time::Duration; use crate::transport::HttpTransportClient; -use crate::types::{ErrorResponse, Id, NotificationSer, ParamsSer, RequestSer, Response}; +use crate::types::{ErrorResponse, Id, NotificationSer, RequestSer, Response}; use async_trait::async_trait; use hyper::http::HeaderMap; use jsonrpsee_core::client::{CertificateStore, ClientT, IdKind, RequestIdManager, Subscription, SubscriptionClientT}; +use jsonrpsee_core::params::BatchRequestBuilder; use jsonrpsee_core::tracing::RpcTracing; +use jsonrpsee_core::traits::ToRpcParams; use jsonrpsee_core::{Error, TEN_MB_SIZE_BYTES}; use jsonrpsee_types::error::CallError; use rustc_hash::FxHashMap; @@ -166,9 +168,13 @@ pub struct HttpClient { #[async_trait] impl ClientT for HttpClient { - async fn notification<'a>(&self, method: &'a str, params: Option>) -> Result<(), Error> { + async fn notification(&self, method: &str, params: Params) -> Result<(), Error> + where + Params: ToRpcParams + Send, + { let trace = RpcTracing::notification(method); async { + let params = params.to_rpc_params()?; let notif = serde_json::to_string(&NotificationSer::new(method, params)).map_err(Error::ParseError)?; let fut = self.transport.send(notif); @@ -184,12 +190,15 @@ impl ClientT for HttpClient { } /// Perform a request towards the server. - async fn request<'a, R>(&self, method: &'a str, params: Option>) -> Result + async fn request(&self, method: &str, params: Params) -> Result where R: DeserializeOwned, + Params: ToRpcParams + Send, { let guard = self.id_manager.next_request_id()?; let id = guard.inner(); + let params = params.to_rpc_params()?; + let request = RequestSer::new(&id, method, params); let trace = RpcTracing::method_call(method); @@ -225,10 +234,11 @@ impl ClientT for HttpClient { .await } - async fn batch_request<'a, R>(&self, batch: Vec<(&'a str, Option>)>) -> Result, Error> + async fn batch_request<'a, R>(&self, batch: BatchRequestBuilder<'a>) -> Result, Error> where R: DeserializeOwned + Default + Clone, { + let batch = batch.build(); let guard = self.id_manager.next_request_ids(batch.len())?; let ids: Vec = guard.inner(); let trace = RpcTracing::batch(); @@ -279,13 +289,14 @@ impl ClientT for HttpClient { #[async_trait] impl SubscriptionClientT for HttpClient { /// Send a subscription request to the server. Not implemented for HTTP; will always return [`Error::HttpNotImplemented`]. - async fn subscribe<'a, N>( + async fn subscribe<'a, N, Params>( &self, _subscribe_method: &'a str, - _params: Option>, + _params: Params, _unsubscribe_method: &'a str, ) -> Result, Error> where + Params: ToRpcParams + Send, N: DeserializeOwned, { Err(Error::HttpNotImplemented) diff --git a/client/http-client/src/tests.rs b/client/http-client/src/tests.rs index 17435828fc..c6d6b61925 100644 --- a/client/http-client/src/tests.rs +++ b/client/http-client/src/tests.rs @@ -25,9 +25,10 @@ // DEALINGS IN THE SOFTWARE. use crate::types::error::{ErrorCode, ErrorObject}; -use crate::types::ParamsSer; + use crate::HttpClientBuilder; use jsonrpsee_core::client::{ClientT, IdKind}; +use jsonrpsee_core::params::BatchRequestBuilder; use jsonrpsee_core::rpc_params; use jsonrpsee_core::Error; use jsonrpsee_test_utils::helpers::*; @@ -52,10 +53,8 @@ async fn method_call_with_wrong_id_kind() { http_server_with_hardcoded_response(ok_response(exp.into(), Id::Num(0))).with_default_timeout().await.unwrap(); let uri = format!("http://{}", server_addr); let client = HttpClientBuilder::default().id_format(IdKind::String).build(&uri).unwrap(); - assert!(matches!( - client.request::("o", None).with_default_timeout().await.unwrap(), - Err(Error::InvalidRequestId) - )); + let res: Result = client.request("o", rpc_params![]).with_default_timeout().await.unwrap(); + assert!(matches!(res, Err(Error::InvalidRequestId))); } #[tokio::test] @@ -67,7 +66,7 @@ async fn method_call_with_id_str() { .unwrap(); let uri = format!("http://{}", server_addr); let client = HttpClientBuilder::default().id_format(IdKind::String).build(&uri).unwrap(); - let response: String = client.request::("o", None).with_default_timeout().await.unwrap().unwrap(); + let response: String = client.request("o", rpc_params![]).with_default_timeout().await.unwrap().unwrap(); assert_eq!(&response, exp); } @@ -77,7 +76,7 @@ async fn notification_works() { let uri = format!("http://{}", server_addr); let client = HttpClientBuilder::default().build(&uri).unwrap(); client - .notification("i_dont_care_about_the_response_because_the_server_should_not_respond", None) + .notification("i_dont_care_about_the_response_because_the_server_should_not_respond", rpc_params![]) .with_default_timeout() .await .unwrap() @@ -137,8 +136,10 @@ async fn subscription_response_to_request() { #[tokio::test] async fn batch_request_works() { - let batch_request = - vec![("say_hello", rpc_params![]), ("say_goodbye", rpc_params![0_u64, 1, 2]), ("get_swag", None)]; + let mut batch_request = BatchRequestBuilder::new(); + batch_request.insert("say_hello", rpc_params![]).unwrap(); + batch_request.insert("say_goodbye", rpc_params![0_u64, 1, 2]).unwrap(); + batch_request.insert("get_swag", rpc_params![]).unwrap(); let server_response = r#"[{"jsonrpc":"2.0","result":"hello","id":0}, {"jsonrpc":"2.0","result":"goodbye","id":1}, {"jsonrpc":"2.0","result":"here's your swag","id":2}]"#.to_string(); let response = run_batch_request_with_response(batch_request, server_response).with_default_timeout().await.unwrap().unwrap(); @@ -147,16 +148,18 @@ async fn batch_request_works() { #[tokio::test] async fn batch_request_out_of_order_response() { - let batch_request = - vec![("say_hello", rpc_params! {}), ("say_goodbye", rpc_params![0_u64, 1, 2]), ("get_swag", None)]; + let mut batch_request = BatchRequestBuilder::new(); + batch_request.insert("say_hello", rpc_params![]).unwrap(); + batch_request.insert("say_goodbye", rpc_params![0_u64, 1, 2]).unwrap(); + batch_request.insert("get_swag", rpc_params![]).unwrap(); let server_response = r#"[{"jsonrpc":"2.0","result":"here's your swag","id":2}, {"jsonrpc":"2.0","result":"hello","id":0}, {"jsonrpc":"2.0","result":"goodbye","id":1}]"#.to_string(); let response = run_batch_request_with_response(batch_request, server_response).with_default_timeout().await.unwrap().unwrap(); assert_eq!(response, vec!["hello".to_string(), "goodbye".to_string(), "here's your swag".to_string()]); } -async fn run_batch_request_with_response<'a>( - batch: Vec<(&'a str, Option>)>, +async fn run_batch_request_with_response( + batch: BatchRequestBuilder<'_>, response: String, ) -> Result, Error> { let server_addr = http_server_with_hardcoded_response(response).with_default_timeout().await.unwrap(); @@ -169,7 +172,7 @@ async fn run_request_with_response(response: String) -> Result { let server_addr = http_server_with_hardcoded_response(response).with_default_timeout().await.unwrap(); let uri = format!("http://{}", server_addr); let client = HttpClientBuilder::default().build(&uri).unwrap(); - client.request("say_hello", None).with_default_timeout().await.unwrap() + client.request("say_hello", rpc_params![]).with_default_timeout().await.unwrap() } fn assert_jsonrpc_error_response(err: Error, exp: ErrorObjectOwned) { diff --git a/client/ws-client/src/tests.rs b/client/ws-client/src/tests.rs index 11ec298c08..641051b784 100644 --- a/client/ws-client/src/tests.rs +++ b/client/ws-client/src/tests.rs @@ -26,12 +26,12 @@ #![cfg(test)] use crate::types::error::{ErrorCode, ErrorObject}; -use crate::types::ParamsSer; + use crate::WsClientBuilder; use jsonrpsee_core::client::{ClientT, SubscriptionClientT}; use jsonrpsee_core::client::{IdKind, Subscription}; -use jsonrpsee_core::rpc_params; -use jsonrpsee_core::Error; +use jsonrpsee_core::params::BatchRequestBuilder; +use jsonrpsee_core::{rpc_params, Error}; use jsonrpsee_test_utils::helpers::*; use jsonrpsee_test_utils::mocks::{Id, WebSocketTestServer}; use jsonrpsee_test_utils::TimeoutFutureExt; @@ -62,7 +62,7 @@ async fn method_call_with_wrong_id_kind() { let client = WsClientBuilder::default().id_format(IdKind::String).build(&uri).with_default_timeout().await.unwrap().unwrap(); - let err = client.request::("o", None).with_default_timeout().await.unwrap(); + let err: Result = client.request("o", rpc_params![]).with_default_timeout().await.unwrap(); assert!(matches!(err, Err(Error::RestartNeeded(e)) if e == "Invalid request ID")); } @@ -79,7 +79,7 @@ async fn method_call_with_id_str() { let uri = format!("ws://{}", server.local_addr()); let client = WsClientBuilder::default().id_format(IdKind::String).build(&uri).with_default_timeout().await.unwrap().unwrap(); - let response: String = client.request::("o", None).with_default_timeout().await.unwrap().unwrap(); + let response: String = client.request("o", rpc_params![]).with_default_timeout().await.unwrap().unwrap(); assert_eq!(&response, exp); } @@ -92,7 +92,7 @@ async fn notif_works() { .unwrap(); let uri = to_ws_uri_string(server.local_addr()); let client = WsClientBuilder::default().build(&uri).with_default_timeout().await.unwrap().unwrap(); - assert!(client.notification("notif", None).with_default_timeout().await.unwrap().is_ok()); + assert!(client.notification("notif", rpc_params![]).with_default_timeout().await.unwrap().is_ok()); } #[tokio::test] @@ -153,7 +153,7 @@ async fn subscription_works() { let client = WsClientBuilder::default().build(&uri).with_default_timeout().await.unwrap().unwrap(); { let mut sub: Subscription = client - .subscribe("subscribe_hello", None, "unsubscribe_hello") + .subscribe("subscribe_hello", rpc_params![], "unsubscribe_hello") .with_default_timeout() .await .unwrap() @@ -226,7 +226,10 @@ async fn notification_without_polling_doesnt_make_client_unuseable() { #[tokio::test] async fn batch_request_works() { - let batch_request = vec![("say_hello", None), ("say_goodbye", rpc_params![0_u64, 1, 2]), ("get_swag", None)]; + let mut batch_request = BatchRequestBuilder::new(); + batch_request.insert("say_hello", rpc_params![]).unwrap(); + batch_request.insert("say_goodbye", rpc_params![0_u64, 1, 2]).unwrap(); + batch_request.insert("get_swag", rpc_params![]).unwrap(); let server_response = r#"[{"jsonrpc":"2.0","result":"hello","id":0}, {"jsonrpc":"2.0","result":"goodbye","id":1}, {"jsonrpc":"2.0","result":"here's your swag","id":2}]"#.to_string(); let response = run_batch_request_with_response(batch_request, server_response).with_default_timeout().await.unwrap().unwrap(); @@ -235,7 +238,10 @@ async fn batch_request_works() { #[tokio::test] async fn batch_request_out_of_order_response() { - let batch_request = vec![("say_hello", None), ("say_goodbye", rpc_params![0_u64, 1, 2]), ("get_swag", None)]; + let mut batch_request = BatchRequestBuilder::new(); + batch_request.insert("say_hello", rpc_params![]).unwrap(); + batch_request.insert("say_goodbye", rpc_params![0_u64, 1, 2]).unwrap(); + batch_request.insert("get_swag", rpc_params![]).unwrap(); let server_response = r#"[{"jsonrpc":"2.0","result":"here's your swag","id":2}, {"jsonrpc":"2.0","result":"hello","id":0}, {"jsonrpc":"2.0","result":"goodbye","id":1}]"#.to_string(); let response = run_batch_request_with_response(batch_request, server_response).with_default_timeout().await.unwrap().unwrap(); @@ -260,15 +266,16 @@ async fn is_connected_works() { let client = WsClientBuilder::default().build(&uri).with_default_timeout().await.unwrap().unwrap(); assert!(client.is_connected()); - client.request::("say_hello", None).with_default_timeout().await.unwrap().unwrap_err(); + let res: Result = client.request("say_hello", rpc_params![]).with_default_timeout().await.unwrap(); + res.unwrap_err(); // give the background thread some time to terminate. tokio::time::sleep(std::time::Duration::from_millis(100)).await; assert!(!client.is_connected()) } -async fn run_batch_request_with_response<'a>( - batch: Vec<(&'a str, Option>)>, +async fn run_batch_request_with_response( + batch: BatchRequestBuilder<'_>, response: String, ) -> Result, Error> { let server = WebSocketTestServer::with_hardcoded_response("127.0.0.1:0".parse().unwrap(), response) @@ -287,7 +294,7 @@ async fn run_request_with_response(response: String) -> Result { .unwrap(); let uri = format!("ws://{}", server.local_addr()); let client = WsClientBuilder::default().build(&uri).with_default_timeout().await.unwrap().unwrap(); - client.request("say_hello", None).with_default_timeout().await.unwrap() + client.request("say_hello", rpc_params![]).with_default_timeout().await.unwrap() } fn assert_error_response(err: Error, exp: ErrorObjectOwned) { @@ -326,6 +333,6 @@ async fn redirections() { // It's connected assert!(client.is_connected()); // It works - let response = client.request::("anything", None).with_default_timeout().await.unwrap(); - assert_eq!(response.unwrap(), String::from(expected)); + let response: String = client.request("anything", rpc_params![]).with_default_timeout().await.unwrap().unwrap(); + assert_eq!(response, String::from(expected)); } diff --git a/core/src/client/async_client/helpers.rs b/core/src/client/async_client/helpers.rs index bda9ef83fb..b19c2206c1 100644 --- a/core/src/client/async_client/helpers.rs +++ b/core/src/client/async_client/helpers.rs @@ -34,10 +34,10 @@ use futures_util::future::{self, Either}; use jsonrpsee_types::error::CallError; use jsonrpsee_types::response::SubscriptionError; -use jsonrpsee_types::{ - ErrorResponse, Id, Notification, ParamsSer, RequestSer, Response, SubscriptionId, SubscriptionResponse, -}; +use jsonrpsee_types::{ErrorResponse, Id, Notification, RequestSer, Response, SubscriptionId, SubscriptionResponse}; use serde_json::Value as JsonValue; +use crate::params::ArrayParams; +use crate::traits::ToRpcParams; /// Attempts to process a batch response. /// @@ -222,10 +222,12 @@ pub(crate) fn build_unsubscribe_message( sub_id: SubscriptionId<'static>, ) -> Option { let (unsub_req_id, _, unsub, sub_id) = manager.remove_subscription(sub_req_id, sub_id)?; - let sub_id_slice: &[JsonValue] = &[sub_id.into()]; - // TODO: https://github.com/paritytech/jsonrpsee/issues/275 - let params = ParamsSer::ArrayRef(sub_id_slice); - let raw = serde_json::to_string(&RequestSer::new(&unsub_req_id, &unsub, Some(params))).ok()?; + + let mut params = ArrayParams::new(); + params.insert(sub_id).ok()?; + let params = params.to_rpc_params().ok()?; + + let raw = serde_json::to_string(&RequestSer::new(&unsub_req_id, &unsub, params)).ok()?; Some(RequestMessage { raw, id: unsub_req_id, send_back: None }) } diff --git a/core/src/client/async_client/manager.rs b/core/src/client/async_client/manager.rs index ab1267aac3..41cc7274d2 100644 --- a/core/src/client/async_client/manager.rs +++ b/core/src/client/async_client/manager.rs @@ -181,7 +181,7 @@ impl RequestManager { } } - /// Inserts a handler for incoming notifications + /// Inserts a handler for incoming notifications. pub(crate) fn insert_notification_handler( &mut self, method: &str, @@ -195,7 +195,7 @@ impl RequestManager { } } - /// Removes a notification handler + /// Removes a notification handler. pub(crate) fn remove_notification_handler(&mut self, method: String) -> Result<(), Error> { if self.notification_handlers.remove(&method).is_some() { Ok(()) @@ -224,7 +224,7 @@ impl RequestManager { } } - /// Tries to complete a pending batch request + /// Tries to complete a pending batch request. /// /// Returns `Some` if the subscription was completed otherwise `None`. pub(crate) fn complete_pending_batch(&mut self, batch: Vec) -> Option { @@ -237,7 +237,7 @@ impl RequestManager { } } - /// Tries to complete a pending call.. + /// Tries to complete a pending call. /// /// Returns `Some` if the call was completed otherwise `None`. pub(crate) fn complete_pending_call(&mut self, request_id: RequestId) -> Option { diff --git a/core/src/client/async_client/mod.rs b/core/src/client/async_client/mod.rs index 201a74c1e7..40935a4a19 100644 --- a/core/src/client/async_client/mod.rs +++ b/core/src/client/async_client/mod.rs @@ -27,11 +27,13 @@ use futures_util::sink::SinkExt; use futures_util::stream::StreamExt; use futures_util::FutureExt; use jsonrpsee_types::{ - response::SubscriptionError, ErrorResponse, Id, Notification, NotificationSer, ParamsSer, RequestSer, Response, + response::SubscriptionError, ErrorResponse, Id, Notification, NotificationSer, RequestSer, Response, SubscriptionResponse, }; use serde::de::DeserializeOwned; use tracing_futures::Instrument; +use crate::params::BatchRequestBuilder; +use crate::traits::ToRpcParams; use super::{FrontToBack, IdKind, RequestIdManager}; @@ -174,7 +176,7 @@ impl ClientBuilder { ping_interval, on_close_tx, ) - .await; + .await; }); Client { to_back, @@ -190,9 +192,9 @@ impl ClientBuilder { #[cfg(all(feature = "async-wasm-client", target_arch = "wasm32"))] #[cfg_attr(docsrs, doc(cfg(feature = "async-wasm-client")))] pub fn build_with_wasm(self, sender: S, receiver: R) -> Client - where - S: TransportSenderT, - R: TransportReceiverT, + where + S: TransportSenderT, + R: TransportReceiverT, { let (to_back, from_front) = mpsc::channel(self.max_concurrent_requests); let (err_tx, err_rx) = oneshot::channel(); @@ -272,10 +274,14 @@ impl Drop for Client { } #[async_trait] -impl ClientT for Client { - async fn notification<'a>(&self, method: &'a str, params: Option>) -> Result<(), Error> { +impl ClientT for Client +{ + async fn notification(&self, method: &str, params: Params) -> Result<(), Error> + where + Params: ToRpcParams + Send { // NOTE: we use this to guard against max number of concurrent requests. let _req_id = self.id_manager.next_request_id()?; + let params = params.to_rpc_params()?; let notif = NotificationSer::new(method, params); let trace = RpcTracing::batch(); @@ -292,13 +298,14 @@ impl ClientT for Client { Either::Right((_, _)) => Err(Error::RequestTimeout), } } - .instrument(trace.into_span()) - .await + .instrument(trace.into_span()) + .await } - async fn request<'a, R>(&self, method: &'a str, params: Option>) -> Result + async fn request(&self, method: &str, params: Params) -> Result where R: DeserializeOwned, + Params: ToRpcParams + Send { let (send_back_tx, send_back_rx) = oneshot::channel(); let guard = self.id_manager.next_request_id()?; @@ -306,6 +313,7 @@ impl ClientT for Client { let trace = RpcTracing::method_call(method); async { + let params = params.to_rpc_params()?; let raw = serde_json::to_string(&RequestSer::new(&id, method, params)).map_err(Error::ParseError)?; tx_log_from_str(&raw, self.max_log_length); @@ -330,16 +338,17 @@ impl ClientT for Client { serde_json::from_value(json_value).map_err(Error::ParseError) } - .instrument(trace.into_span()) - .await + .instrument(trace.into_span()) + .await } - async fn batch_request<'a, R>(&self, batch: Vec<(&'a str, Option>)>) -> Result, Error> + async fn batch_request<'a, R>(&self, batch: BatchRequestBuilder<'a>) -> Result, Error> where - R: DeserializeOwned + Default + Clone, + R: DeserializeOwned + Default + Clone { let trace = RpcTracing::batch(); async { + let batch = batch.build(); let guard = self.id_manager.next_request_ids(batch.len())?; let batch_ids: Vec = guard.inner(); let mut batches = Vec::with_capacity(batch.len()); @@ -374,8 +383,8 @@ impl ClientT for Client { json_values.into_iter().map(|val| serde_json::from_value(val).map_err(Error::ParseError)).collect() } - .instrument(trace.into_span()) - .await + .instrument(trace.into_span()) + .await } } @@ -385,14 +394,15 @@ impl SubscriptionClientT for Client { /// /// The `subscribe_method` and `params` are used to ask for the subscription towards the /// server. The `unsubscribe_method` is used to close the subscription. - async fn subscribe<'a, N>( + async fn subscribe<'a, Notif, Params>( &self, subscribe_method: &'a str, - params: Option>, + params: Params, unsubscribe_method: &'a str, - ) -> Result, Error> + ) -> Result, Error> where - N: DeserializeOwned, + Params: ToRpcParams + Send, + Notif: DeserializeOwned { if subscribe_method == unsubscribe_method { return Err(Error::SubscriptionNameConflict(unsubscribe_method.to_owned())); @@ -401,6 +411,7 @@ impl SubscriptionClientT for Client { let guard = self.id_manager.next_request_ids(2)?; let mut ids: Vec = guard.inner(); let trace = RpcTracing::method_call(subscribe_method); + let params = params.to_rpc_params()?; async { let id = ids[0].clone(); @@ -439,8 +450,8 @@ impl SubscriptionClientT for Client { Ok(Subscription::new(self.to_back.clone(), notifs_rx, SubscriptionKind::Subscription(sub_id))) } - .instrument(trace.into_span()) - .await + .instrument(trace.into_span()) + .await } /// Subscribe to a specific method. diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index 44566b55e2..438d93c4db 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -38,14 +38,19 @@ use futures_channel::{mpsc, oneshot}; use futures_util::future::FutureExt; use futures_util::sink::SinkExt; use futures_util::stream::{Stream, StreamExt}; -use jsonrpsee_types::{Id, ParamsSer, SubscriptionId}; +use jsonrpsee_types::{Id, SubscriptionId}; use serde::de::DeserializeOwned; use serde_json::Value as JsonValue; +use crate::params::BatchRequestBuilder; +use crate::traits::ToRpcParams; +// Re-exports for the `rpc_params` macro. #[doc(hidden)] pub mod __reexports { - pub use crate::to_json_value; - pub use jsonrpsee_types::ParamsSer; + // Needs to be in scope for `ArrayParams` to implement it. + pub use crate::traits::ToRpcParams; + // Main builder object for constructing the rpc parameters. + pub use crate::params::ArrayParams; } cfg_async_client! { @@ -57,12 +62,15 @@ cfg_async_client! { #[async_trait] pub trait ClientT { /// Send a [notification request](https://www.jsonrpc.org/specification#notification) - async fn notification<'a>(&self, method: &'a str, params: Option>) -> Result<(), Error>; + async fn notification(&self, method: &str, params: Params) -> Result<(), Error> + where + Params: ToRpcParams + Send; /// Send a [method call request](https://www.jsonrpc.org/specification#request_object). - async fn request<'a, R>(&self, method: &'a str, params: Option>) -> Result + async fn request(&self, method: &str, params: Params) -> Result where - R: DeserializeOwned; + R: DeserializeOwned, + Params: ToRpcParams + Send; /// Send a [batch request](https://www.jsonrpc.org/specification#batch). /// @@ -70,7 +78,7 @@ pub trait ClientT { /// /// Returns `Ok` if all requests in the batch were answered successfully. /// Returns `Error` if any of the requests in batch fails. - async fn batch_request<'a, R>(&self, batch: Vec<(&'a str, Option>)>) -> Result, Error> + async fn batch_request<'a, R>(&self, batch: BatchRequestBuilder<'a>) -> Result, Error> where R: DeserializeOwned + Default + Clone; } @@ -90,13 +98,14 @@ pub trait SubscriptionClientT: ClientT { /// /// The `Notif` param is a generic type to receive generic subscriptions, see [`Subscription`] for further /// documentation. - async fn subscribe<'a, Notif>( + async fn subscribe<'a, Notif, Params>( &self, subscribe_method: &'a str, - params: Option>, + params: Params, unsubscribe_method: &'a str, ) -> Result, Error> where + Params: ToRpcParams + Send, Notif: DeserializeOwned; /// Register a method subscription, this is used to filter only server notifications that a user is interested in. @@ -172,21 +181,25 @@ pub trait TransportReceiverT: 'static { async fn receive(&mut self) -> Result; } +/// Convert the given values to a [`crate::params::ArrayParams`] as expected by a +/// jsonrpsee Client (http or websocket). +/// +/// # Panics +/// +/// Panics if the serialization of parameters fails. #[macro_export] -/// Convert the given values to a [`jsonrpsee_types::ParamsSer`] as expected by a jsonrpsee Client (http or websocket). macro_rules! rpc_params { ($($param:expr),*) => { { - let mut __params = vec![]; + let mut params = $crate::client::__reexports::ArrayParams::new(); $( - __params.push($crate::client::__reexports::to_json_value($param).expect("json serialization is infallible; qed.")); + if let Err(err) = params.insert($param) { + panic!("Parameter `{}` cannot be serialized: {:?}", stringify!($param), err); + } )* - Some($crate::client::__reexports::ParamsSer::Array(__params)) + params } }; - () => { - None - } } /// Subscription kind diff --git a/core/src/lib.rs b/core/src/lib.rs index 2c68b076cc..aba1f50aa2 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -39,6 +39,9 @@ pub mod error; /// Traits pub mod traits; +/// RPC Parameters. +pub mod params; + cfg_http_helpers! { pub mod http_helpers; } diff --git a/core/src/params.rs b/core/src/params.rs new file mode 100644 index 0000000000..e4af5cd979 --- /dev/null +++ b/core/src/params.rs @@ -0,0 +1,246 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! RPC parameters. + +use crate::traits::ToRpcParams; +use crate::Error; +use serde::Serialize; +use serde_json::value::RawValue; + +/// Helper module for building parameters. +mod params_builder { + use serde::Serialize; + + /// Initial number of bytes for a parameter length. + const PARAM_BYTES_CAPACITY: usize = 128; + + /// Generic parameter builder that serializes parameters to bytes. + /// This produces a JSON compatible String. + /// + /// The implementation relies on `Vec` to hold the serialized + /// parameters in memory for the following reasons: + /// 1. Other serialization methods than `serde_json::to_writer` would internally + /// have an extra heap allocation for temporarily holding the value in memory. + /// 2. `io::Write` is not implemented for `String` required for serialization. + #[derive(Debug)] + pub(crate) struct ParamsBuilder { + bytes: Vec, + start: char, + end: char, + } + + impl ParamsBuilder { + /// Construct a new [`ParamsBuilder`] with custom start and end tokens. + /// The inserted values are wrapped by the _start_ and _end_ tokens. + fn new(start: char, end: char) -> Self { + ParamsBuilder { bytes: Vec::new(), start, end } + } + + /// Construct a new [`ParamsBuilder`] for positional parameters equivalent to a JSON array object. + pub(crate) fn positional() -> Self { + Self::new('[', ']') + } + + /// Construct a new [`ParamsBuilder`] for named parameters equivalent to a JSON map object. + pub(crate) fn named() -> Self { + Self::new('{', '}') + } + + /// Initialize the internal vector if it is empty: + /// - allocate [`PARAM_BYTES_CAPACITY`] to avoid resizing + /// - add the `start` character. + /// + /// # Note + /// + /// Initialization is needed prior to inserting elements. + fn maybe_initialize(&mut self) { + if self.bytes.is_empty() { + self.bytes.reserve(PARAM_BYTES_CAPACITY); + self.bytes.push(self.start as u8); + } + } + + /// Insert a named value (key, value) pair into the builder. + /// The _name_ and _value_ are delimited by the `:` token. + pub(crate) fn insert_named(&mut self, name: &str, value: P) -> Result<(), serde_json::Error> { + self.maybe_initialize(); + + serde_json::to_writer(&mut self.bytes, name)?; + self.bytes.push(b':'); + serde_json::to_writer(&mut self.bytes, &value)?; + self.bytes.push(b','); + + Ok(()) + } + + /// Insert a plain value into the builder. + pub(crate) fn insert(&mut self, value: P) -> Result<(), serde_json::Error> { + self.maybe_initialize(); + + serde_json::to_writer(&mut self.bytes, &value)?; + self.bytes.push(b','); + + Ok(()) + } + + /// Finish the building process and return a JSON compatible string. + pub(crate) fn build(mut self) -> Option { + if self.bytes.is_empty() { + return None; + } + + let idx = self.bytes.len() - 1; + if self.bytes[idx] == b',' { + self.bytes[idx] = self.end as u8; + } else { + self.bytes.push(self.end as u8); + } + + // Safety: This is safe because JSON does not emit invalid UTF-8. + Some(unsafe { String::from_utf8_unchecked(self.bytes) }) + } + } +} + +/// Parameter builder that serializes named value parameters to a JSON compatible string. +/// This is the equivalent of a JSON Map object `{ key: value }`. +/// +/// # Examples +/// +/// ```rust +/// +/// use jsonrpsee_core::params::ObjectParams; +/// +/// let mut builder = ObjectParams::new(); +/// builder.insert("param1", 1); +/// builder.insert("param2", "abc"); +/// +/// // Use RPC parameters... +/// ``` +#[derive(Debug)] +pub struct ObjectParams(params_builder::ParamsBuilder); + +impl ObjectParams { + /// Construct a new [`ObjectParams`]. + pub fn new() -> Self { + Self::default() + } + + /// Insert a named value (key, value) pair into the builder. + /// The _name_ and _value_ are delimited by the `:` token. + pub fn insert(&mut self, name: &str, value: P) -> Result<(), serde_json::Error> { + self.0.insert_named(name, value) + } +} + +impl Default for ObjectParams { + fn default() -> Self { + Self(params_builder::ParamsBuilder::named()) + } +} + +impl ToRpcParams for ObjectParams { + fn to_rpc_params(self) -> Result>, Error> { + if let Some(json) = self.0.build() { + RawValue::from_string(json).map(Some).map_err(Error::ParseError) + } else { + Ok(None) + } + } +} + +/// Parameter builder that serializes plain value parameters to a JSON compatible string. +/// This is the equivalent of a JSON Array object `[ value0, value1, .., valueN ]`. +/// +/// # Examples +/// +/// ```rust +/// +/// use jsonrpsee_core::params::ArrayParams; +/// +/// let mut builder = ArrayParams::new(); +/// builder.insert("param1"); +/// builder.insert(1); +/// +/// // Use RPC parameters... +/// ``` +#[derive(Debug)] +pub struct ArrayParams(params_builder::ParamsBuilder); + +impl ArrayParams { + /// Construct a new [`ArrayParams`]. + pub fn new() -> Self { + Self::default() + } + + /// Insert a plain value into the builder. + pub fn insert(&mut self, value: P) -> Result<(), serde_json::Error> { + self.0.insert(value) + } +} + +impl Default for ArrayParams { + fn default() -> Self { + Self(params_builder::ParamsBuilder::positional()) + } +} + +impl ToRpcParams for ArrayParams { + fn to_rpc_params(self) -> Result>, Error> { + if let Some(json) = self.0.build() { + RawValue::from_string(json).map(Some).map_err(Error::ParseError) + } else { + Ok(None) + } + } +} + +/// Initial number of parameters in a batch request. +const BATCH_PARAMS_NUM_CAPACITY: usize = 4; + +/// Request builder that serializes RPC parameters to construct a valid batch parameter. +/// This is the equivalent of chaining multiple RPC requests. +#[derive(Clone, Debug, Default)] +pub struct BatchRequestBuilder<'a>(Vec<(&'a str, Option>)>); + +impl<'a> BatchRequestBuilder<'a> { + /// Construct a new [`BatchRequestBuilder`]. + pub fn new() -> Self { + Self(Vec::with_capacity(BATCH_PARAMS_NUM_CAPACITY)) + } + + /// Inserts the RPC method with provided parameters into the builder. + pub fn insert(&mut self, method: &'a str, value: Params) -> Result<(), Error> { + self.0.push((method, value.to_rpc_params()?)); + Ok(()) + } + + /// Finish the building process and return a valid batch parameter. + pub fn build(self) -> Vec<(&'a str, Option>)> { + self.0 + } +} diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index d19aad8295..c077217435 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -339,7 +339,7 @@ impl Methods { /// /// The params must be serializable as JSON array, see [`ToRpcParams`] for further documentation. /// - /// Returns the decoded value of the `result field` in JSON-RPC response if succesful. + /// Returns the decoded value of the `result field` in JSON-RPC response if successful. /// /// # Examples /// @@ -363,7 +363,7 @@ impl Methods { params: Params, ) -> Result { let params = params.to_rpc_params()?; - let req = Request::new(method.into(), Some(¶ms), Id::Number(0)); + let req = Request::new(method.into(), params.as_ref().map(|p| p.as_ref()), Id::Number(0)); tracing::trace!("[Methods::call] Method: {:?}, params: {:?}", method, params); let (resp, _, _) = self.inner_call(req).await; @@ -459,7 +459,7 @@ impl Methods { /// ``` /// #[tokio::main] /// async fn main() { - /// use jsonrpsee::{RpcModule, types::EmptyParams}; + /// use jsonrpsee::{RpcModule, types::EmptyServerParams}; /// /// let mut module = RpcModule::new(()); /// module.register_subscription("hi", "hi", "goodbye", |_, mut sink, _| { @@ -467,7 +467,7 @@ impl Methods { /// Ok(()) /// }).unwrap(); /// - /// let mut sub = module.subscribe("hi", EmptyParams::new()).await.unwrap(); + /// let mut sub = module.subscribe("hi", EmptyServerParams::new()).await.unwrap(); /// // In this case we ignore the subscription ID, /// let (sub_resp, _sub_id) = sub.next::().await.unwrap().unwrap(); /// assert_eq!(&sub_resp, "one answer"); @@ -475,7 +475,7 @@ impl Methods { /// ``` pub async fn subscribe(&self, sub_method: &str, params: impl ToRpcParams) -> Result { let params = params.to_rpc_params()?; - let req = Request::new(sub_method.into(), Some(¶ms), Id::Number(0)); + let req = Request::new(sub_method.into(), params.as_ref().map(|p| p.as_ref()), Id::Number(0)); tracing::trace!("[Methods::subscribe] Method: {}, params: {:?}", sub_method, params); diff --git a/core/src/traits.rs b/core/src/traits.rs index 3734300ca9..9b3532236f 100644 --- a/core/src/traits.rs +++ b/core/src/traits.rs @@ -24,29 +24,96 @@ // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use crate::Error; use jsonrpsee_types::SubscriptionId; use serde::Serialize; use serde_json::value::RawValue; -/// Marker trait for types that can be serialized as JSON array/sequence. +/// Marker trait for types that can be serialized as JSON compatible strings. /// -/// If your type isn't a sequence, for example `String`, `usize` or similar -/// you must insert it in a tuple, slice, array or Vec for it to work. -pub trait ToRpcParams: Serialize { - /// Serialize the type as a JSON array. - fn to_rpc_params(&self) -> Result, serde_json::Error> { - serde_json::to_string(&self).map(|json| RawValue::from_string(json).expect("JSON String; qed")) - } +/// This trait ensures the correctness of the RPC parameters. +/// +/// # Note +/// +/// Please consider using the [`crate::params::ArrayParams`] and [`crate::params::ObjectParams`] than +/// implementing this trait. +/// +/// # Examples +/// +/// ## Implementation for hard-coded strings +/// +/// ```rust +/// use jsonrpsee_core::traits::ToRpcParams; +/// use serde_json::value::RawValue; +/// use jsonrpsee_core::Error; +/// +/// struct ManualParam; +/// +/// impl ToRpcParams for ManualParam { +/// fn to_rpc_params(self) -> Result>, Error> { +/// // Manually define a valid JSONRPC parameter. +/// RawValue::from_string("[1, \"2\", 3]".to_string()).map(Some).map_err(Error::ParseError) +/// } +/// } +/// ``` +/// +/// ## Implementation for JSON serializable structures +/// +/// ```rust +/// use jsonrpsee_core::traits::ToRpcParams; +/// use serde_json::value::RawValue; +/// use serde::Serialize; +/// use jsonrpsee_core::Error; +/// +/// #[derive(Serialize)] +/// struct SerParam { +/// param_1: u8, +/// param_2: String, +/// }; +/// +/// impl ToRpcParams for SerParam { +/// fn to_rpc_params(self) -> Result>, Error> { +/// let s = String::from_utf8(serde_json::to_vec(&self)?).expect("Valid UTF8 format"); +/// RawValue::from_string(s).map(Some).map_err(Error::ParseError) +/// } +/// } +/// ``` +pub trait ToRpcParams { + /// Consume and serialize the type as a JSON raw value. + fn to_rpc_params(self) -> Result>, Error>; +} + +// To not bound the `ToRpcParams: Serialize` define a custom implementation +// for types which are serializable. +macro_rules! to_rpc_params_impl { + () => { + fn to_rpc_params(self) -> Result>, Error> { + let json = serde_json::to_string(&self).map_err(Error::ParseError)?; + RawValue::from_string(json).map(Some).map_err(Error::ParseError) + } + }; } -impl ToRpcParams for &[P] {} -impl ToRpcParams for Vec

{} -impl ToRpcParams for [P; N] where [P; N]: Serialize {} +impl ToRpcParams for &[P] { + to_rpc_params_impl!(); +} + +impl ToRpcParams for Vec

{ + to_rpc_params_impl!(); +} +impl ToRpcParams for [P; N] +where + [P; N]: Serialize, +{ + to_rpc_params_impl!(); +} macro_rules! tuple_impls { ($($len:expr => ($($n:tt $name:ident)+))+) => { $( - impl<$($name: Serialize),+> ToRpcParams for ($($name,)+) {} + impl<$($name: Serialize),+> ToRpcParams for ($($name,)+) { + to_rpc_params_impl!(); + } )+ } } diff --git a/examples/examples/core_client.rs b/examples/examples/core_client.rs index 820c2c12c5..aa741e935f 100644 --- a/examples/examples/core_client.rs +++ b/examples/examples/core_client.rs @@ -28,6 +28,7 @@ use std::net::SocketAddr; use jsonrpsee::client_transport::ws::{Uri, WsTransportClientBuilder}; use jsonrpsee::core::client::{Client, ClientBuilder, ClientT}; +use jsonrpsee::rpc_params; use jsonrpsee::ws_server::{RpcModule, WsServerBuilder}; #[tokio::main] @@ -42,7 +43,7 @@ async fn main() -> anyhow::Result<()> { let (tx, rx) = WsTransportClientBuilder::default().build(uri).await?; let client: Client = ClientBuilder::default().build_with_tokio(tx, rx); - let response: String = client.request("say_hello", None).await?; + let response: String = client.request("say_hello", rpc_params![]).await?; tracing::info!("response: {:?}", response); Ok(()) diff --git a/examples/examples/http.rs b/examples/examples/http.rs index 030e96356f..7350c5f9fb 100644 --- a/examples/examples/http.rs +++ b/examples/examples/http.rs @@ -42,7 +42,7 @@ async fn main() -> anyhow::Result<()> { let url = format!("http://{}", server_addr); let client = HttpClientBuilder::default().build(url)?; - let params = rpc_params!(1_u64, 2, 3); + let params = rpc_params![1_u64, 2, 3]; let response: Result = client.request("say_hello", params).await; tracing::info!("r: {:?}", response); diff --git a/examples/examples/http_middleware.rs b/examples/examples/http_middleware.rs index dfa3f1d1e4..f2b95ced07 100644 --- a/examples/examples/http_middleware.rs +++ b/examples/examples/http_middleware.rs @@ -37,6 +37,7 @@ use tower_http::LatencyUnit; use jsonrpsee::core::client::ClientT; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::http_server::{HttpServerBuilder, HttpServerHandle, RpcModule}; +use jsonrpsee::rpc_params; #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -49,10 +50,10 @@ async fn main() -> anyhow::Result<()> { let url = format!("http://{}", addr); let client = HttpClientBuilder::default().build(&url)?; - let response: String = client.request("say_hello", None).await?; + let response: String = client.request("say_hello", rpc_params![]).await?; println!("[main]: response: {:?}", response); - let _response: Result = client.request("unknown_method", None).await; - let _ = client.request::("say_hello", None).await?; + let _response: Result = client.request("unknown_method", rpc_params![]).await; + let _: String = client.request("say_hello", rpc_params![]).await?; Ok(()) } diff --git a/examples/examples/http_proxy_middleware.rs b/examples/examples/http_proxy_middleware.rs index 53dd2d15b8..a04bd005f3 100644 --- a/examples/examples/http_proxy_middleware.rs +++ b/examples/examples/http_proxy_middleware.rs @@ -45,6 +45,7 @@ use jsonrpsee::core::client::ClientT; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::http_server::middleware::proxy_get_request::ProxyGetRequestLayer; use jsonrpsee::http_server::{HttpServerBuilder, HttpServerHandle, RpcModule}; +use jsonrpsee::rpc_params; #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -58,7 +59,7 @@ async fn main() -> anyhow::Result<()> { // Use RPC client to get the response of `say_hello` method. let client = HttpClientBuilder::default().build(&url)?; - let response: String = client.request("say_hello", None).await?; + let response: String = client.request("say_hello", rpc_params![]).await?; println!("[main]: response: {:?}", response); // Use hyper client to manually submit a `GET /health` request. diff --git a/examples/examples/logger_http.rs b/examples/examples/logger_http.rs index 6803d6a9da..342d41f2c6 100644 --- a/examples/examples/logger_http.rs +++ b/examples/examples/logger_http.rs @@ -31,6 +31,7 @@ use jsonrpsee::core::client::ClientT; use jsonrpsee::core::logger::{self, Body, MethodKind, Params, Request}; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::http_server::{HttpServerBuilder, HttpServerHandle, RpcModule}; +use jsonrpsee::rpc_params; #[derive(Clone)] struct Timings; @@ -67,10 +68,10 @@ async fn main() -> anyhow::Result<()> { let url = format!("http://{}", addr); let client = HttpClientBuilder::default().build(&url)?; - let response: String = client.request("say_hello", None).await?; + let response: String = client.request("say_hello", rpc_params![]).await?; println!("response: {:?}", response); - let _response: Result = client.request("unknown_method", None).await; - let _ = client.request::("say_hello", None).await?; + let _response: Result = client.request("unknown_method", rpc_params![]).await; + let _: String = client.request("say_hello", rpc_params![]).await?; Ok(()) } diff --git a/examples/examples/logger_ws.rs b/examples/examples/logger_ws.rs index 9ae4d8f11d..cf8220c79e 100644 --- a/examples/examples/logger_ws.rs +++ b/examples/examples/logger_ws.rs @@ -29,6 +29,7 @@ use std::time::Instant; use jsonrpsee::core::client::ClientT; use jsonrpsee::core::logger::{self, Headers, MethodKind, Params}; +use jsonrpsee::rpc_params; use jsonrpsee::ws_client::WsClientBuilder; use jsonrpsee::ws_server::{RpcModule, WsServerBuilder}; @@ -75,10 +76,10 @@ async fn main() -> anyhow::Result<()> { let url = format!("ws://{}", addr); let client = WsClientBuilder::default().build(&url).await?; - let response: String = client.request("say_hello", None).await?; + let response: String = client.request("say_hello", rpc_params![]).await?; println!("response: {:?}", response); - let _response: Result = client.request("unknown_method", None).await; - let _ = client.request::("say_hello", None).await?; + let _response: Result = client.request("unknown_method", rpc_params![]).await; + let _: String = client.request("say_hello", rpc_params![]).await?; Ok(()) } diff --git a/examples/examples/multi_logger.rs b/examples/examples/multi_logger.rs index 651789d36c..360de2ba5a 100644 --- a/examples/examples/multi_logger.rs +++ b/examples/examples/multi_logger.rs @@ -132,11 +132,11 @@ async fn main() -> anyhow::Result<()> { let url = format!("ws://{}", addr); let client = WsClientBuilder::default().build(&url).await?; - let response: String = client.request("say_hello", None).await?; + let response: String = client.request("say_hello", rpc_params![]).await?; println!("response: {:?}", response); - let _response: Result = client.request("unknown_method", None).await; - let _ = client.request::("say_hello", None).await?; - client.request::<()>("thready", rpc_params![4]).await?; + let _response: Result = client.request("unknown_method", rpc_params![]).await; + let _: String = client.request("say_hello", rpc_params![]).await?; + client.request("thready", rpc_params![4]).await?; Ok(()) } diff --git a/examples/examples/ws.rs b/examples/examples/ws.rs index 7f27351c38..4f8e9b6fb7 100644 --- a/examples/examples/ws.rs +++ b/examples/examples/ws.rs @@ -27,6 +27,7 @@ use std::net::SocketAddr; use jsonrpsee::core::client::ClientT; +use jsonrpsee::rpc_params; use jsonrpsee::ws_client::WsClientBuilder; use jsonrpsee::ws_server::{RpcModule, WsServerBuilder}; use tracing_subscriber::util::SubscriberInitExt; @@ -42,7 +43,7 @@ async fn main() -> anyhow::Result<()> { let url = format!("ws://{}", addr); let client = WsClientBuilder::default().build(&url).await?; - let response: String = client.request("say_hello", None).await?; + let response: String = client.request("say_hello", rpc_params![]).await?; tracing::info!("response: {:?}", response); Ok(()) diff --git a/examples/examples/ws_pubsub_with_params.rs b/examples/examples/ws_pubsub_with_params.rs index 8eb22be9b4..16ab365f08 100644 --- a/examples/examples/ws_pubsub_with_params.rs +++ b/examples/examples/ws_pubsub_with_params.rs @@ -28,7 +28,7 @@ use std::net::SocketAddr; use std::time::Duration; use futures::StreamExt; -use jsonrpsee::core::client::SubscriptionClientT; +use jsonrpsee::core::client::{Subscription, SubscriptionClientT}; use jsonrpsee::core::error::SubscriptionClosed; use jsonrpsee::rpc_params; use jsonrpsee::ws_client::WsClientBuilder; @@ -49,13 +49,13 @@ async fn main() -> anyhow::Result<()> { let client = WsClientBuilder::default().build(&url).await?; // Subscription with a single parameter - let mut sub_params_one = - client.subscribe::>("sub_one_param", rpc_params![3], "unsub_one_param").await?; + let mut sub_params_one: Subscription> = + client.subscribe("sub_one_param", rpc_params![3], "unsub_one_param").await?; tracing::info!("subscription with one param: {:?}", sub_params_one.next().await); // Subscription with multiple parameters - let mut sub_params_two = - client.subscribe::("sub_params_two", rpc_params![2, 5], "unsub_params_two").await?; + let mut sub_params_two: Subscription = + client.subscribe("sub_params_two", rpc_params![2, 5], "unsub_params_two").await?; tracing::info!("subscription with two params: {:?}", sub_params_two.next().await); Ok(()) diff --git a/proc-macros/src/render_client.rs b/proc-macros/src/render_client.rs index 64348242ef..652f3486ff 100644 --- a/proc-macros/src/render_client.rs +++ b/proc-macros/src/render_client.rs @@ -54,7 +54,6 @@ impl RpcDescription { // Doc-comment to be associated with the client. let doc_comment = format!("Client implementation for the `{}` RPC API.", &self.trait_def.ident); - let trait_impl = quote! { #[#async_trait] #[doc = #doc_comment] @@ -95,7 +94,7 @@ impl RpcDescription { }; // Encoded parameters for the request. - let parameters = self.encode_params(&method.params, &method.param_kind, &method.signature); + let parameter_builder = self.encode_params(&method.params, &method.param_kind, &method.signature); // Doc-comment to be associated with the method. let docs = &method.docs; // Mark the method as deprecated, if previously declared as so. @@ -105,7 +104,8 @@ impl RpcDescription { #docs #deprecated async fn #rust_method_name(#rust_method_params) -> #returns { - self.#called_method(#rpc_method_name, #parameters).await + let params = { #parameter_builder }; + self.#called_method(#rpc_method_name, params).await } }; Ok(method) @@ -130,14 +130,15 @@ impl RpcDescription { let returns = quote! { Result<#sub_type<#item>, #jrps_error> }; // Encoded parameters for the request. - let parameters = self.encode_params(&sub.params, &sub.param_kind, &sub.signature); + let parameter_builder = self.encode_params(&sub.params, &sub.param_kind, &sub.signature); // Doc-comment to be associated with the method. let docs = &sub.docs; let method = quote! { #docs async fn #rust_method_name(#rust_method_params) -> #returns { - self.subscribe(#rpc_sub_name, #parameters, #rpc_unsub_name).await + let params = #parameter_builder; + self.subscribe(#rpc_sub_name, params, #rpc_unsub_name).await } }; Ok(method) @@ -149,39 +150,48 @@ impl RpcDescription { param_kind: &ParamKind, signature: &syn::TraitItemMethod, ) -> TokenStream2 { - if !params.is_empty() { - let serde_json = self.jrps_client_item(quote! { core::__reexports::serde_json }); - let params = params.iter().map(|(param, _param_type)| { - quote! { #serde_json::to_value(&#param)? } + let jsonrpsee = self.jsonrpsee_client_path.as_ref().unwrap(); + + if params.is_empty() { + return quote!({ + #jsonrpsee::core::params::ArrayParams::new() }); - match param_kind { - ParamKind::Map => { - let jsonrpsee = self.jsonrpsee_client_path.as_ref().unwrap(); - // Extract parameter names. - let param_names = extract_param_names(&signature.sig); - // Combine parameter names and values into tuples. - let params = param_names.iter().zip(params).map(|pair| { - let param = pair.0; - let value = pair.1; - quote! { (#param, #value) } - }); - quote! { - Some(#jsonrpsee::types::ParamsSer::Map( - std::collections::BTreeMap::<&str, #serde_json::Value>::from( - [#(#params),*] - ) - ) - ) - } - } - ParamKind::Array => { - quote! { - Some(vec![ #(#params),* ].into()) - } - } + } + + match param_kind { + ParamKind::Map => { + // Extract parameter names. + let param_names = extract_param_names(&signature.sig); + // Combine parameter names and values to pass them as parameters. + let params_insert = param_names.iter().zip(params).map(|pair| { + let name = pair.0; + // Throw away the type. + let (value, _value_type) = pair.1; + quote!(#name, #value) + }); + quote!({ + let mut params = #jsonrpsee::core::params::ObjectParams::new(); + #( + if let Err(err) = params.insert( #params_insert ) { + panic!("Parameter `{}` cannot be serialized: {:?}", stringify!( #params_insert ), err); + } + )* + params + }) + } + ParamKind::Array => { + // Throw away the type. + let params = params.iter().map(|(param, _param_type)| param); + quote!({ + let mut params = #jsonrpsee::core::params::ArrayParams::new(); + #( + if let Err(err) = params.insert( #params ) { + panic!("Parameter `{}` cannot be serialized: {:?}", stringify!( #params ), err); + } + )* + params + }) } - } else { - quote! { None } } } } diff --git a/proc-macros/tests/ui/correct/basic.rs b/proc-macros/tests/ui/correct/basic.rs index 98e48b0d65..4aabddfaf7 100644 --- a/proc-macros/tests/ui/correct/basic.rs +++ b/proc-macros/tests/ui/correct/basic.rs @@ -5,6 +5,7 @@ use std::net::SocketAddr; use jsonrpsee::core::{async_trait, client::ClientT, RpcResult}; use jsonrpsee::proc_macros::rpc; use jsonrpsee::rpc_params; +use jsonrpsee::core::params::ArrayParams; use jsonrpsee::types::SubscriptionResult; use jsonrpsee::ws_client::*; use jsonrpsee::ws_server::{SubscriptionSink, WsServerBuilder}; @@ -103,11 +104,10 @@ async fn main() { 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", rpc_params![Vec::::new()]).await.unwrap(), 0); + assert_eq!(client.request::("foo_array_params", rpc_params![Vec::::new()]).await.unwrap(), 0); - assert_eq!(client.request::("foo_optional_param", rpc_params![]).await.unwrap(), false); - assert_eq!(client.request::("foo_optional_param", None).await.unwrap(), false); - assert_eq!(client.request::("foo_optional_param", rpc_params![1]).await.unwrap(), true); + assert_eq!(client.request::("foo_optional_param", rpc_params![]).await.unwrap(), false); + assert_eq!(client.request::("foo_optional_param", rpc_params![1]).await.unwrap(), true); let mut sub = client.sub().await.unwrap(); let first_recv = sub.next().await.transpose().unwrap(); 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 ec2f0f0892..3ab57c2a92 100644 --- a/proc-macros/tests/ui/incorrect/rpc/rpc_empty_bounds.stderr +++ b/proc-macros/tests/ui/incorrect/rpc/rpc_empty_bounds.stderr @@ -21,6 +21,6 @@ error[E0277]: the trait bound `for<'de> ::Hash: Deserialize<'de> note: required by a bound in `request` --> $WORKSPACE/core/src/client/mod.rs | - | R: DeserializeOwned; + | R: DeserializeOwned, | ^^^^^^^^^^^^^^^^ required by this bound in `request` = note: this error originates in the attribute macro `rpc` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index 18fc9963a2..b06976b196 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -35,6 +35,7 @@ use helpers::{http_server, http_server_with_access_control, websocket_server, we use hyper::http::HeaderValue; use jsonrpsee::core::client::{ClientT, IdKind, Subscription, SubscriptionClientT}; use jsonrpsee::core::error::SubscriptionClosed; +use jsonrpsee::core::params::{ArrayParams, BatchRequestBuilder}; use jsonrpsee::core::{Error, JsonValue}; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::http_server::AccessControlBuilder; @@ -61,8 +62,9 @@ async fn ws_subscription_works() { let server_url = format!("ws://{}", server_addr); let client = WsClientBuilder::default().build(&server_url).await.unwrap(); let mut hello_sub: Subscription = - client.subscribe("subscribe_hello", None, "unsubscribe_hello").await.unwrap(); - let mut foo_sub: Subscription = client.subscribe("subscribe_foo", None, "unsubscribe_foo").await.unwrap(); + client.subscribe("subscribe_hello", rpc_params![], "unsubscribe_hello").await.unwrap(); + let mut foo_sub: Subscription = + client.subscribe("subscribe_foo", rpc_params![], "unsubscribe_foo").await.unwrap(); for _ in 0..10 { let hello = hello_sub.next().await.unwrap().unwrap(); @@ -80,7 +82,8 @@ async fn ws_unsubscription_works() { let server_url = format!("ws://{}", server_addr); let client = WsClientBuilder::default().max_concurrent_requests(1).build(&server_url).await.unwrap(); - let mut sub: Subscription = client.subscribe("subscribe_foo", None, "unsubscribe_foo").await.unwrap(); + let mut sub: Subscription = + client.subscribe("subscribe_foo", rpc_params![], "unsubscribe_foo").await.unwrap(); // It's technically possible to have race-conditions between the notifications and the unsubscribe message. // So let's wait for the first notification and then unsubscribe. @@ -93,7 +96,8 @@ async fn ws_unsubscription_works() { // Wait until a slot is available, as only one concurrent call is allowed. // Then when this finishes we know that unsubscribe call has been finished. for _ in 0..30 { - if client.request::("say_hello", rpc_params![]).await.is_ok() { + let res: Result = client.request("say_hello", rpc_params![]).await; + if res.is_ok() { success = true; break; } @@ -126,7 +130,7 @@ async fn ws_method_call_works() { let server_addr = websocket_server().await; let server_url = format!("ws://{}", server_addr); let client = WsClientBuilder::default().build(&server_url).await.unwrap(); - let response: String = client.request("say_hello", None).await.unwrap(); + let response: String = client.request("say_hello", rpc_params![]).await.unwrap(); assert_eq!(&response, "hello"); } @@ -137,7 +141,7 @@ async fn ws_method_call_str_id_works() { let server_addr = websocket_server().await; let server_url = format!("ws://{}", server_addr); let client = WsClientBuilder::default().id_format(IdKind::String).build(&server_url).await.unwrap(); - let response: String = client.request("say_hello", None).await.unwrap(); + let response: String = client.request("say_hello", rpc_params![]).await.unwrap(); assert_eq!(&response, "hello"); } @@ -148,7 +152,7 @@ async fn http_method_call_works() { let (server_addr, _handle) = http_server().await; let uri = format!("http://{}", server_addr); let client = HttpClientBuilder::default().build(&uri).unwrap(); - let response: String = client.request("say_hello", None).await.unwrap(); + let response: String = client.request("say_hello", rpc_params![]).await.unwrap(); assert_eq!(&response, "hello"); } @@ -159,7 +163,7 @@ async fn http_method_call_str_id_works() { let (server_addr, _handle) = http_server().await; let uri = format!("http://{}", server_addr); let client = HttpClientBuilder::default().id_format(IdKind::String).build(&uri).unwrap(); - let response: String = client.request("say_hello", None).await.unwrap(); + let response: String = client.request("say_hello", rpc_params![]).await.unwrap(); assert_eq!(&response, "hello"); } @@ -171,8 +175,10 @@ async fn http_concurrent_method_call_limits_works() { let uri = format!("http://{}", server_addr); let client = HttpClientBuilder::default().max_concurrent_requests(1).build(&uri).unwrap(); - let (first, second) = - tokio::join!(client.request::("say_hello", None), client.request::("say_hello", None),); + let (first, second) = tokio::join!( + client.request::("say_hello", rpc_params!()), + client.request::("say_hello", rpc_params![]), + ); assert!(first.is_ok()); assert!(matches!(second, Err(Error::MaxSlotsExceeded))); @@ -189,9 +195,9 @@ async fn ws_subscription_several_clients() { for _ in 0..10 { let client = WsClientBuilder::default().build(&server_url).await.unwrap(); let hello_sub: Subscription = - client.subscribe("subscribe_hello", None, "unsubscribe_hello").await.unwrap(); + client.subscribe("subscribe_hello", rpc_params![], "unsubscribe_hello").await.unwrap(); let foo_sub: Subscription = - client.subscribe("subscribe_foo", None, "unsubscribe_foo").await.unwrap(); + client.subscribe("subscribe_foo", rpc_params![], "unsubscribe_foo").await.unwrap(); clients.push((client, hello_sub, foo_sub)) } } @@ -208,8 +214,9 @@ async fn ws_subscription_several_clients_with_drop() { let client = WsClientBuilder::default().max_notifs_per_subscription(u32::MAX as usize).build(&server_url).await.unwrap(); let hello_sub: Subscription = - client.subscribe("subscribe_hello", None, "unsubscribe_hello").await.unwrap(); - let foo_sub: Subscription = client.subscribe("subscribe_foo", None, "unsubscribe_foo").await.unwrap(); + client.subscribe("subscribe_hello", rpc_params![], "unsubscribe_hello").await.unwrap(); + let foo_sub: Subscription = + client.subscribe("subscribe_foo", rpc_params![], "unsubscribe_foo").await.unwrap(); clients.push((client, hello_sub, foo_sub)) } @@ -253,7 +260,7 @@ async fn ws_subscription_without_polling_doesnt_make_client_unuseable() { let client = WsClientBuilder::default().max_notifs_per_subscription(4).build(&server_url).await.unwrap(); let mut hello_sub: Subscription = - client.subscribe("subscribe_hello", None, "unsubscribe_hello").await.unwrap(); + client.subscribe("subscribe_hello", rpc_params![], "unsubscribe_hello").await.unwrap(); // don't poll the subscription stream for 2 seconds, should be full now. tokio::time::sleep(Duration::from_secs(2)).await; @@ -267,11 +274,11 @@ async fn ws_subscription_without_polling_doesnt_make_client_unuseable() { assert!(hello_sub.next().await.is_none()); // The client should still be useable => make sure it still works. - let _hello_req: JsonValue = client.request("say_hello", None).await.unwrap(); + let _hello_req: JsonValue = client.request("say_hello", rpc_params![]).await.unwrap(); // The same subscription should be possible to register again. let mut other_sub: Subscription = - client.subscribe("subscribe_hello", None, "unsubscribe_hello").await.unwrap(); + client.subscribe("subscribe_hello", rpc_params![], "unsubscribe_hello").await.unwrap(); other_sub.next().await.unwrap().unwrap(); } @@ -288,7 +295,7 @@ async fn ws_making_more_requests_than_allowed_should_not_deadlock() { for _ in 0..6 { let c = client.clone(); - requests.push(tokio::spawn(async move { c.request::("say_hello", None).await })); + requests.push(tokio::spawn(async move { c.request::("say_hello", rpc_params![]).await })); } for req in requests { @@ -309,7 +316,7 @@ async fn http_making_more_requests_than_allowed_should_not_deadlock() { for _ in 0..6 { let c = client.clone(); - requests.push(tokio::spawn(async move { c.request::("say_hello", None).await })); + requests.push(tokio::spawn(async move { c.request::("say_hello", rpc_params![]).await })); } for req in requests { @@ -322,7 +329,7 @@ async fn https_works() { init_logger(); let client = HttpClientBuilder::default().build("https://kusama-rpc.polkadot.io:443").unwrap(); - let response: String = client.request("system_chain", None).await.unwrap(); + let response: String = client.request("system_chain", rpc_params![]).await.unwrap(); assert_eq!(&response, "Kusama"); } @@ -331,7 +338,7 @@ async fn wss_works() { init_logger(); let client = WsClientBuilder::default().build("wss://kusama-rpc.polkadot.io:443").await.unwrap(); - let response: String = client.request("system_chain", None).await.unwrap(); + let response: String = client.request("system_chain", rpc_params![]).await.unwrap(); assert_eq!(&response, "Kusama"); } @@ -360,9 +367,11 @@ async fn ws_unsubscribe_releases_request_slots() { let client = WsClientBuilder::default().max_concurrent_requests(1).build(&server_url).await.unwrap(); - let sub1: Subscription = client.subscribe("subscribe_hello", None, "unsubscribe_hello").await.unwrap(); + let sub1: Subscription = + client.subscribe("subscribe_hello", rpc_params![], "unsubscribe_hello").await.unwrap(); drop(sub1); - let _: Subscription = client.subscribe("subscribe_hello", None, "unsubscribe_hello").await.unwrap(); + let _: Subscription = + client.subscribe("subscribe_hello", rpc_params![], "unsubscribe_hello").await.unwrap(); } #[tokio::test] @@ -374,7 +383,8 @@ async fn server_should_be_able_to_close_subscriptions() { let client = WsClientBuilder::default().build(&server_url).await.unwrap(); - let mut sub: Subscription = client.subscribe("subscribe_noop", None, "unsubscribe_noop").await.unwrap(); + let mut sub: Subscription = + client.subscribe("subscribe_noop", rpc_params![], "unsubscribe_noop").await.unwrap(); assert!(sub.next().await.is_none()); } @@ -388,13 +398,15 @@ async fn ws_close_pending_subscription_when_server_terminated() { let c1 = WsClientBuilder::default().build(&server_url).await.unwrap(); - let mut sub: Subscription = c1.subscribe("subscribe_hello", None, "unsubscribe_hello").await.unwrap(); + let mut sub: Subscription = + c1.subscribe("subscribe_hello", rpc_params![], "unsubscribe_hello").await.unwrap(); assert!(matches!(sub.next().await, Some(Ok(_)))); handle.stop().unwrap().await; - let sub2: Result, _> = c1.subscribe("subscribe_hello", None, "unsubscribe_hello").await; + let sub2: Result, _> = + c1.subscribe("subscribe_hello", rpc_params![], "unsubscribe_hello").await; // no new request should be accepted. assert!(matches!(sub2, Err(_))); @@ -446,7 +458,8 @@ async fn ws_server_should_stop_subscription_after_client_drop() { let client = WsClientBuilder::default().build(&server_url).await.unwrap(); - let mut sub: Subscription = client.subscribe("subscribe_hello", None, "unsubscribe_hello").await.unwrap(); + let mut sub: Subscription = + client.subscribe("subscribe_hello", rpc_params![], "unsubscribe_hello").await.unwrap(); let res = sub.next().await.unwrap().unwrap(); @@ -474,7 +487,7 @@ async fn ws_server_notify_client_on_disconnect() { tokio::spawn(async move { let client = WsClientBuilder::default().build(&server_url).await.unwrap(); // Validate server is up. - client.request::("say_hello", None).await.unwrap(); + client.request::("say_hello", rpc_params![]).await.unwrap(); // Signal client is waiting for the server to disconnect. up_tx.send(()).unwrap(); @@ -521,7 +534,7 @@ async fn ws_server_notify_client_on_disconnect_with_closed_server() { let client = WsClientBuilder::default().build(&server_url).await.unwrap(); // Validate server is up. - client.request::("say_hello", None).await.unwrap(); + client.request::("say_hello", rpc_params![]).await.unwrap(); // Stop the server. server_handle.stop().unwrap().await; @@ -541,7 +554,12 @@ async fn ws_server_cancels_subscriptions_on_reset_conn() { let mut subs = Vec::new(); for _ in 0..10 { - subs.push(client.subscribe::("subscribe_sleep", None, "unsubscribe_sleep").await.unwrap()); + subs.push( + client + .subscribe::("subscribe_sleep", rpc_params![], "unsubscribe_sleep") + .await + .unwrap(), + ); } // terminate connection. @@ -560,8 +578,10 @@ async fn ws_server_cancels_sub_stream_after_err() { let server_url = format!("ws://{}", addr); let client = WsClientBuilder::default().build(&server_url).await.unwrap(); - let mut sub: Subscription = - client.subscribe("subscribe_with_err_on_stream", None, "unsubscribe_with_err_on_stream").await.unwrap(); + let mut sub: Subscription = client + .subscribe("subscribe_with_err_on_stream", rpc_params![], "unsubscribe_with_err_on_stream") + .await + .unwrap(); assert_eq!(sub.next().await.unwrap().unwrap(), 1); // The server closed down the subscription with the underlying error from the stream. @@ -576,8 +596,10 @@ async fn ws_server_subscribe_with_stream() { let server_url = format!("ws://{}", addr); let client = WsClientBuilder::default().build(&server_url).await.unwrap(); - let mut sub1: Subscription = client.subscribe("subscribe_5_ints", None, "unsubscribe_5_ints").await.unwrap(); - let mut sub2: Subscription = client.subscribe("subscribe_5_ints", None, "unsubscribe_5_ints").await.unwrap(); + let mut sub1: Subscription = + client.subscribe("subscribe_5_ints", rpc_params![], "unsubscribe_5_ints").await.unwrap(); + let mut sub2: Subscription = + client.subscribe("subscribe_5_ints", rpc_params![], "unsubscribe_5_ints").await.unwrap(); let (r1, r2) = futures::future::try_join( sub1.by_ref().take(2).try_collect::>(), @@ -609,7 +631,9 @@ async fn ws_server_pipe_from_stream_should_cancel_tasks_immediately() { let mut subs = Vec::new(); for _ in 0..10 { - subs.push(client.subscribe::("subscribe_sleep", None, "unsubscribe_sleep").await.unwrap()) + subs.push( + client.subscribe::("subscribe_sleep", rpc_params![], "unsubscribe_sleep").await.unwrap(), + ) } // This will call the `unsubscribe method`. @@ -626,7 +650,10 @@ async fn ws_server_pipe_from_stream_can_be_reused() { let (addr, _handle) = websocket_server_with_subscription().await; let client = WsClientBuilder::default().build(&format!("ws://{}", addr)).await.unwrap(); - let sub = client.subscribe::("can_reuse_subscription", None, "u_can_reuse_subscription").await.unwrap(); + let sub = client + .subscribe::("can_reuse_subscription", rpc_params![], "u_can_reuse_subscription") + .await + .unwrap(); let items = sub.fold(0, |acc, _| async move { acc + 1 }).await; @@ -641,10 +668,9 @@ async fn ws_batch_works() { let server_url = format!("ws://{}", server_addr); let client = WsClientBuilder::default().build(&server_url).await.unwrap(); - let mut batch = Vec::new(); - - batch.push(("say_hello", rpc_params![])); - batch.push(("slow_hello", rpc_params![])); + let mut batch = BatchRequestBuilder::new(); + batch.insert("say_hello", rpc_params![]).unwrap(); + batch.insert("slow_hello", rpc_params![]).unwrap(); let responses: Vec = client.batch_request(batch).await.unwrap(); assert_eq!(responses, vec!["hello".to_string(), "hello".to_string()]); @@ -688,12 +714,20 @@ async fn ws_server_limit_subs_per_conn_works() { let mut subs2 = Vec::new(); for _ in 0..10 { - subs1.push(c1.subscribe::("subscribe_forever", None, "unsubscribe_forever").await.unwrap()); - subs2.push(c2.subscribe::("subscribe_forever", None, "unsubscribe_forever").await.unwrap()); + subs1.push( + c1.subscribe::("subscribe_forever", rpc_params![], "unsubscribe_forever") + .await + .unwrap(), + ); + subs2.push( + c2.subscribe::("subscribe_forever", rpc_params![], "unsubscribe_forever") + .await + .unwrap(), + ); } - let err1 = c1.subscribe::("subscribe_forever", None, "unsubscribe_forever").await; - let err2 = c1.subscribe::("subscribe_forever", None, "unsubscribe_forever").await; + let err1 = c1.subscribe::("subscribe_forever", rpc_params![], "unsubscribe_forever").await; + let err2 = c1.subscribe::("subscribe_forever", rpc_params![], "unsubscribe_forever").await; let data = "\"Exceeded max limit of 10\""; @@ -741,7 +775,12 @@ async fn ws_server_unsub_methods_should_ignore_sub_limit() { // Add 10 subscriptions (this should fill our subscrition limit for this connection): let mut subs = Vec::new(); for _ in 0..10 { - subs.push(client.subscribe::("subscribe_forever", None, "unsubscribe_forever").await.unwrap()); + subs.push( + client + .subscribe::("subscribe_forever", rpc_params![], "unsubscribe_forever") + .await + .unwrap(), + ); } // Get the ID of one of them: @@ -913,7 +952,7 @@ async fn ws_subscribe_with_bad_params() { let client = WsClientBuilder::default().build(&server_url).await.unwrap(); let err = client - .subscribe::("subscribe_add_one", rpc_params!["0x0"], "unsubscribe_add_one") + .subscribe::("subscribe_add_one", rpc_params!["0x0"], "unsubscribe_add_one") .await .unwrap_err(); assert!(matches!(err, Error::Call(_))); @@ -961,7 +1000,7 @@ async fn ws_host_filtering_wildcard_works() { let server_url = format!("ws://{}", addr); let client = WsClientBuilder::default().build(&server_url).await.unwrap(); - assert!(client.request::("say_hello", None).await.is_ok()); + assert!(client.request::("say_hello", rpc_params![]).await.is_ok()); } #[tokio::test] @@ -985,5 +1024,5 @@ async fn http_host_filtering_wildcard_works() { let server_url = format!("http://{}", addr); let client = HttpClientBuilder::default().build(&server_url).unwrap(); - assert!(client.request::("say_hello", None).await.is_ok()); + assert!(client.request::("say_hello", rpc_params![]).await.is_ok()); } diff --git a/tests/tests/metrics.rs b/tests/tests/metrics.rs index be442eec97..0f090bca59 100644 --- a/tests/tests/metrics.rs +++ b/tests/tests/metrics.rs @@ -38,7 +38,7 @@ use jsonrpsee::proc_macros::rpc; use jsonrpsee::types::Params; use jsonrpsee::ws_client::WsClientBuilder; use jsonrpsee::ws_server::{WsServerBuilder, WsServerHandle}; -use jsonrpsee::RpcModule; +use jsonrpsee::{rpc_params, RpcModule}; use tokio::time::sleep; #[derive(Clone, Default)] @@ -177,14 +177,19 @@ async fn ws_server_logger() { let server_url = format!("ws://{}", server_addr); let client = WsClientBuilder::default().build(&server_url).await.unwrap(); - assert_eq!(client.request::("say_hello", None).await.unwrap(), "hello"); + let res: String = client.request("say_hello", rpc_params![]).await.unwrap(); + assert_eq!(res, "hello"); - assert!(client.request::("unknown_method", None).await.is_err()); + let res: Result = client.request("unknown_method", rpc_params![]).await; + assert!(res.is_err()); - assert_eq!(client.request::("say_hello", None).await.unwrap(), "hello"); - assert_eq!(client.request::("say_hello", None).await.unwrap(), "hello"); + let res: String = client.request("say_hello", rpc_params![]).await.unwrap(); + assert_eq!(res, "hello"); + let res: String = client.request("say_hello", rpc_params![]).await.unwrap(); + assert_eq!(res, "hello"); - assert!(client.request::("unknown_method", None).await.is_err()); + let res: Result = client.request("unknown_method", rpc_params![]).await; + assert!(res.is_err()); { let inner = counter.inner.lock().unwrap(); @@ -208,14 +213,19 @@ async fn http_server_logger() { let server_url = format!("http://{}", server_addr); let client = HttpClientBuilder::default().build(&server_url).unwrap(); - assert_eq!(client.request::("say_hello", None).await.unwrap(), "hello"); + let res: String = client.request("say_hello", rpc_params![]).await.unwrap(); + assert_eq!(res, "hello"); - assert!(client.request::("unknown_method", None).await.is_err()); + let res: Result = client.request("unknown_method", rpc_params![]).await; + assert!(res.is_err()); - assert_eq!(client.request::("say_hello", None).await.unwrap(), "hello"); - assert_eq!(client.request::("say_hello", None).await.unwrap(), "hello"); + let res: String = client.request("say_hello", rpc_params![]).await.unwrap(); + assert_eq!(res, "hello"); + let res: String = client.request("say_hello", rpc_params![]).await.unwrap(); + assert_eq!(res, "hello"); - assert!(client.request::("unknown_method", None).await.is_err()); + let res: Result = client.request("unknown_method", rpc_params![]).await; + assert!(res.is_err()); { let inner = counter.inner.lock().unwrap(); diff --git a/tests/tests/proc_macros.rs b/tests/tests/proc_macros.rs index 3f110dc259..e82dedd30b 100644 --- a/tests/tests/proc_macros.rs +++ b/tests/tests/proc_macros.rs @@ -26,7 +26,6 @@ //! Example of using proc macro to generate working client and server. -use std::collections::BTreeMap; use std::net::SocketAddr; use jsonrpsee::core::client::ClientT; @@ -35,7 +34,7 @@ use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::http_server::HttpServerBuilder; use jsonrpsee::rpc_params; use jsonrpsee::types::error::{CallError, ErrorCode}; -use jsonrpsee::types::ParamsSer; + use jsonrpsee::ws_client::*; use jsonrpsee::ws_server::WsServerBuilder; use serde_json::json; @@ -197,6 +196,7 @@ mod rpc_impl { } // Use generated implementations of server and client. +use jsonrpsee::core::params::{ArrayParams, ObjectParams}; use rpc_impl::{RpcClient, RpcServer, RpcServerImpl}; pub async fn websocket_server() -> SocketAddr { @@ -300,12 +300,13 @@ async fn macro_zero_copy_cow() { #[cfg(not(target_os = "macos"))] #[tokio::test] async fn multiple_blocking_calls_overlap() { - use jsonrpsee::types::EmptyParams; + use jsonrpsee::types::EmptyServerParams; use std::time::{Duration, Instant}; let module = RpcServerImpl.into_rpc(); - let futures = std::iter::repeat_with(|| module.call::<_, u64>("foo_blocking_call", EmptyParams::new())).take(4); + let futures = + std::iter::repeat_with(|| module.call::<_, u64>("foo_blocking_call", EmptyServerParams::new())).take(4); let now = Instant::now(); let results = futures::future::join_all(futures).await; let elapsed = now.elapsed(); @@ -340,8 +341,8 @@ async fn calls_with_bad_params() { let client = WsClientBuilder::default().build(&server_url).await.unwrap(); // Sub with faulty params as array. - let err = client - .subscribe::("foo_echo", rpc_params!["0x0"], "foo_unsubscribe_echo") + let err: Error = client + .subscribe::("foo_echo", rpc_params!["0x0"], "foo_unsubscribe_echo") .await .unwrap_err(); assert!( @@ -349,28 +350,28 @@ async fn calls_with_bad_params() { ); // Call with faulty params as array. - let err = client.request::("foo_foo", rpc_params!["faulty", "ok"]).await.unwrap_err(); + let err: Error = client.request::("foo_foo", rpc_params!["faulty", "ok"]).await.unwrap_err(); assert!( matches!(err, Error::Call(CallError::Custom (err)) if err.message().contains("invalid type: string \"faulty\", expected u8") && err.code() == ErrorCode::InvalidParams.code()) ); // Sub with faulty params as map. - let mut map = BTreeMap::new(); - map.insert("val", "0x0".into()); - let params = ParamsSer::Map(map); - let err = - client.subscribe::("foo_echo", Some(params), "foo_unsubscribe_echo").await.unwrap_err(); + let mut params = ObjectParams::new(); + params.insert("val", "0x0").unwrap(); + + let err: Error = + client.subscribe::("foo_echo", params, "foo_unsubscribe_echo").await.unwrap_err(); assert!( matches!(err, Error::Call(CallError::Custom (err)) if err.message().contains("invalid type: string \"0x0\", expected u32") && err.code() == ErrorCode::InvalidParams.code()) ); // Call with faulty params as map. - let mut map = BTreeMap::new(); - map.insert("param_a", 1.into()); - map.insert("param_b", 99.into()); - let params = ParamsSer::Map(map); - let err = client.request::("foo_foo", Some(params)).await.unwrap_err(); + let mut params = ObjectParams::new(); + params.insert("param_a", 1).unwrap(); + params.insert("param_b", 2).unwrap(); + + let err: Error = client.request::("foo_foo", params).await.unwrap_err(); assert!( - matches!(err, Error::Call(CallError::Custom (err)) if err.message().contains("invalid type: integer `99`, expected a string") && err.code() == ErrorCode::InvalidParams.code()) + matches!(err, Error::Call(CallError::Custom (err)) if err.message().contains("invalid type: integer `2`, expected a string") && err.code() == ErrorCode::InvalidParams.code()) ); } diff --git a/tests/tests/resource_limiting.rs b/tests/tests/resource_limiting.rs index 029e28673e..03deb39d0e 100644 --- a/tests/tests/resource_limiting.rs +++ b/tests/tests/resource_limiting.rs @@ -29,15 +29,17 @@ use std::time::Duration; use futures::StreamExt; use jsonrpsee::core::client::{ClientT, SubscriptionClientT}; +use jsonrpsee::core::params::ArrayParams; use jsonrpsee::core::Error; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::http_server::{HttpServerBuilder, HttpServerHandle}; use jsonrpsee::proc_macros::rpc; use jsonrpsee::types::error::CallError; use jsonrpsee::types::SubscriptionResult; + use jsonrpsee::ws_client::WsClientBuilder; use jsonrpsee::ws_server::{WsServerBuilder, WsServerHandle}; -use jsonrpsee::{RpcModule, SubscriptionSink}; +use jsonrpsee::{rpc_params, RpcModule, SubscriptionSink}; use tokio::time::{interval, sleep}; use tokio_stream::wrappers::IntervalStream; @@ -185,10 +187,10 @@ async fn run_tests_on_ws_server(server_addr: SocketAddr, server_handle: WsServer // 2 CPU units (default) per call, so 4th call exceeds cap let (pass1, pass2, pass3, fail) = tokio::join!( - client.request::("say_hello", None), - client.request::("say_hello", None), - client.request::("say_hello", None), - client.request::("say_hello", None), + client.request::("say_hello", rpc_params!()), + client.request::("say_hello", rpc_params![]), + client.request::("say_hello", rpc_params![]), + client.request::("say_hello", rpc_params![]), ); assert!(pass1.is_ok()); @@ -198,11 +200,11 @@ async fn run_tests_on_ws_server(server_addr: SocketAddr, server_handle: WsServer // 3 CPU units per call, so 3rd call exceeds CPU cap, but we can still get on MEM let (pass_cpu1, pass_cpu2, fail_cpu, pass_mem, fail_mem) = tokio::join!( - client.request::("expensive_call", None), - client.request::("expensive_call", None), - client.request::("expensive_call", None), - client.request::("memory_hog", None), - client.request::("memory_hog", None), + client.request::("expensive_call", rpc_params![]), + client.request::("expensive_call", rpc_params![]), + client.request::("expensive_call", rpc_params![]), + client.request::("memory_hog", rpc_params![]), + client.request::("memory_hog", rpc_params![]), ); assert!(pass_cpu1.is_ok()); @@ -216,8 +218,8 @@ async fn run_tests_on_ws_server(server_addr: SocketAddr, server_handle: WsServer // // Thus, we can't assume that all subscriptions drop their resources instantly anymore. let (pass1, pass2) = tokio::join!( - client.subscribe::("subscribe_hello", None, "unsubscribe_hello"), - client.subscribe::("subscribe_hello", None, "unsubscribe_hello"), + client.subscribe::("subscribe_hello", rpc_params![], "unsubscribe_hello"), + client.subscribe::("subscribe_hello", rpc_params![], "unsubscribe_hello"), ); assert!(pass1.is_ok()); @@ -225,9 +227,9 @@ async fn run_tests_on_ws_server(server_addr: SocketAddr, server_handle: WsServer // 3 CPU units (manually set for subscriptions) per call, so 3th call exceeds cap let (pass1, pass2, fail) = tokio::join!( - client.subscribe::("subscribe_hello_limit", None, "unsubscribe_hello_limit"), - client.subscribe::("subscribe_hello_limit", None, "unsubscribe_hello_limit"), - client.subscribe::("subscribe_hello_limit", None, "unsubscribe_hello_limit"), + client.subscribe::("subscribe_hello_limit", rpc_params![], "unsubscribe_hello_limit"), + client.subscribe::("subscribe_hello_limit", rpc_params![], "unsubscribe_hello_limit"), + client.subscribe::("subscribe_hello_limit", rpc_params![], "unsubscribe_hello_limit"), ); assert!(pass1.is_ok()); @@ -243,10 +245,10 @@ async fn run_tests_on_http_server(server_addr: SocketAddr, server_handle: HttpSe // 2 CPU units (default) per call, so 4th call exceeds cap let (a, b, c, d) = tokio::join!( - client.request::("say_hello", None), - client.request::("say_hello", None), - client.request::("say_hello", None), - client.request::("say_hello", None), + client.request::("say_hello", rpc_params![]), + client.request::("say_hello", rpc_params![]), + client.request::("say_hello", rpc_params![]), + client.request::("say_hello", rpc_params![]), ); // HTTP does not guarantee ordering diff --git a/tests/tests/rpc_module.rs b/tests/tests/rpc_module.rs index 58aaf702b8..408a14f531 100644 --- a/tests/tests/rpc_module.rs +++ b/tests/tests/rpc_module.rs @@ -31,7 +31,7 @@ use futures::StreamExt; use jsonrpsee::core::error::{Error, SubscriptionClosed}; use jsonrpsee::core::server::rpc_module::*; use jsonrpsee::types::error::{CallError, ErrorCode, ErrorObject, PARSE_ERROR_CODE}; -use jsonrpsee::types::{EmptyParams, Params}; +use jsonrpsee::types::{EmptyServerParams, Params}; use serde::{Deserialize, Serialize}; use tokio::time::interval; use tokio_stream::wrappers::IntervalStream; @@ -98,7 +98,7 @@ async fn calling_method_without_server() { let mut module = RpcModule::new(()); module.register_method("boo", |_: Params, _| Ok(String::from("boo!"))).unwrap(); - let res: String = module.call("boo", EmptyParams::new()).await.unwrap(); + let res: String = module.call("boo", EmptyServerParams::new()).await.unwrap(); assert_eq!(&res, "boo!"); // Call sync method with params @@ -112,7 +112,7 @@ async fn calling_method_without_server() { assert_eq!(res, 6); // Call sync method with bad param - let err = module.call::<_, ()>("foo", (false,)).await.unwrap_err(); + let err = module.call::<_, EmptyServerParams>("foo", (false,)).await.unwrap_err(); assert!(matches!( err, Error::Call(CallError::Custom(err)) if err.code() == -32602 && err.message() == "invalid type: boolean `false`, expected u16 at line 1 column 6" @@ -198,7 +198,7 @@ async fn calling_method_without_server_using_proc_macro() { let module = CoolServerImpl.into_rpc(); // Call sync method with no params - let res: bool = module.call("rebel_without_cause", EmptyParams::new()).await.unwrap(); + let res: bool = module.call("rebel_without_cause", EmptyServerParams::new()).await.unwrap(); assert!(!res); // Call sync method with params @@ -206,7 +206,7 @@ async fn calling_method_without_server_using_proc_macro() { assert_eq!(&res, "0 Gun { shoots: true }"); // Call sync method with bad params - let err = module.call::<_, ()>("rebel", (Gun { shoots: true }, false)).await.unwrap_err(); + let err = module.call::<_, EmptyServerParams>("rebel", (Gun { shoots: true }, false)).await.unwrap_err(); assert!(matches!(err, Error::Call(CallError::Custom(err)) if err.code() == -32602 && err.message() == "invalid type: boolean `false`, expected a map at line 1 column 5" )); @@ -253,7 +253,7 @@ async fn subscribing_without_server() { }) .unwrap(); - let mut my_sub = module.subscribe("my_sub", EmptyParams::new()).await.unwrap(); + let mut my_sub = module.subscribe("my_sub", EmptyServerParams::new()).await.unwrap(); for i in (0..=2).rev() { let (val, id) = my_sub.next::().await.unwrap().unwrap(); assert_eq!(val, std::char::from_digit(i, 10).unwrap()); @@ -288,11 +288,11 @@ async fn close_test_subscribing_without_server() { }) .unwrap(); - let mut my_sub = module.subscribe("my_sub", EmptyParams::new()).await.unwrap(); + let mut my_sub = module.subscribe("my_sub", EmptyServerParams::new()).await.unwrap(); let (val, id) = my_sub.next::().await.unwrap().unwrap(); assert_eq!(&val, "lo"); assert_eq!(&id, my_sub.subscription_id()); - let mut my_sub2 = std::mem::ManuallyDrop::new(module.subscribe("my_sub", EmptyParams::new()).await.unwrap()); + let mut my_sub2 = std::mem::ManuallyDrop::new(module.subscribe("my_sub", EmptyServerParams::new()).await.unwrap()); // Close the subscription to ensure it doesn't return any items. my_sub.close(); @@ -332,7 +332,7 @@ async fn subscribing_without_server_bad_params() { }) .unwrap(); - let sub = module.subscribe("my_sub", EmptyParams::new()).await.unwrap_err(); + let sub = module.subscribe("my_sub", EmptyServerParams::new()).await.unwrap_err(); assert!( matches!(sub, Error::Call(CallError::Custom(e)) if e.message().contains("invalid length 0, expected an array of length 1 at line 1 column 2") && e.code() == ErrorCode::InvalidParams.code()) @@ -355,7 +355,7 @@ async fn subscribe_unsubscribe_without_server() { .unwrap(); async fn subscribe_and_assert(module: &RpcModule<()>) { - let sub = module.subscribe("my_sub", EmptyParams::new()).await.unwrap(); + let sub = module.subscribe("my_sub", EmptyServerParams::new()).await.unwrap(); let ser_id = serde_json::to_string(sub.subscription_id()).unwrap(); @@ -390,7 +390,7 @@ async fn empty_subscription_without_server() { }) .unwrap(); - let sub_err = module.subscribe("my_sub", EmptyParams::new()).await.unwrap_err(); + let sub_err = module.subscribe("my_sub", EmptyServerParams::new()).await.unwrap_err(); assert!( matches!(sub_err, Error::Call(CallError::Custom(e)) if e.message().contains("Invalid params") && e.code() == ErrorCode::InvalidParams.code()) ); @@ -407,7 +407,7 @@ async fn rejected_subscription_without_server() { }) .unwrap(); - let sub_err = module.subscribe("my_sub", EmptyParams::new()).await.unwrap_err(); + let sub_err = module.subscribe("my_sub", EmptyServerParams::new()).await.unwrap_err(); assert!( matches!(sub_err, Error::Call(CallError::Custom(e)) if e.message().contains("rejected") && e.code() == PARSE_ERROR_CODE) ); @@ -432,7 +432,7 @@ async fn accepted_twice_subscription_without_server() { }) .unwrap(); - let _ = module.subscribe("my_sub", EmptyParams::new()).await.expect("Subscription should not fail"); + let _ = module.subscribe("my_sub", EmptyServerParams::new()).await.expect("Subscription should not fail"); } #[tokio::test] @@ -455,7 +455,7 @@ async fn reject_twice_subscription_without_server() { }) .unwrap(); - let sub_err = module.subscribe("my_sub", EmptyParams::new()).await.unwrap_err(); + let sub_err = module.subscribe("my_sub", EmptyServerParams::new()).await.unwrap_err(); assert!( matches!(sub_err, Error::Call(CallError::Custom(e)) if e.message().contains("rejected") && e.code() == PARSE_ERROR_CODE) ); diff --git a/types/src/lib.rs b/types/src/lib.rs index 5c7b4e04bd..9380d06c27 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -26,7 +26,6 @@ //! Shared types in `jsonrpsee` for clients, servers and utilities. -#![deny(unsafe_code)] #![warn(missing_docs, missing_debug_implementations)] extern crate alloc; @@ -44,9 +43,9 @@ pub mod response; pub mod error; pub use error::{ErrorObject, ErrorObjectOwned, ErrorResponse, SubscriptionEmptyError, SubscriptionResult}; -pub use params::{Id, Params, ParamsSequence, ParamsSer, SubscriptionId, TwoPointZero}; +pub use params::{Id, Params, ParamsSequence, SubscriptionId, TwoPointZero}; pub use request::{InvalidRequest, Notification, NotificationSer, Request, RequestSer}; pub use response::{Response, SubscriptionPayload, SubscriptionResponse}; -/// Empty `RpcParams` type; -pub type EmptyParams = Vec<()>; +/// Empty server `RpcParams` type to use while registering modules. +pub type EmptyServerParams = Vec<()>; diff --git a/types/src/params.rs b/types/src/params.rs index 5c015ef417..b660182b12 100644 --- a/types/src/params.rs +++ b/types/src/params.rs @@ -30,7 +30,6 @@ use std::fmt; use crate::error::CallError; -use alloc::collections::BTreeMap; use anyhow::anyhow; use beef::Cow; use serde::de::{self, Deserializer, Unexpected, Visitor}; @@ -260,40 +259,6 @@ impl<'a> ParamsSequence<'a> { } } -/// [Serializable JSON-RPC parameters](https://www.jsonrpc.org/specification#parameter_structures) -/// -/// If your type implements `Into`, call that in favor of `serde_json::to:value` to -/// construct the parameters. Because `serde_json::to_value` serializes the type which allocates -/// whereas `Into` doesn't in most cases. -#[derive(Serialize, Debug, Clone)] -#[serde(untagged)] -pub enum ParamsSer<'a> { - /// Positional params (heap allocated). - Array(Vec), - /// Positional params (slice). - ArrayRef(&'a [JsonValue]), - /// Params by name. - Map(BTreeMap<&'a str, JsonValue>), -} - -impl<'a> From> for ParamsSer<'a> { - fn from(map: BTreeMap<&'a str, JsonValue>) -> Self { - Self::Map(map) - } -} - -impl<'a> From> for ParamsSer<'a> { - fn from(arr: Vec) -> Self { - Self::Array(arr) - } -} - -impl<'a> From<&'a [JsonValue]> for ParamsSer<'a> { - fn from(slice: &'a [JsonValue]) -> Self { - Self::ArrayRef(slice) - } -} - /// Id of a subscription, communicated by the server. #[derive(Debug, PartialEq, Clone, Hash, Eq, Deserialize, Serialize)] #[serde(deny_unknown_fields)] @@ -410,7 +375,7 @@ impl<'a> Id<'a> { #[cfg(test)] mod test { - use super::{Cow, Id, JsonValue, Params, ParamsSer, SubscriptionId, TwoPointZero}; + use super::{Cow, Id, JsonValue, Params, SubscriptionId, TwoPointZero}; use crate::response::SubscriptionPayload; #[test] @@ -449,23 +414,6 @@ mod test { assert_eq!(serialized, r#"[null,0,2,3,"\"3","test"]"#); } - #[test] - fn params_serialize() { - let test_vector = &[ - ("[]", ParamsSer::Array(serde_json::from_str("[]").unwrap())), - ("[42,23]", ParamsSer::Array(serde_json::from_str("[42,23]").unwrap())), - ( - r#"{"a":42,"b":null,"c":"aa"}"#, - ParamsSer::Map(serde_json::from_str(r#"{"a":42,"b":null,"c":"aa"}"#).unwrap()), - ), - ]; - - for (initial_ser, params) in test_vector { - let serialized = serde_json::to_string(params).unwrap(); - assert_eq!(&serialized, initial_ser); - } - } - #[test] fn params_parse() { let none = Params::new(None); diff --git a/types/src/request.rs b/types/src/request.rs index 1cbd1ee2d6..e20d4ef632 100644 --- a/types/src/request.rs +++ b/types/src/request.rs @@ -27,7 +27,7 @@ //! Types to handle JSON-RPC requests according to the [spec](https://www.jsonrpc.org/specification#request-object). //! Some types come with a "*Ser" variant that implements [`serde::Serialize`]; these are used in the client. -use crate::params::{Id, ParamsSer, TwoPointZero}; +use crate::params::{Id, TwoPointZero}; use beef::Cow; use serde::{Deserialize, Serialize}; use serde_json::value::RawValue; @@ -96,12 +96,12 @@ pub struct RequestSer<'a> { pub method: &'a str, /// Parameter values of the request. #[serde(skip_serializing_if = "Option::is_none")] - pub params: Option>, + pub params: Option>, } impl<'a> RequestSer<'a> { /// Create a new serializable JSON-RPC request. - pub fn new(id: &'a Id<'a>, method: &'a str, params: Option>) -> Self { + pub fn new(id: &'a Id<'a>, method: &'a str, params: Option>) -> Self { Self { jsonrpc: TwoPointZero, id, method, params } } } @@ -117,20 +117,20 @@ pub struct NotificationSer<'a> { pub method: &'a str, /// Parameter values of the request. #[serde(skip_serializing_if = "Option::is_none")] - pub params: Option>, + pub params: Option>, } impl<'a> NotificationSer<'a> { /// Create a new serializable JSON-RPC request. - pub fn new(method: &'a str, params: Option>) -> Self { + pub fn new(method: &'a str, params: Option>) -> Self { Self { jsonrpc: TwoPointZero, method, params } } } #[cfg(test)] mod test { - use super::{Id, InvalidRequest, Notification, NotificationSer, ParamsSer, Request, RequestSer, TwoPointZero}; - use serde_json::{value::RawValue, Value}; + use super::{Id, InvalidRequest, Notification, NotificationSer, Request, RequestSer, TwoPointZero}; + use serde_json::value::RawValue; fn assert_request<'a>(request: Request<'a>, id: Id<'a>, method: &str, params: Option<&str>) { assert_eq!(request.jsonrpc, TwoPointZero); @@ -206,19 +206,15 @@ mod test { fn serialize_call() { let method = "subtract"; let id = Id::Number(1); // It's enough to check one variant, since the type itself also has tests. - let params: ParamsSer = vec![Value::Number(42.into()), Value::Number(23.into())].into(); // Same as above. - let test_vector = &[ + let params = Some(RawValue::from_string("[42,23]".into()).unwrap()); + + let test_vector: &[(&'static str, Option<_>, Option<_>, &'static str)] = &[ // With all fields set. - ( - r#"{"jsonrpc":"2.0","id":1,"method":"subtract","params":[42,23]}"#, - Some(&id), - Some(params.clone()), - method, - ), + (r#"{"jsonrpc":"2.0","id":1,"method":"subtract","params":[42,23]}"#, Some(&id), params.clone(), method), // Escaped method name. (r#"{"jsonrpc":"2.0","id":1,"method":"\"m"}"#, Some(&id), None, "\"m"), // Without ID field. - (r#"{"jsonrpc":"2.0","id":null,"method":"subtract","params":[42,23]}"#, None, Some(params), method), + (r#"{"jsonrpc":"2.0","id":null,"method":"subtract","params":[42,23]}"#, None, params, method), // Without params field (r#"{"jsonrpc":"2.0","id":1,"method":"subtract"}"#, Some(&id), None, method), // Without params and ID. @@ -241,7 +237,8 @@ mod test { #[test] fn serialize_notif() { let exp = r#"{"jsonrpc":"2.0","method":"say_hello","params":["hello"]}"#; - let req = NotificationSer::new("say_hello", Some(vec!["hello".into()].into())); + let params = Some(RawValue::from_string(r#"["hello"]"#.into()).unwrap()); + let req = NotificationSer::new("say_hello", params); let ser = serde_json::to_string(&req).unwrap(); assert_eq!(exp, ser); }