From ac8880a91799adf1d2b7d29ff0699b67daecfee4 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 7 Apr 2021 12:28:09 +0200 Subject: [PATCH 01/41] rewrite me --- benches/benches/bench.rs | 4 +- examples/examples/http.rs | 4 +- examples/examples/ws.rs | 7 +- examples/examples/ws_subscription.rs | 13 +- http-client/src/client.rs | 95 +++++--------- http-client/src/transport.rs | 33 +++-- types/src/client.rs | 26 ++-- types/src/error.rs | 10 +- types/src/lib.rs | 2 +- types/src/traits.rs | 36 ++---- types/src/v2/dummy.rs | 184 +++++++++++++++++++++++++++ types/src/v2/mod.rs | 8 +- utils/src/http/hyper_helpers.rs | 6 +- ws-client/src/client.rs | 141 +++++++++----------- ws-client/src/jsonrpc_transport.rs | 52 ++++---- ws-client/src/manager.rs | 7 +- ws-client/src/transport.rs | 23 ++-- 17 files changed, 390 insertions(+), 261 deletions(-) create mode 100644 types/src/v2/dummy.rs diff --git a/benches/benches/bench.rs b/benches/benches/bench.rs index b163ab990e..14bf840b73 100644 --- a/benches/benches/bench.rs +++ b/benches/benches/bench.rs @@ -30,7 +30,7 @@ fn run_round_trip(rt: &TokioRuntime, crit: &mut Criterion, client: Arc("say_hello", Params::None).await.unwrap()); + black_box(client.request::("say_hello".into(), None.into()).await.unwrap()); }) }) }); @@ -50,7 +50,7 @@ fn run_concurrent_round_trip( for _ in 0..num_concurrent_tasks { let client_rc = client.clone(); let task = rt.spawn(async move { - let _ = black_box(client_rc.request::("say_hello", Params::None)).await; + let _ = black_box(client_rc.request::("say_hello".into(), None.into())).await; }); tasks.push(task); } diff --git a/examples/examples/http.rs b/examples/examples/http.rs index b2a3de914c..b3acb3b892 100644 --- a/examples/examples/http.rs +++ b/examples/examples/http.rs @@ -28,7 +28,7 @@ use std::net::SocketAddr; use jsonrpsee_http_client::HttpClientBuilder; use jsonrpsee_http_server::HttpServerBuilder; -use jsonrpsee_types::{jsonrpc::Params, traits::Client}; +use jsonrpsee_types::{traits::Client, v2::RawValue}; #[tokio::main] async fn main() -> Result<(), Box> { @@ -38,7 +38,7 @@ async fn main() -> Result<(), Box> { let url = format!("http://{}", server_addr); let client = HttpClientBuilder::default().build(url)?; - let response: Result = client.request("say_hello", Params::None).await; + let response: Result = client.request("say_hello".into(), None.into()).await; println!("r: {:?}", response); Ok(()) diff --git a/examples/examples/ws.rs b/examples/examples/ws.rs index dae810f336..745b70e6ec 100644 --- a/examples/examples/ws.rs +++ b/examples/examples/ws.rs @@ -25,10 +25,7 @@ // DEALINGS IN THE SOFTWARE. use futures::channel::oneshot::{self, Sender}; -use jsonrpsee_types::{ - jsonrpc::{JsonValue, Params}, - traits::Client, -}; +use jsonrpsee_types::traits::Client; use jsonrpsee_ws_client::WsClientBuilder; use jsonrpsee_ws_server::WsServer; use tokio::task; @@ -47,7 +44,7 @@ async fn main() -> Result<(), Box> { server_started_rx.await?; let client = WsClientBuilder::default().build(SERVER_URI).await?; - let response: JsonValue = client.request("say_hello", Params::None).await?; + let response: String = client.request("say_hello".into(), None.into()).await?; println!("r: {:?}", response); Ok(()) diff --git a/examples/examples/ws_subscription.rs b/examples/examples/ws_subscription.rs index 6da9bbf55f..cab947734f 100644 --- a/examples/examples/ws_subscription.rs +++ b/examples/examples/ws_subscription.rs @@ -25,10 +25,7 @@ // DEALINGS IN THE SOFTWARE. use futures::channel::oneshot::{self, Sender}; -use jsonrpsee_types::{ - jsonrpc::{JsonValue, Params}, - traits::SubscriptionClient, -}; +use jsonrpsee_types::traits::SubscriptionClient; use jsonrpsee_ws_client::{WsClientBuilder, WsSubscription}; use jsonrpsee_ws_server::WsServer; use tokio::task; @@ -48,8 +45,8 @@ async fn main() -> Result<(), Box> { server_started_rx.await?; let client = WsClientBuilder::default().build(SERVER_URI).await?; - let mut subscribe_hello: WsSubscription = - client.subscribe("subscribe_hello", Params::None, "unsubscribe_hello").await?; + let mut subscribe_hello: WsSubscription = + client.subscribe("subscribe_hello".into(), None.into(), "unsubscribe_hello".into()).await?; let mut i = 0; while i <= NUM_SUBSCRIPTION_RESPONSES { @@ -58,6 +55,10 @@ async fn main() -> Result<(), Box> { i += 1; } + drop(subscribe_hello); + + loop {} + Ok(()) } diff --git a/http-client/src/client.rs b/http-client/src/client.rs index eeff2e9f4f..628f7d7e3b 100644 --- a/http-client/src/client.rs +++ b/http-client/src/client.rs @@ -1,15 +1,18 @@ use crate::transport::HttpTransportClient; use async_trait::async_trait; use fnv::FnvHashMap; -use jsonrpc::DeserializeOwned; use jsonrpsee_types::{ error::{Error, Mismatch}, - jsonrpc, traits::Client, + v2::dummy::{JsonRpcCall, JsonRpcMethod, JsonRpcNotification, JsonRpcParams, JsonRpcResponse}, }; -use std::convert::TryInto; +use serde::de::DeserializeOwned; use std::sync::atomic::{AtomicU64, Ordering}; +const SINGLE_RESPONSE: &str = "Single Response"; +const BATCH_RESPONSE: &str = "Batch response"; +const SUBSCRIPTION_RESPONSE: &str = "Subscription response"; + /// Http Client Builder. #[derive(Debug)] pub struct HttpClientBuilder { @@ -48,34 +51,19 @@ pub struct HttpClient { #[async_trait] impl Client for HttpClient { - async fn notification(&self, method: M, params: P) -> Result<(), Error> - where - M: Into + Send, - P: Into + Send, - { - let request = jsonrpc::Request::Single(jsonrpc::Call::Notification(jsonrpc::Notification { - jsonrpc: jsonrpc::Version::V2, - method: method.into(), - params: params.into(), - })); - self.transport.send_notification(request).await.map_err(|e| Error::TransportError(Box::new(e))) + async fn notification<'a>(&self, method: JsonRpcMethod<'a>, params: JsonRpcParams<'a>) -> Result<(), Error> { + let notif = JsonRpcNotification::new(method.inner(), params.inner()); + self.transport.send_notification(notif).await.map_err(|e| Error::TransportError(Box::new(e))) } /// Perform a request towards the server. - async fn request(&self, method: M, params: P) -> Result + async fn request<'a, T>(&self, method: JsonRpcMethod<'a>, params: JsonRpcParams<'a>) -> Result where T: DeserializeOwned, - M: Into + Send, - P: Into + Send, { // NOTE: `fetch_add` wraps on overflow which is intended. let id = self.request_id.fetch_add(1, Ordering::Relaxed); - let request = jsonrpc::Request::Single(jsonrpc::Call::MethodCall(jsonrpc::MethodCall { - jsonrpc: jsonrpc::Version::V2, - method: method.into(), - params: params.into(), - id: jsonrpc::Id::Num(id), - })); + let request = JsonRpcCall::new(id, method.inner(), params.inner()); let response = self .transport @@ -83,47 +71,30 @@ impl Client for HttpClient { .await .map_err(|e| Error::TransportError(Box::new(e)))?; - let json_value = match response { - jsonrpc::Response::Single(response) => match response.id() { - jsonrpc::Id::Num(n) if n == &id => response.try_into().map_err(Error::Request), - _ => Err(Error::InvalidRequestId), - }, - jsonrpc::Response::Batch(_rps) => Err(Error::InvalidResponse(Mismatch { - expected: "Single response".into(), - got: "Batch Response".into(), - })), - jsonrpc::Response::Notif(_notif) => Err(Error::InvalidResponse(Mismatch { - expected: "Single response".into(), - got: "Notification Response".into(), - })), - }?; - jsonrpc::from_value(json_value).map_err(Error::ParseError) + match response { + JsonRpcResponse::Single(response) if response.id == id => Ok(response.result), + JsonRpcResponse::Single(_) => Err(Error::InvalidRequestId), + JsonRpcResponse::Batch(_rps) => Err(invalid_response(SINGLE_RESPONSE, BATCH_RESPONSE)), + JsonRpcResponse::Subscription(_notif) => Err(invalid_response(SINGLE_RESPONSE, SUBSCRIPTION_RESPONSE)), + } } - async fn batch_request(&self, batch: Vec<(M, P)>) -> Result, Error> + async fn batch_request<'a, T>(&self, batch: Vec<(JsonRpcMethod<'a>, JsonRpcParams<'a>)>) -> Result, Error> where T: DeserializeOwned + Default + Clone, - M: Into + Send, - P: Into + Send, { - let mut calls = Vec::with_capacity(batch.len()); + let mut batch_request = Vec::with_capacity(batch.len()); // NOTE(niklasad1): `ID` is not necessarily monotonically increasing. let mut ordered_requests = Vec::with_capacity(batch.len()); let mut request_set = FnvHashMap::with_capacity_and_hasher(batch.len(), Default::default()); for (pos, (method, params)) in batch.into_iter().enumerate() { let id = self.request_id.fetch_add(1, Ordering::SeqCst); - calls.push(jsonrpc::Call::MethodCall(jsonrpc::MethodCall { - jsonrpc: jsonrpc::Version::V2, - method: method.into(), - params: params.into(), - id: jsonrpc::Id::Num(id), - })); + batch_request.push(JsonRpcCall::new(id, method.inner(), params.inner())); ordered_requests.push(id); request_set.insert(id, pos); } - let batch_request = jsonrpc::Request::Batch(calls); let response = self .transport .send_request_and_wait_for_response(batch_request) @@ -131,32 +102,24 @@ impl Client for HttpClient { .map_err(|e| Error::TransportError(Box::new(e)))?; match response { - jsonrpc::Response::Single(_) => Err(Error::InvalidResponse(Mismatch { - expected: "Batch response".into(), - got: "Single Response".into(), - })), - jsonrpc::Response::Notif(_notif) => Err(Error::InvalidResponse(Mismatch { - expected: "Batch response".into(), - got: "Notification response".into(), - })), - jsonrpc::Response::Batch(rps) => { + JsonRpcResponse::Single(_response) => Err(invalid_response(BATCH_RESPONSE, SINGLE_RESPONSE)), + JsonRpcResponse::Subscription(_notif) => Err(invalid_response(BATCH_RESPONSE, SUBSCRIPTION_RESPONSE)), + JsonRpcResponse::Batch(rps) => { // NOTE: `T::default` is placeholder and will be replaced in loop below. let mut responses = vec![T::default(); ordered_requests.len()]; for rp in rps { - let id = match rp.id().as_number() { - Some(n) => *n, - _ => return Err(Error::InvalidRequestId), - }; - let pos = match request_set.get(&id) { + let pos = match request_set.get(&rp.id) { Some(pos) => *pos, None => return Err(Error::InvalidRequestId), }; - let json_val: jsonrpc::JsonValue = rp.try_into().map_err(Error::Request)?; - let response = jsonrpc::from_value(json_val).map_err(Error::ParseError)?; - responses[pos] = response; + responses[pos] = rp.result } Ok(responses) } } } } + +fn invalid_response(expected: impl Into, got: impl Into) -> Error { + Error::InvalidResponse(Mismatch { expected: expected.into(), got: got.into() }) +} diff --git a/http-client/src/transport.rs b/http-client/src/transport.rs index 10d2453ef7..40a57348af 100644 --- a/http-client/src/transport.rs +++ b/http-client/src/transport.rs @@ -8,8 +8,13 @@ use hyper::client::{Client, HttpConnector}; use hyper_rustls::HttpsConnector; -use jsonrpsee_types::{error::GenericTransportError, jsonrpc}; +use jsonrpsee_types::{ + error::GenericTransportError, + v2::dummy::{JsonRpcNotification, JsonRpcRequest, JsonRpcResponse}, +}; use jsonrpsee_utils::http::hyper_helpers; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; use thiserror::Error; const CONTENT_TYPE_JSON: &str = "application/json"; @@ -42,9 +47,8 @@ impl HttpTransportClient { } /// Send request. - async fn send_request(&self, request: jsonrpc::Request) -> Result, Error> { - let body = jsonrpc::to_vec(&request).map_err(Error::Serialization)?; - log::debug!("send: {}", request); + async fn send<'a>(&self, body: String) -> Result, Error> { + log::debug!("send: {}", body); if body.len() > self.max_request_body_size as usize { return Err(Error::RequestTooLarge); @@ -65,24 +69,29 @@ impl HttpTransportClient { } /// Send notification. - pub async fn send_notification(&self, request: jsonrpc::Request) -> Result<(), Error> { - let _response = self.send_request(request).await?; + pub async fn send_notification<'a>(&self, notif: JsonRpcNotification<'a>) -> Result<(), Error> { + let body = serde_json::to_string(¬if).map_err(Error::Serialization)?; + let _response = self.send(body).await?; Ok(()) } /// Send request and wait for response. - pub async fn send_request_and_wait_for_response( + pub async fn send_request_and_wait_for_response<'a, T>( &self, - request: jsonrpc::Request, - ) -> Result { - let response = self.send_request(request).await?; + request: impl Into>, + ) -> Result, Error> + where + T: DeserializeOwned, + { + let body = serde_json::to_string(&request.into()).map_err(Error::Serialization)?; + let response = self.send(body).await?; let (parts, body) = response.into_parts(); let body = hyper_helpers::read_response_to_body(&parts.headers, body, self.max_request_body_size).await?; // Note that we don't check the Content-Type of the request. This is deemed // unnecessary, as a parsing error while happen anyway. - let response: jsonrpc::Response = jsonrpc::from_slice(&body).map_err(Error::ParseError)?; - log::debug!("recv: {}", jsonrpc::to_string(&response).expect("request valid JSON; qed")); + let response = serde_json::from_slice(&body).map_err(Error::ParseError)?; + //log::debug!("recv: {}", serde_json::to_string(&response).expect("valid JSON; qed")); Ok(response) } } diff --git a/types/src/client.rs b/types/src/client.rs index 95d610e30e..340d6fc748 100644 --- a/types/src/client.rs +++ b/types/src/client.rs @@ -1,8 +1,12 @@ -use crate::error::Error; -use crate::jsonrpc::{self, DeserializeOwned, JsonValue, Params, SubscriptionId}; +use crate::{ + error::Error, + v2::dummy::{JsonRpcMethod, JsonRpcMethodOwned, JsonRpcParams, JsonRpcParamsOwned, SubscriptionId}, +}; use core::marker::PhantomData; use futures::channel::{mpsc, oneshot}; use futures::prelude::*; +use serde::de::DeserializeOwned; +use serde_json::Value as JsonValue; /// Active subscription on a Client. pub struct Subscription { @@ -20,16 +24,16 @@ pub struct Subscription { #[derive(Debug)] pub struct NotificationMessage { /// Method for the notification. - pub method: String, + pub method: JsonRpcMethodOwned, /// Parameters to send to the server. - pub params: Params, + pub params: JsonRpcParamsOwned, } /// Batch request message. #[derive(Debug)] pub struct BatchMessage { /// Requests in the batch - pub requests: Vec<(String, Params)>, + pub requests: Vec<(JsonRpcMethodOwned, JsonRpcParamsOwned)>, /// One-shot channel over which we send back the result of this request. pub send_back: oneshot::Sender, Error>>, } @@ -38,9 +42,9 @@ pub struct BatchMessage { #[derive(Debug)] pub struct RequestMessage { /// Method for the request. - pub method: String, + pub method: JsonRpcMethodOwned, /// Parameters of the request. - pub params: Params, + pub params: JsonRpcParamsOwned, /// One-shot channel over which we send back the result of this request. pub send_back: Option>>, } @@ -49,11 +53,11 @@ pub struct RequestMessage { #[derive(Debug)] pub struct SubscriptionMessage { /// Method for the subscription request. - pub subscribe_method: String, + pub subscribe_method: JsonRpcMethodOwned, /// Parameters to send for the subscription. - pub params: Params, + pub params: JsonRpcParamsOwned, /// Method to use to unsubscribe later. Used if the channel unexpectedly closes. - pub unsubscribe_method: String, + pub unsubscribe_method: JsonRpcMethodOwned, /// If the subscription succeeds, we return a [`mpsc::Receiver`] that will receive notifications. /// When we get a response from the server about that subscription, we send the result over /// this channel. @@ -91,7 +95,7 @@ where pub async fn next(&mut self) -> Option { loop { match self.notifs_rx.next().await { - Some(n) => match jsonrpc::from_value(n) { + Some(n) => match serde_json::from_value(n) { Ok(parsed) => return Some(parsed), Err(e) => log::debug!("Subscription response error: {:?}", e), }, diff --git a/types/src/error.rs b/types/src/error.rs index cd164c9d70..e667be3094 100644 --- a/types/src/error.rs +++ b/types/src/error.rs @@ -1,4 +1,3 @@ -use crate::jsonrpc; use std::fmt; /// Convenience type for displaying errors. #[derive(Clone, Debug, PartialEq)] @@ -22,11 +21,8 @@ pub enum Error { #[error("Networking or low-level protocol error: {0}")] TransportError(#[source] Box), /// JSON-RPC request error. - #[error("JSON-RPC request error: {0:?}")] - Request(#[source] jsonrpc::Error), - /// Subscription error. - #[error("Subscription failed, subscribe_method: {0} unsubscribe_method: {1}")] - Subscription(String, String), + //#[error("JSON-RPC request error: {0:?}")] + //Request(#[source] jsonrpc::Error), /// Frontend/backend channel error. #[error("Frontend/backend channel error: {0}")] Internal(#[source] futures::channel::mpsc::SendError), @@ -38,7 +34,7 @@ pub enum Error { RestartNeeded(String), /// Failed to parse the data that the server sent back to us. #[error("Parse error: {0}")] - ParseError(#[source] jsonrpc::ParseError), + ParseError(#[source] serde_json::Error), /// Invalid subscription ID. #[error("Invalid subscription ID")] InvalidSubscriptionId, diff --git a/types/src/lib.rs b/types/src/lib.rs index d770dd070b..1eb93567cf 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -6,7 +6,7 @@ extern crate alloc; /// JSON-RPC 2.0 specification related types. -pub mod jsonrpc; +//pub mod jsonrpc; /// JSON-RPC 2.0 specification related types v2. pub mod v2; diff --git a/types/src/traits.rs b/types/src/traits.rs index bfd5c56df5..d6607cd9a7 100644 --- a/types/src/traits.rs +++ b/types/src/traits.rs @@ -1,24 +1,21 @@ -use crate::client::Subscription; use crate::error::Error; -use crate::jsonrpc::{DeserializeOwned, Params}; -use alloc::string::String; +use crate::{ + client::Subscription, + v2::dummy::{JsonRpcMethod, JsonRpcParams}, +}; use async_trait::async_trait; +use serde::de::DeserializeOwned; /// [JSON-RPC](https://www.jsonrpc.org/specification) client interface that can make requests and notifications. #[async_trait] pub trait Client { /// Send a [notification request](https://www.jsonrpc.org/specification#notification) - async fn notification(&self, method: M, params: P) -> Result<(), Error> - where - M: Into + Send, - P: Into + Send; + async fn notification<'a>(&self, method: JsonRpcMethod<'a>, params: JsonRpcParams<'a>) -> Result<(), Error>; /// Send a [method call request](https://www.jsonrpc.org/specification#request_object). - async fn request(&self, method: M, params: P) -> Result + async fn request<'a, T>(&self, method: JsonRpcMethod<'a>, params: JsonRpcParams<'a>) -> Result where - T: DeserializeOwned, - M: Into + Send, - P: Into + Send; + T: DeserializeOwned; /// Send a [batch request](https://www.jsonrpc.org/specification#batch). /// @@ -26,11 +23,9 @@ pub trait Client { /// /// 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(&self, batch: Vec<(M, P)>) -> Result, Error> + async fn batch_request<'a, T>(&self, batch: Vec<(JsonRpcMethod<'a>, JsonRpcParams<'a>)>) -> Result, Error> where - T: DeserializeOwned + Default + Clone, - M: Into + Send, - P: Into + Send; + T: DeserializeOwned + Default + Clone; } /// [JSON-RPC](https://www.jsonrpc.org/specification) client interface that can make requests, notifications and subscriptions. @@ -44,15 +39,12 @@ pub trait SubscriptionClient: Client { /// The `unsubscribe_method` is used to close the subscription. /// /// The `Notif` param is a generic type to receive generic subscriptions, see [`Subscription`](crate::client::Subscription) for further documentation. - async fn subscribe( + async fn subscribe<'a, Notif>( &self, - subscribe_method: SM, - params: P, - unsubscribe_method: UM, + subscribe_method: JsonRpcMethod<'a>, + params: JsonRpcParams<'a>, + unsubscribe_method: JsonRpcMethod<'a>, ) -> Result, Error> where - SM: Into + Send, - UM: Into + Send, - P: Into + Send, Notif: DeserializeOwned; } diff --git a/types/src/v2/dummy.rs b/types/src/v2/dummy.rs new file mode 100644 index 0000000000..bef41d122b --- /dev/null +++ b/types/src/v2/dummy.rs @@ -0,0 +1,184 @@ +//! Client side JSON-RPC types. + +use crate::v2::TwoPointZero; +use serde::{Deserialize, Serialize}; + +#[derive(Debug)] +/// JSON-RPC method call. +pub struct JsonRpcMethodOwned(pub String); + +impl JsonRpcMethodOwned { + /// Get inner. + pub fn inner(&self) -> &str { + self.0.as_str() + } +} + +#[derive(Debug)] +/// JSON-RPC method call. +pub struct JsonRpcParamsOwned(pub Option); + +impl JsonRpcParamsOwned { + /// Get inner. + pub fn inner(&self) -> Option<&str> { + self.0.as_ref().map(|p| p.as_str()) + } +} + +/// Serializable JSON-RPC method call. +#[derive(Serialize, Debug, PartialEq)] +pub struct JsonRpcMethod<'a>(&'a str); + +impl<'a> From<&'a str> for JsonRpcMethod<'a> { + fn from(raw: &'a str) -> Self { + Self(raw) + } +} + +impl<'a> JsonRpcMethod<'a> { + /// Get inner representation of the method. + pub fn inner(&self) -> &'a str { + self.0 + } + + /// To owned. + pub fn to_owned(&self) -> JsonRpcMethodOwned { + JsonRpcMethodOwned(self.0.to_owned()) + } +} + +/// Serializable JSON-RPC params. +#[derive(Serialize, Debug, PartialEq)] +pub struct JsonRpcParams<'a>(Option<&'a str>); + +impl<'a> From> for JsonRpcParams<'a> { + fn from(raw: Option<&'a str>) -> Self { + Self(raw) + } +} + +impl<'a> JsonRpcParams<'a> { + /// Get inner representation of the params. + pub fn inner(&self) -> Option<&'a str> { + self.0 + } + + /// To owned. + pub fn to_owned(&self) -> JsonRpcParamsOwned { + JsonRpcParamsOwned(self.0.map(ToOwned::to_owned)) + } +} + +#[derive(Serialize, Debug)] +#[serde(untagged)] +pub enum JsonRpcRequest<'a> { + Single(JsonRpcCall<'a>), + Batch(Vec>), + Notif(JsonRpcNotification<'a>), +} + +impl<'a> From> for JsonRpcRequest<'a> { + fn from(call: JsonRpcCall<'a>) -> Self { + JsonRpcRequest::Single(call) + } +} +impl<'a> From>> for JsonRpcRequest<'a> { + fn from(batch: Vec>) -> Self { + JsonRpcRequest::Batch(batch) + } +} +impl<'a> From> for JsonRpcRequest<'a> { + fn from(notif: JsonRpcNotification<'a>) -> Self { + JsonRpcRequest::Notif(notif) + } +} + +/// Serializable [JSON-RPC object](https://www.jsonrpc.org/specification#request-object) +#[derive(Serialize, Debug)] +pub struct JsonRpcCall<'a> { + /// JSON-RPC version. + pub jsonrpc: TwoPointZero, + /// Name of the method to be invoked. + #[serde(borrow)] + pub method: &'a str, + /// Request ID + pub id: u64, + /// Parameter values of the request. + #[serde(borrow)] + pub params: Option<&'a str>, +} + +impl<'a> JsonRpcCall<'a> { + /// Create a new serializable JSON-RPC request. + pub fn new(id: u64, method: &'a str, params: Option<&'a str>) -> Self { + Self { jsonrpc: TwoPointZero, id, method, params } + } +} + +/// Serializable [JSON-RPC notification object](https://www.jsonrpc.org/specification#request-object) +#[derive(Serialize, Debug)] +pub struct JsonRpcNotification<'a> { + /// JSON-RPC version. + pub jsonrpc: TwoPointZero, + /// Name of the method to be invoked. + #[serde(borrow)] + pub method: &'a str, + /// Parameter values of the request. + #[serde(borrow)] + pub params: Option<&'a str>, +} + +impl<'a> JsonRpcNotification<'a> { + /// Create a new serializable JSON-RPC request. + pub fn new(method: &'a str, params: Option<&'a str>) -> Self { + Self { jsonrpc: TwoPointZero, method, params } + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct JsonRpcResponseObject { + /// JSON-RPC version. + pub jsonrpc: TwoPointZero, + /// Result. + pub result: T, + /// Request ID + pub id: u64, +} + +/// JSON-RPC parameter values for subscriptions. +#[derive(Serialize, Deserialize, Debug)] +pub struct JsonRpcNotificationParams { + /// Subscription ID + pub subscription: SubscriptionId, + /// Result. + pub result: T, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct JsonRpcResponseNotif { + pub jsonrpc: TwoPointZero, + pub params: JsonRpcNotificationParams, +} + +/// Represent the different JSON-RPC responses. +#[derive(Serialize, Deserialize, Debug)] +#[serde(untagged)] +pub enum JsonRpcResponse { + /// Single response. + Single(JsonRpcResponseObject), + /// Batch response. + Batch(Vec>), + /// Notification response used for subscriptions. + Subscription(JsonRpcResponseNotif), +} + +/// Id of a subscription, communicated by the server. +#[derive(Debug, PartialEq, Clone, Hash, Eq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +#[serde(untagged)] +pub enum SubscriptionId { + /// Numeric id + Num(u64), + /// String id + Str(String), +} diff --git a/types/src/v2/mod.rs b/types/src/v2/mod.rs index 416a061a4c..5db4965521 100644 --- a/types/src/v2/mod.rs +++ b/types/src/v2/mod.rs @@ -1,17 +1,19 @@ -use beef::lean::Cow; use serde::de::{self, Deserializer, Unexpected, Visitor}; use serde::ser::Serializer; use serde::{Deserialize, Serialize}; -use serde_json::value::RawValue; use std::fmt; +/// Niklas dummy types +pub mod dummy; /// Error type. pub mod error; - /// Traits. pub mod traits; +pub use beef::lean::Cow; pub use error::RpcError; +pub use serde_json::to_string; +pub use serde_json::value::{to_raw_value, RawValue}; /// [JSON-RPC request object](https://www.jsonrpc.org/specification#request-object) #[derive(Deserialize, Debug)] diff --git a/utils/src/http/hyper_helpers.rs b/utils/src/http/hyper_helpers.rs index 6cb71afe8e..05be83ff5b 100644 --- a/utils/src/http/hyper_helpers.rs +++ b/utils/src/http/hyper_helpers.rs @@ -91,16 +91,16 @@ pub fn read_header_values<'a>( #[cfg(test)] mod tests { use super::{read_header_content_length, read_response_to_body}; - use jsonrpsee_types::jsonrpc; + use jsonrpsee_types::v2::JsonRpcRequest; #[tokio::test] async fn body_to_request_works() { let s = r#"[{"a":"hello"}]"#; - let expected: jsonrpc::Request = serde_json::from_str(s).unwrap(); + let expected: JsonRpcRequest = serde_json::from_str(s).unwrap(); let body = hyper::Body::from(s.to_owned()); let headers = hyper::header::HeaderMap::new(); let bytes = read_response_to_body(&headers, body, 10 * 1024 * 1024).await.unwrap(); - let req: jsonrpc::Request = serde_json::from_slice(&bytes).unwrap(); + let req: JsonRpcRequest = serde_json::from_slice(&bytes).unwrap(); assert_eq!(req, expected); } diff --git a/ws-client/src/client.rs b/ws-client/src/client.rs index ea6fdfafd5..6c8357770d 100644 --- a/ws-client/src/client.rs +++ b/ws-client/src/client.rs @@ -35,16 +35,19 @@ use futures::{ prelude::*, sink::SinkExt, }; -use jsonrpc::DeserializeOwned; use jsonrpsee_types::{ client::{BatchMessage, FrontToBack, NotificationMessage, RequestMessage, Subscription, SubscriptionMessage}, error::Error, - jsonrpc::{self, JsonValue, SubscriptionId}, traits::{Client, SubscriptionClient}, + v2::dummy::{ + JsonRpcMethod, JsonRpcParams, JsonRpcParamsOwned, JsonRpcResponse, JsonRpcResponseObject, SubscriptionId, + }, }; +use serde::de::DeserializeOwned; +use serde_json::Value as JsonValue; +use std::borrow::Cow; use std::marker::PhantomData; use std::time::Duration; -use std::{borrow::Cow, convert::TryInto}; /// Wrapper over a [`oneshot::Receiver`](futures::channel::oneshot::Receiver) that reads /// the underlying channel once and then stores the result in String. @@ -84,6 +87,9 @@ impl ErrorFromBack { #[derive(Debug)] pub struct WsClient { /// Channel to send requests to the background task. + // + // NOTE(niklasad1): allow str slices instead of allocated Strings but it's hard to do because putting a lifetime on FrontToBack + // screws everything up. to_back: mpsc::Sender, /// If the background thread terminates the error is sent to this channel. // NOTE(niklasad1): This is a Mutex to circumvent that the async fns takes immutable references. @@ -229,37 +235,37 @@ impl WsClient { #[async_trait] impl Client for WsClient { - /// Send a notification to the server. - async fn notification(&self, method: M, params: P) -> Result<(), Error> - where - M: Into + Send, - P: Into + Send, - { - let method = method.into(); - let params = params.into(); + async fn notification<'a>(&self, method: JsonRpcMethod<'a>, params: JsonRpcParams<'a>) -> Result<(), Error> { log::trace!("[frontend]: send notification: method={:?}, params={:?}", method, params); - match self.to_back.clone().send(FrontToBack::Notification(NotificationMessage { method, params })).await { + match self + .to_back + .clone() + .send(FrontToBack::Notification(NotificationMessage { + method: method.to_owned(), + params: params.to_owned(), + })) + .await + { Ok(()) => Ok(()), Err(_) => Err(self.read_error_from_backend().await), } } - /// Perform a request towards the server. - async fn request(&self, method: M, params: P) -> Result + async fn request<'a, T>(&self, method: JsonRpcMethod<'a>, params: JsonRpcParams<'a>) -> Result where T: DeserializeOwned, - M: Into + Send, - P: Into + Send, { - let method = method.into(); - let params = params.into(); log::trace!("[frontend]: send request: method={:?}, params={:?}", method, params); let (send_back_tx, send_back_rx) = oneshot::channel(); if self .to_back .clone() - .send(FrontToBack::StartRequest(RequestMessage { method, params, send_back: Some(send_back_tx) })) + .send(FrontToBack::StartRequest(RequestMessage { + method: method.to_owned(), + params: params.to_owned(), + send_back: Some(send_back_tx), + })) .await .is_err() { @@ -282,15 +288,15 @@ impl Client for WsClient { Ok(Err(err)) => return Err(err), Err(_) => return Err(self.read_error_from_backend().await), }; - jsonrpc::from_value(json_value).map_err(Error::ParseError) + serde_json::from_value(json_value).map_err(Error::ParseError) } - async fn batch_request(&self, batch: Vec<(M, P)>) -> Result, Error> + async fn batch_request<'a, T>(&self, batch: Vec<(JsonRpcMethod<'a>, JsonRpcParams<'a>)>) -> Result, Error> where T: DeserializeOwned + Default + Clone, - M: Into + Send, - P: Into + Send, { + todo!(); + /* let (send_back_tx, send_back_rx) = oneshot::channel(); let requests: Vec<(String, jsonrpc::Params)> = batch.into_iter().map(|(r, p)| (r.into(), p.into())).collect(); log::trace!("[frontend]: send batch request: {:?}", requests); @@ -312,7 +318,7 @@ impl Client for WsClient { let values: Result<_, _> = json_values.into_iter().map(|val| jsonrpc::from_value(val).map_err(Error::ParseError)).collect(); - Ok(values?) + Ok(values?)*/ } } @@ -322,35 +328,31 @@ impl SubscriptionClient for WsClient { /// /// 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( + async fn subscribe<'a, N>( &self, - subscribe_method: SM, - params: P, - unsubscribe_method: UM, + subscribe_method: JsonRpcMethod<'a>, + params: JsonRpcParams<'a>, + unsubscribe_method: JsonRpcMethod<'a>, ) -> Result, Error> where - SM: Into + Send, - UM: Into + Send, - P: Into + Send, N: DeserializeOwned, { - let subscribe_method = subscribe_method.into(); - let unsubscribe_method = unsubscribe_method.into(); - let params = params.into(); + log::trace!("[frontend]: subscribe: {:?}, unsubscribe: {:?}", subscribe_method, unsubscribe_method); + let sub_method = subscribe_method.to_owned(); + let unsub_method = unsubscribe_method.to_owned(); if subscribe_method == unsubscribe_method { - return Err(Error::Subscription(subscribe_method, unsubscribe_method)); + return Err(Error::SubscriptionNameConflict(sub_method.0)); } - log::trace!("[frontend]: subscribe: {:?}, unsubscribe: {:?}", subscribe_method, unsubscribe_method); let (send_back_tx, send_back_rx) = oneshot::channel(); if self .to_back .clone() .send(FrontToBack::Subscribe(SubscriptionMessage { - subscribe_method, - unsubscribe_method, - params, + subscribe_method: sub_method, + unsubscribe_method: unsub_method, + params: params.to_owned(), send_back: send_back_tx, })) .await @@ -380,7 +382,8 @@ async fn background_task( let mut manager = RequestManager::new(max_concurrent_requests); let backend_event = futures::stream::unfold(receiver, |mut receiver| async { - let res = receiver.next_response().await; + // TODO: fix JsonValue here. + let res = receiver.next_response::().await; Some((res, receiver)) }); @@ -440,7 +443,7 @@ async fn background_task( stop_subscription(&mut sender, &mut manager, unsub).await; } } - Either::Right((Some(Ok(jsonrpc::Response::Single(response))), _)) => { + Either::Right((Some(Ok(JsonRpcResponse::Single(response))), _)) => { match process_response(&mut manager, response, max_notifs_per_subscription) { Ok(Some(unsub)) => { stop_subscription(&mut sender, &mut manager, unsub).await; @@ -452,29 +455,14 @@ async fn background_task( } } } - Either::Right((Some(Ok(jsonrpc::Response::Batch(batch))), _)) => { + Either::Right((Some(Ok(JsonRpcResponse::Batch(batch))), _)) => { let mut digest = Vec::with_capacity(batch.len()); let mut ordered_responses = vec![JsonValue::Null; batch.len()]; let mut rps_unordered: Vec<_> = Vec::with_capacity(batch.len()); for rp in batch { - let id = match rp.id().as_number().copied() { - Some(id) => id, - None => { - let _ = front_error.send(Error::InvalidRequestId); - return; - } - }; - let rp: Result = rp.try_into().map_err(Error::Request); - let rp = match rp { - Ok(rp) => rp, - Err(err) => { - let _ = front_error.send(err); - return; - } - }; - digest.push(id); - rps_unordered.push((id, rp)); + digest.push(rp.id); + rps_unordered.push((rp.id, rp.result)); } digest.sort_unstable(); @@ -497,7 +485,8 @@ async fn background_task( manager.reclaim_request_id(batch_state.request_id); let _ = batch_state.send_back.send(Ok(ordered_responses)); } - Either::Right((Some(Ok(jsonrpc::Response::Notif(notif))), _)) => { + Either::Right((Some(Ok(JsonRpcResponse::Subscription(notif))), _)) => { + log::info!("notif: {:?}", notif); let sub_id = notif.params.subscription; let request_id = match manager.get_request_id_by_subscription_id(&sub_id) { Some(r) => r, @@ -542,36 +531,26 @@ async fn background_task( /// Returns `Err(_)` if the response couldn't be handled. fn process_response( manager: &mut RequestManager, - response: jsonrpc::Output, + response: JsonRpcResponseObject, max_capacity_per_subscription: usize, ) -> Result, Error> { - let response_id: u64 = *response.id().as_number().ok_or(Error::InvalidRequestId)?; - - match manager.request_status(&response_id) { + match manager.request_status(&response.id) { RequestStatus::PendingMethodCall => { - let send_back_oneshot = match manager.complete_pending_call(response_id) { + let send_back_oneshot = match manager.complete_pending_call(response.id) { Some(Some(send)) => send, Some(None) => return Ok(None), None => return Err(Error::InvalidRequestId), }; - manager.reclaim_request_id(response_id); - let response = response.try_into().map_err(Error::Request); - let _ = send_back_oneshot.send(response); + manager.reclaim_request_id(response.id); + let _ = send_back_oneshot.send(Ok(response.result)); Ok(None) } RequestStatus::PendingSubscription => { let (send_back_oneshot, unsubscribe_method) = - manager.complete_pending_subscription(response_id).ok_or(Error::InvalidRequestId)?; - let json_sub_id: JsonValue = match response.try_into() { - Ok(response) => response, - Err(e) => { - let _ = send_back_oneshot.send(Err(Error::Request(e))); - return Ok(None); - } - }; + manager.complete_pending_subscription(response.id).ok_or(Error::InvalidRequestId)?; - let sub_id: SubscriptionId = match jsonrpc::from_value(json_sub_id) { + let sub_id: SubscriptionId = match serde_json::from_value(response.result) { Ok(sub_id) => sub_id, Err(_) => { let _ = send_back_oneshot.send(Err(Error::InvalidSubscriptionId)); @@ -579,6 +558,7 @@ fn process_response( } }; + let response_id = response.id; let (subscribe_tx, subscribe_rx) = mpsc::channel(max_capacity_per_subscription); if manager.insert_subscription(response_id, sub_id.clone(), subscribe_tx, unsubscribe_method).is_ok() { match send_back_oneshot.send(Ok((subscribe_rx, sub_id.clone()))) { @@ -614,6 +594,9 @@ fn build_unsubscribe_message( ) -> Option { let (_, unsub, sub_id) = manager.remove_subscription(req_id, sub_id)?; manager.reclaim_request_id(req_id); - let json_sub_id = jsonrpc::to_value(sub_id).expect("SubscriptionId to JSON is infallible; qed"); - Some(RequestMessage { method: unsub, params: jsonrpc::Params::Array(vec![json_sub_id]), send_back: None }) + // TODO(niklasad): better type for params or maybe a macro?!. + let sub_id_str = serde_json::to_string(&sub_id).expect("SubscriptionId to JSON is infallible; qed"); + let params_str = format!("[ {} ]", sub_id_str); + let params = JsonRpcParamsOwned(Some(params_str)); + Some(RequestMessage { method: unsub, params, send_back: None }) } diff --git a/ws-client/src/jsonrpc_transport.rs b/ws-client/src/jsonrpc_transport.rs index faad8ad2ac..d500097f3c 100644 --- a/ws-client/src/jsonrpc_transport.rs +++ b/ws-client/src/jsonrpc_transport.rs @@ -6,9 +6,12 @@ use crate::{ manager::RequestManager, transport::{self, WsConnectError}, }; -use jsonrpsee_types::client::{BatchMessage, NotificationMessage, RequestMessage, SubscriptionMessage}; -use jsonrpsee_types::error::Error; -use jsonrpsee_types::jsonrpc::{self, Request}; +use jsonrpsee_types::{ + client::{BatchMessage, NotificationMessage, RequestMessage, SubscriptionMessage}, + error::Error, + v2::dummy::{JsonRpcCall, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse}, +}; +use serde::de::DeserializeOwned; /// JSONRPC WebSocket sender. #[derive(Debug)] @@ -28,7 +31,8 @@ impl Sender { batch: BatchMessage, request_manager: &mut RequestManager, ) -> Result<(), Error> { - let req_id = request_manager.next_request_id()?; + todo!(); + /*let req_id = request_manager.next_request_id()?; let mut calls = Vec::with_capacity(batch.requests.len()); let mut ids = Vec::with_capacity(batch.requests.len()); @@ -58,7 +62,7 @@ impl Sender { request_manager.reclaim_request_id(req_id); Err(e) } - } + }*/ } /// Sends a request to the server but it doesn’t wait for a response. @@ -78,12 +82,7 @@ impl Sender { return Err(Error::Custom(str_err)); } }; - let req = jsonrpc::Request::Single(jsonrpc::Call::MethodCall(jsonrpc::MethodCall { - jsonrpc: jsonrpc::Version::V2, - method: request.method, - params: request.params, - id: jsonrpc::Id::Num(id), - })); + let req = JsonRpcCall::new(id, request.method.inner(), request.params.inner()); match self.transport.send_request(req).await { Ok(_) => { request_manager.insert_pending_call(id, request.send_back).expect("ID unused checked above; qed"); @@ -100,14 +99,9 @@ impl Sender { /// Sends a notification to the server. The notification doesn't need any response. /// /// Returns `Ok(())` if the notification was successfully sent otherwise `Err(_)`. - pub async fn send_notification(&mut self, notif: NotificationMessage) -> Result<(), Error> { - let request = jsonrpc::Request::Single(jsonrpc::Call::Notification(jsonrpc::Notification { - jsonrpc: jsonrpc::Version::V2, - method: notif.method, - params: notif.params, - })); - - self.transport.send_request(request).await.map_err(|e| Error::TransportError(Box::new(e))) + pub async fn send_notification<'a>(&mut self, notif: NotificationMessage) -> Result<(), Error> { + let notif = JsonRpcNotification::new(notif.method.inner(), notif.params.inner()); + self.transport.send_request(notif).await.map_err(|e| Error::TransportError(Box::new(e))) } /// Sends a request to the server to start a new subscription but it doesn't wait for a response. @@ -116,31 +110,26 @@ impl Sender { /// Returns `Ok()` if the request was successfully sent otherwise `Err(_)`. pub async fn start_subscription( &mut self, - subscription: SubscriptionMessage, + sub: SubscriptionMessage, request_manager: &mut RequestManager, ) -> Result<(), Error> { let id = match request_manager.next_request_id() { Ok(id) => id, Err(err) => { let str_err = err.to_string(); - let _ = subscription.send_back.send(Err(err)); + let _ = sub.send_back.send(Err(err)); return Err(Error::Custom(str_err)); } }; + let req = JsonRpcCall::new(id, sub.subscribe_method.inner(), sub.params.inner()); - let req = jsonrpc::Request::Single(jsonrpc::Call::MethodCall(jsonrpc::MethodCall { - jsonrpc: jsonrpc::Version::V2, - method: subscription.subscribe_method, - params: subscription.params, - id: jsonrpc::Id::Num(id as u64), - })); if let Err(e) = self.transport.send_request(req).await { let str_err = e.to_string(); - let _ = subscription.send_back.send(Err(Error::TransportError(Box::new(e)))); + let _ = sub.send_back.send(Err(Error::TransportError(Box::new(e)))); return Err(Error::Custom(str_err)); } request_manager - .insert_pending_subscription(id, subscription.send_back, subscription.unsubscribe_method) + .insert_pending_subscription(id, sub.send_back, sub.unsubscribe_method) .expect("Request ID unused checked above; qed"); Ok(()) } @@ -159,7 +148,10 @@ impl Receiver { } /// Reads the next response, fails if the response ID was not a number. - pub async fn next_response(&mut self) -> Result { + pub async fn next_response(&mut self) -> Result, WsConnectError> + where + T: DeserializeOwned + std::fmt::Debug, + { self.transport.next_response().await } } diff --git a/ws-client/src/manager.rs b/ws-client/src/manager.rs index 3cd89677e6..b33fd81946 100644 --- a/ws-client/src/manager.rs +++ b/ws-client/src/manager.rs @@ -10,8 +10,9 @@ use fnv::FnvHashMap; use futures::channel::{mpsc, oneshot}; use jsonrpsee_types::{ error::Error, - jsonrpc::{JsonValue, SubscriptionId}, + v2::dummy::{JsonRpcMethodOwned, SubscriptionId}, }; +use serde_json::Value as JsonValue; use std::collections::{ hash_map::{Entry, HashMap}, VecDeque, @@ -41,7 +42,7 @@ type PendingCallOneshot = Option>>; type PendingBatchOneshot = oneshot::Sender, Error>>; type PendingSubscriptionOneshot = oneshot::Sender, SubscriptionId), Error>>; type SubscriptionSink = mpsc::Sender; -type UnsubscribeMethod = String; +type UnsubscribeMethod = JsonRpcMethodOwned; /// Unique ID that are generated by the RequestManager. // TODO: new type for this https://github.com/paritytech/jsonrpsee/issues/249 type RequestId = u64; @@ -167,7 +168,7 @@ impl RequestManager { request_id: RequestId, subscription_id: SubscriptionId, send_back: SubscriptionSink, - unsubscribe_method: String, + unsubscribe_method: UnsubscribeMethod, ) -> Result<(), SubscriptionSink> { if let (Entry::Vacant(request), Entry::Vacant(subscription)) = (self.requests.entry(request_id), self.subscriptions.entry(subscription_id)) diff --git a/ws-client/src/transport.rs b/ws-client/src/transport.rs index d77ae97936..72c61cb990 100644 --- a/ws-client/src/transport.rs +++ b/ws-client/src/transport.rs @@ -28,7 +28,8 @@ use async_std::net::TcpStream; use async_tls::client::TlsStream; use futures::io::{BufReader, BufWriter}; use futures::prelude::*; -use jsonrpsee_types::jsonrpc; +use jsonrpsee_types::v2::dummy::{JsonRpcNotification, JsonRpcRequest, JsonRpcResponse}; +use serde::de::DeserializeOwned; use soketto::connection; use soketto::handshake::client::{Client as WsRawClient, ServerResponse}; use std::{borrow::Cow, io, net::SocketAddr, time::Duration}; @@ -156,12 +157,12 @@ pub enum WsConnectError { } impl Sender { - /// Sends out out a request. Returns a `Future` that finishes when the request has been + /// Sends out a request. Returns a `Future` that finishes when the request has been /// successfully sent. - pub async fn send_request(&mut self, request: jsonrpc::Request) -> Result<(), WsConnectError> { - log::debug!("send: {}", request); - let request = jsonrpc::to_vec(&request).map_err(WsConnectError::Serialization)?; - self.inner.send_binary(request).await?; + pub async fn send_request<'a>(&mut self, request: impl Into>) -> Result<(), WsConnectError> { + let body = serde_json::to_string(&request.into()).map_err(WsConnectError::Serialization)?; + log::debug!("send: {}", body); + self.inner.send_text(body).await?; self.inner.flush().await?; Ok(()) } @@ -169,12 +170,16 @@ impl Sender { impl Receiver { /// Returns a `Future` resolving when the server sent us something back. - pub async fn next_response(&mut self) -> Result { + pub async fn next_response(&mut self) -> Result, WsConnectError> + where + T: DeserializeOwned + std::fmt::Debug, + { let mut message = Vec::new(); self.inner.receive_data(&mut message).await?; - let response = jsonrpc::from_slice(&message).map_err(WsConnectError::ParseError)?; - log::debug!("recv: {}", response); + let response = serde_json::from_slice(&message).map_err(WsConnectError::ParseError)?; + log::debug!("recv: {:?}", response); + Ok(response) } } From 17bce9437cf856e56fb366d113b6425995dad1c1 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 9 Apr 2021 19:18:27 +0200 Subject: [PATCH 02/41] v2 --- examples/examples/http.rs | 17 ++++- examples/examples/ws.rs | 5 +- http-client/src/client.rs | 25 ++++--- http-client/src/transport.rs | 14 ++-- types/src/client.rs | 4 +- types/src/traits.rs | 27 ++++--- types/src/v2/dummy.rs | 112 +++++++++++++++++------------ ws-client/src/client.rs | 54 +++++++------- ws-client/src/jsonrpc_transport.rs | 15 ++-- ws-client/src/transport.rs | 7 +- 10 files changed, 170 insertions(+), 110 deletions(-) diff --git a/examples/examples/http.rs b/examples/examples/http.rs index b3acb3b892..2955886877 100644 --- a/examples/examples/http.rs +++ b/examples/examples/http.rs @@ -28,7 +28,10 @@ use std::net::SocketAddr; use jsonrpsee_http_client::HttpClientBuilder; use jsonrpsee_http_server::HttpServerBuilder; -use jsonrpsee_types::{traits::Client, v2::RawValue}; +use jsonrpsee_types::{ + traits::Client, + v2::{dummy::JsonRpcParams, RawValue}, +}; #[tokio::main] async fn main() -> Result<(), Box> { @@ -37,8 +40,10 @@ async fn main() -> Result<(), Box> { let server_addr = run_server().await; let url = format!("http://{}", server_addr); + let params: JsonRpcParams<_> = vec![&1_u64, &2, &3].into(); + let client = HttpClientBuilder::default().build(url)?; - let response: Result = client.request("say_hello".into(), None.into()).await; + let response: Result = client.request("say_hello", params).await; println!("r: {:?}", response); Ok(()) @@ -46,7 +51,13 @@ async fn main() -> Result<(), Box> { async fn run_server() -> SocketAddr { let mut server = HttpServerBuilder::default().build("127.0.0.1:0".parse().unwrap()).unwrap(); - server.register_method("say_hello", |_| Ok("lo")).unwrap(); + server + .register_method("say_hello", |params| { + let params: Vec = params.parse()?; + let sum: u64 = params.into_iter().sum(); + Ok(sum) + }) + .unwrap(); let addr = server.local_addr().unwrap(); tokio::spawn(async move { server.start().await.unwrap() }); addr diff --git a/examples/examples/ws.rs b/examples/examples/ws.rs index 745b70e6ec..13b4438d11 100644 --- a/examples/examples/ws.rs +++ b/examples/examples/ws.rs @@ -25,7 +25,7 @@ // DEALINGS IN THE SOFTWARE. use futures::channel::oneshot::{self, Sender}; -use jsonrpsee_types::traits::Client; +use jsonrpsee_types::{traits::Client, v2::dummy::JsonRpcParams}; use jsonrpsee_ws_client::WsClientBuilder; use jsonrpsee_ws_server::WsServer; use tokio::task; @@ -44,7 +44,8 @@ async fn main() -> Result<(), Box> { server_started_rx.await?; let client = WsClientBuilder::default().build(SERVER_URI).await?; - let response: String = client.request("say_hello".into(), None.into()).await?; + let params: JsonRpcParams = None.into(); + let response: String = client.request("say_hello", params).await?; println!("r: {:?}", response); Ok(()) diff --git a/http-client/src/client.rs b/http-client/src/client.rs index 628f7d7e3b..20bb152e6e 100644 --- a/http-client/src/client.rs +++ b/http-client/src/client.rs @@ -6,7 +6,7 @@ use jsonrpsee_types::{ traits::Client, v2::dummy::{JsonRpcCall, JsonRpcMethod, JsonRpcNotification, JsonRpcParams, JsonRpcResponse}, }; -use serde::de::DeserializeOwned; +use serde::{de::DeserializeOwned, Serialize}; use std::sync::atomic::{AtomicU64, Ordering}; const SINGLE_RESPONSE: &str = "Single Response"; @@ -51,19 +51,23 @@ pub struct HttpClient { #[async_trait] impl Client for HttpClient { - async fn notification<'a>(&self, method: JsonRpcMethod<'a>, params: JsonRpcParams<'a>) -> Result<(), Error> { - let notif = JsonRpcNotification::new(method.inner(), params.inner()); + async fn notification<'a, T>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result<(), Error> + where + T: Serialize + std::fmt::Debug + PartialEq + Send + Sync, + { + let notif = JsonRpcNotification::new(method, params); self.transport.send_notification(notif).await.map_err(|e| Error::TransportError(Box::new(e))) } /// Perform a request towards the server. - async fn request<'a, T>(&self, method: JsonRpcMethod<'a>, params: JsonRpcParams<'a>) -> Result + async fn request<'a, T, R>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result where - T: DeserializeOwned, + T: Serialize + std::fmt::Debug + PartialEq + Send + Sync, + R: DeserializeOwned, { // NOTE: `fetch_add` wraps on overflow which is intended. let id = self.request_id.fetch_add(1, Ordering::Relaxed); - let request = JsonRpcCall::new(id, method.inner(), params.inner()); + let request = JsonRpcCall::new(id, method, params); let response = self .transport @@ -79,9 +83,10 @@ impl Client for HttpClient { } } - async fn batch_request<'a, T>(&self, batch: Vec<(JsonRpcMethod<'a>, JsonRpcParams<'a>)>) -> Result, Error> + async fn batch_request<'a, T, R>(&self, batch: Vec<(&'a str, JsonRpcParams<'a, T>)>) -> Result, Error> where - T: DeserializeOwned + Default + Clone, + T: Serialize + std::fmt::Debug + PartialEq + Send + Sync, + R: DeserializeOwned + Default + Clone, { let mut batch_request = Vec::with_capacity(batch.len()); // NOTE(niklasad1): `ID` is not necessarily monotonically increasing. @@ -90,7 +95,7 @@ impl Client for HttpClient { for (pos, (method, params)) in batch.into_iter().enumerate() { let id = self.request_id.fetch_add(1, Ordering::SeqCst); - batch_request.push(JsonRpcCall::new(id, method.inner(), params.inner())); + batch_request.push(JsonRpcCall::new(id, method, params)); ordered_requests.push(id); request_set.insert(id, pos); } @@ -106,7 +111,7 @@ impl Client for HttpClient { JsonRpcResponse::Subscription(_notif) => Err(invalid_response(BATCH_RESPONSE, SUBSCRIPTION_RESPONSE)), JsonRpcResponse::Batch(rps) => { // NOTE: `T::default` is placeholder and will be replaced in loop below. - let mut responses = vec![T::default(); ordered_requests.len()]; + let mut responses = vec![R::default(); ordered_requests.len()]; for rp in rps { let pos = match request_set.get(&rp.id) { Some(pos) => *pos, diff --git a/http-client/src/transport.rs b/http-client/src/transport.rs index 40a57348af..9967c1a4d2 100644 --- a/http-client/src/transport.rs +++ b/http-client/src/transport.rs @@ -69,19 +69,23 @@ impl HttpTransportClient { } /// Send notification. - pub async fn send_notification<'a>(&self, notif: JsonRpcNotification<'a>) -> Result<(), Error> { + pub async fn send_notification<'a, T>(&self, notif: JsonRpcNotification<'a, T>) -> Result<(), Error> + where + T: Serialize + std::fmt::Debug + PartialEq, + { let body = serde_json::to_string(¬if).map_err(Error::Serialization)?; let _response = self.send(body).await?; Ok(()) } /// Send request and wait for response. - pub async fn send_request_and_wait_for_response<'a, T>( + pub async fn send_request_and_wait_for_response<'a, T, R>( &self, - request: impl Into>, - ) -> Result, Error> + request: impl Into>, + ) -> Result, Error> where - T: DeserializeOwned, + T: Serialize + std::fmt::Debug + PartialEq + 'a, + R: DeserializeOwned, { let body = serde_json::to_string(&request.into()).map_err(Error::Serialization)?; let response = self.send(body).await?; diff --git a/types/src/client.rs b/types/src/client.rs index 340d6fc748..b35ff2fed7 100644 --- a/types/src/client.rs +++ b/types/src/client.rs @@ -1,6 +1,6 @@ use crate::{ error::Error, - v2::dummy::{JsonRpcMethod, JsonRpcMethodOwned, JsonRpcParams, JsonRpcParamsOwned, SubscriptionId}, + v2::dummy::{JsonRpcMethod, JsonRpcMethodOwned, JsonRpcParams, SubscriptionId}, }; use core::marker::PhantomData; use futures::channel::{mpsc, oneshot}; @@ -8,6 +8,8 @@ use futures::prelude::*; use serde::de::DeserializeOwned; use serde_json::Value as JsonValue; +type JsonRpcParamsOwned = String; + /// Active subscription on a Client. pub struct Subscription { /// Channel to send requests to the background task. diff --git a/types/src/traits.rs b/types/src/traits.rs index d6607cd9a7..bd5bc4ff2d 100644 --- a/types/src/traits.rs +++ b/types/src/traits.rs @@ -4,18 +4,21 @@ use crate::{ v2::dummy::{JsonRpcMethod, JsonRpcParams}, }; use async_trait::async_trait; -use serde::de::DeserializeOwned; +use serde::{de::DeserializeOwned, Serialize}; /// [JSON-RPC](https://www.jsonrpc.org/specification) client interface that can make requests and notifications. #[async_trait] pub trait Client { /// Send a [notification request](https://www.jsonrpc.org/specification#notification) - async fn notification<'a>(&self, method: JsonRpcMethod<'a>, params: JsonRpcParams<'a>) -> Result<(), Error>; + async fn notification<'a, T>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result<(), Error> + where + T: Serialize + std::fmt::Debug + PartialEq + Send + Sync + Clone; /// Send a [method call request](https://www.jsonrpc.org/specification#request_object). - async fn request<'a, T>(&self, method: JsonRpcMethod<'a>, params: JsonRpcParams<'a>) -> Result + async fn request<'a, T, R>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result where - T: DeserializeOwned; + T: Serialize + std::fmt::Debug + PartialEq + Send + Sync + Clone, + R: DeserializeOwned; /// Send a [batch request](https://www.jsonrpc.org/specification#batch). /// @@ -23,9 +26,10 @@ pub trait Client { /// /// 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, T>(&self, batch: Vec<(JsonRpcMethod<'a>, JsonRpcParams<'a>)>) -> Result, Error> + async fn batch_request<'a, T, R>(&self, batch: Vec<(&'a str, JsonRpcParams<'a, T>)>) -> Result, Error> where - T: DeserializeOwned + Default + Clone; + T: Serialize + std::fmt::Debug + PartialEq + Send + Sync + Clone, + R: DeserializeOwned + Default + Clone; } /// [JSON-RPC](https://www.jsonrpc.org/specification) client interface that can make requests, notifications and subscriptions. @@ -39,12 +43,13 @@ pub trait SubscriptionClient: Client { /// The `unsubscribe_method` is used to close the subscription. /// /// The `Notif` param is a generic type to receive generic subscriptions, see [`Subscription`](crate::client::Subscription) for further documentation. - async fn subscribe<'a, Notif>( + async fn subscribe<'a, T, Notif>( &self, - subscribe_method: JsonRpcMethod<'a>, - params: JsonRpcParams<'a>, - unsubscribe_method: JsonRpcMethod<'a>, + subscribe_method: &'a str, + params: JsonRpcParams<'a, T>, + unsubscribe_method: &'a str, ) -> Result, Error> where - Notif: DeserializeOwned; + Notif: DeserializeOwned, + T: Serialize + std::fmt::Debug + PartialEq + Send + Sync + Clone; } diff --git a/types/src/v2/dummy.rs b/types/src/v2/dummy.rs index bef41d122b..8ca07ad879 100644 --- a/types/src/v2/dummy.rs +++ b/types/src/v2/dummy.rs @@ -14,17 +14,6 @@ impl JsonRpcMethodOwned { } } -#[derive(Debug)] -/// JSON-RPC method call. -pub struct JsonRpcParamsOwned(pub Option); - -impl JsonRpcParamsOwned { - /// Get inner. - pub fn inner(&self) -> Option<&str> { - self.0.as_ref().map(|p| p.as_str()) - } -} - /// Serializable JSON-RPC method call. #[derive(Serialize, Debug, PartialEq)] pub struct JsonRpcMethod<'a>(&'a str); @@ -47,55 +36,77 @@ impl<'a> JsonRpcMethod<'a> { } } -/// Serializable JSON-RPC params. #[derive(Serialize, Debug, PartialEq)] -pub struct JsonRpcParams<'a>(Option<&'a str>); - -impl<'a> From> for JsonRpcParams<'a> { - fn from(raw: Option<&'a str>) -> Self { - Self(raw) +#[serde(untagged)] +pub enum JsonRpcParams<'a, T> +where + T: Serialize + std::fmt::Debug + PartialEq, +{ + NoParams, + Array(Vec<&'a T>), +} + +impl<'a, T> From> for JsonRpcParams<'a, T> +where + T: Serialize + std::fmt::Debug + PartialEq, +{ + fn from(_raw: Option<&'a T>) -> Self { + Self::NoParams } } -impl<'a> JsonRpcParams<'a> { - /// Get inner representation of the params. - pub fn inner(&self) -> Option<&'a str> { - self.0 - } - - /// To owned. - pub fn to_owned(&self) -> JsonRpcParamsOwned { - JsonRpcParamsOwned(self.0.map(ToOwned::to_owned)) +impl<'a, T> From> for JsonRpcParams<'a, T> +where + T: Serialize + std::fmt::Debug + PartialEq, +{ + fn from(arr: Vec<&'a T>) -> Self { + Self::Array(arr) } } #[derive(Serialize, Debug)] #[serde(untagged)] -pub enum JsonRpcRequest<'a> { - Single(JsonRpcCall<'a>), - Batch(Vec>), - Notif(JsonRpcNotification<'a>), -} - -impl<'a> From> for JsonRpcRequest<'a> { - fn from(call: JsonRpcCall<'a>) -> Self { +pub enum JsonRpcRequest<'a, T> +where + T: Serialize + std::fmt::Debug + PartialEq + 'a, +{ + Single(JsonRpcCall<'a, T>), + Batch(Vec>), + Notif(JsonRpcNotification<'a, T>), +} + +impl<'a, T> From> for JsonRpcRequest<'a, T> +where + T: Serialize + std::fmt::Debug + PartialEq, +{ + fn from(call: JsonRpcCall<'a, T>) -> Self { JsonRpcRequest::Single(call) } } -impl<'a> From>> for JsonRpcRequest<'a> { - fn from(batch: Vec>) -> Self { +impl<'a, T> From>> for JsonRpcRequest<'a, T> +where + T: Serialize + std::fmt::Debug + PartialEq, +{ + fn from(batch: Vec>) -> Self { JsonRpcRequest::Batch(batch) } } -impl<'a> From> for JsonRpcRequest<'a> { - fn from(notif: JsonRpcNotification<'a>) -> Self { + +impl<'a, T> From> for JsonRpcRequest<'a, T> +where + T: Serialize + std::fmt::Debug + PartialEq, +{ + fn from(notif: JsonRpcNotification<'a, T>) -> Self { JsonRpcRequest::Notif(notif) } } /// Serializable [JSON-RPC object](https://www.jsonrpc.org/specification#request-object) #[derive(Serialize, Debug)] -pub struct JsonRpcCall<'a> { +pub struct JsonRpcCall<'a, T> +where + T: Serialize + std::fmt::Debug + PartialEq, +{ /// JSON-RPC version. pub jsonrpc: TwoPointZero, /// Name of the method to be invoked. @@ -105,19 +116,25 @@ pub struct JsonRpcCall<'a> { pub id: u64, /// Parameter values of the request. #[serde(borrow)] - pub params: Option<&'a str>, + pub params: JsonRpcParams<'a, T>, } -impl<'a> JsonRpcCall<'a> { +impl<'a, T> JsonRpcCall<'a, T> +where + T: Serialize + std::fmt::Debug + PartialEq, +{ /// Create a new serializable JSON-RPC request. - pub fn new(id: u64, method: &'a str, params: Option<&'a str>) -> Self { + pub fn new(id: u64, method: &'a str, params: JsonRpcParams<'a, T>) -> Self { Self { jsonrpc: TwoPointZero, id, method, params } } } /// Serializable [JSON-RPC notification object](https://www.jsonrpc.org/specification#request-object) #[derive(Serialize, Debug)] -pub struct JsonRpcNotification<'a> { +pub struct JsonRpcNotification<'a, T> +where + T: Serialize + std::fmt::Debug + PartialEq, +{ /// JSON-RPC version. pub jsonrpc: TwoPointZero, /// Name of the method to be invoked. @@ -125,12 +142,15 @@ pub struct JsonRpcNotification<'a> { pub method: &'a str, /// Parameter values of the request. #[serde(borrow)] - pub params: Option<&'a str>, + pub params: JsonRpcParams<'a, T>, } -impl<'a> JsonRpcNotification<'a> { +impl<'a, T> JsonRpcNotification<'a, T> +where + T: Serialize + std::fmt::Debug + PartialEq, +{ /// Create a new serializable JSON-RPC request. - pub fn new(method: &'a str, params: Option<&'a str>) -> Self { + pub fn new(method: &'a str, params: JsonRpcParams<'a, T>) -> Self { Self { jsonrpc: TwoPointZero, method, params } } } diff --git a/ws-client/src/client.rs b/ws-client/src/client.rs index 6c8357770d..943f1aa34d 100644 --- a/ws-client/src/client.rs +++ b/ws-client/src/client.rs @@ -39,11 +39,9 @@ use jsonrpsee_types::{ client::{BatchMessage, FrontToBack, NotificationMessage, RequestMessage, Subscription, SubscriptionMessage}, error::Error, traits::{Client, SubscriptionClient}, - v2::dummy::{ - JsonRpcMethod, JsonRpcParams, JsonRpcParamsOwned, JsonRpcResponse, JsonRpcResponseObject, SubscriptionId, - }, + v2::dummy::{JsonRpcMethod, JsonRpcParams, JsonRpcResponse, JsonRpcResponseObject, SubscriptionId}, }; -use serde::de::DeserializeOwned; +use serde::{de::DeserializeOwned, Serialize}; use serde_json::Value as JsonValue; use std::borrow::Cow; use std::marker::PhantomData; @@ -235,27 +233,33 @@ impl WsClient { #[async_trait] impl Client for WsClient { - async fn notification<'a>(&self, method: JsonRpcMethod<'a>, params: JsonRpcParams<'a>) -> Result<(), Error> { - log::trace!("[frontend]: send notification: method={:?}, params={:?}", method, params); + async fn notification<'a, T>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result<(), Error> + where + T: Serialize + std::fmt::Debug + PartialEq + Send + Sync + Clone, + { + todo!(); + /*log::trace!("[frontend]: send notification: method={:?}, params={:?}", method, params); match self .to_back .clone() .send(FrontToBack::Notification(NotificationMessage { - method: method.to_owned(), + method: JsonRpcMethodOwned(method.to_string()), params: params.to_owned(), })) .await { Ok(()) => Ok(()), Err(_) => Err(self.read_error_from_backend().await), - } + }*/ } - async fn request<'a, T>(&self, method: JsonRpcMethod<'a>, params: JsonRpcParams<'a>) -> Result + async fn request<'a, T, R>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result where - T: DeserializeOwned, + T: Serialize + std::fmt::Debug + PartialEq + Send + Sync + Clone, + R: DeserializeOwned, { - log::trace!("[frontend]: send request: method={:?}, params={:?}", method, params); + todo!(); + /*log::trace!("[frontend]: send request: method={:?}, params={:?}", method, params); let (send_back_tx, send_back_rx) = oneshot::channel(); if self @@ -288,12 +292,13 @@ impl Client for WsClient { Ok(Err(err)) => return Err(err), Err(_) => return Err(self.read_error_from_backend().await), }; - serde_json::from_value(json_value).map_err(Error::ParseError) + serde_json::from_value(json_value).map_err(Error::ParseError)*/ } - async fn batch_request<'a, T>(&self, batch: Vec<(JsonRpcMethod<'a>, JsonRpcParams<'a>)>) -> Result, Error> + async fn batch_request<'a, T, R>(&self, batch: Vec<(&'a str, JsonRpcParams<'a, T>)>) -> Result, Error> where - T: DeserializeOwned + Default + Clone, + T: Serialize + std::fmt::Debug + PartialEq + Send + Sync + Clone, + R: DeserializeOwned + Default + Clone, { todo!(); /* @@ -328,16 +333,18 @@ impl SubscriptionClient for WsClient { /// /// 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, T, N>( &self, - subscribe_method: JsonRpcMethod<'a>, - params: JsonRpcParams<'a>, - unsubscribe_method: JsonRpcMethod<'a>, + subscribe_method: &'a str, + params: JsonRpcParams<'a, T>, + unsubscribe_method: &'a str, ) -> Result, Error> where N: DeserializeOwned, + T: Serialize + std::fmt::Debug + PartialEq + Send + Sync + Clone, { - log::trace!("[frontend]: subscribe: {:?}, unsubscribe: {:?}", subscribe_method, unsubscribe_method); + todo!(); + /*log::trace!("[frontend]: subscribe: {:?}, unsubscribe: {:?}", subscribe_method, unsubscribe_method); let sub_method = subscribe_method.to_owned(); let unsub_method = unsubscribe_method.to_owned(); @@ -366,7 +373,7 @@ impl SubscriptionClient for WsClient { Ok(Err(err)) => return Err(err), Err(_) => return Err(self.read_error_from_backend().await), }; - Ok(Subscription { to_back: self.to_back.clone(), notifs_rx, marker: PhantomData, id }) + Ok(Subscription { to_back: self.to_back.clone(), notifs_rx, marker: PhantomData, id })*/ } } @@ -595,8 +602,7 @@ fn build_unsubscribe_message( let (_, unsub, sub_id) = manager.remove_subscription(req_id, sub_id)?; manager.reclaim_request_id(req_id); // TODO(niklasad): better type for params or maybe a macro?!. - let sub_id_str = serde_json::to_string(&sub_id).expect("SubscriptionId to JSON is infallible; qed"); - let params_str = format!("[ {} ]", sub_id_str); - let params = JsonRpcParamsOwned(Some(params_str)); - Some(RequestMessage { method: unsub, params, send_back: None }) + let params: JsonRpcParams<_> = vec![&sub_id].into(); + todo!(); + //Some(RequestMessage { method: unsub, params, send_back: None }) } diff --git a/ws-client/src/jsonrpc_transport.rs b/ws-client/src/jsonrpc_transport.rs index d500097f3c..9f107e1157 100644 --- a/ws-client/src/jsonrpc_transport.rs +++ b/ws-client/src/jsonrpc_transport.rs @@ -74,7 +74,8 @@ impl Sender { request: RequestMessage, request_manager: &mut RequestManager, ) -> Result<(), Error> { - let id = match request_manager.next_request_id() { + todo!(); + /*let id = match request_manager.next_request_id() { Ok(id) => id, Err(err) => { let str_err = err.to_string(); @@ -93,15 +94,16 @@ impl Sender { let _ = request.send_back.map(|tx| tx.send(Err(Error::TransportError(Box::new(e))))); Err(Error::Custom(str_err)) } - } + }*/ } /// Sends a notification to the server. The notification doesn't need any response. /// /// Returns `Ok(())` if the notification was successfully sent otherwise `Err(_)`. pub async fn send_notification<'a>(&mut self, notif: NotificationMessage) -> Result<(), Error> { - let notif = JsonRpcNotification::new(notif.method.inner(), notif.params.inner()); - self.transport.send_request(notif).await.map_err(|e| Error::TransportError(Box::new(e))) + todo!(); + //let notif = JsonRpcNotification::new(notif.method.inner(), notif.params.inner()); + //self.transport.send_request(notif).await.map_err(|e| Error::TransportError(Box::new(e))) } /// Sends a request to the server to start a new subscription but it doesn't wait for a response. @@ -113,7 +115,8 @@ impl Sender { sub: SubscriptionMessage, request_manager: &mut RequestManager, ) -> Result<(), Error> { - let id = match request_manager.next_request_id() { + todo!(); + /* let id = match request_manager.next_request_id() { Ok(id) => id, Err(err) => { let str_err = err.to_string(); @@ -130,7 +133,7 @@ impl Sender { } request_manager .insert_pending_subscription(id, sub.send_back, sub.unsubscribe_method) - .expect("Request ID unused checked above; qed"); + .expect("Request ID unused checked above; qed");*/ Ok(()) } } diff --git a/ws-client/src/transport.rs b/ws-client/src/transport.rs index 72c61cb990..fad2ccf83c 100644 --- a/ws-client/src/transport.rs +++ b/ws-client/src/transport.rs @@ -29,7 +29,7 @@ use async_tls::client::TlsStream; use futures::io::{BufReader, BufWriter}; use futures::prelude::*; use jsonrpsee_types::v2::dummy::{JsonRpcNotification, JsonRpcRequest, JsonRpcResponse}; -use serde::de::DeserializeOwned; +use serde::{de::DeserializeOwned, Serialize}; use soketto::connection; use soketto::handshake::client::{Client as WsRawClient, ServerResponse}; use std::{borrow::Cow, io, net::SocketAddr, time::Duration}; @@ -159,7 +159,10 @@ pub enum WsConnectError { impl Sender { /// Sends out a request. Returns a `Future` that finishes when the request has been /// successfully sent. - pub async fn send_request<'a>(&mut self, request: impl Into>) -> Result<(), WsConnectError> { + pub async fn send_request<'a, T>(&mut self, request: impl Into>) -> Result<(), WsConnectError> + where + T: Serialize + std::fmt::Debug + PartialEq + 'a, + { let body = serde_json::to_string(&request.into()).map_err(WsConnectError::Serialization)?; log::debug!("send: {}", body); self.inner.send_text(body).await?; From ef11d59835a5d09861b7f61f283ac6a7de551f8e Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 13 Apr 2021 12:14:24 +0200 Subject: [PATCH 03/41] PoC works without batch request --- examples/examples/ws_subscription.rs | 8 +- http-client/src/client.rs | 2 +- types/src/client.rs | 44 +++----- types/src/traits.rs | 13 +-- types/src/v2/dummy.rs | 47 ++------ ws-client/src/client.rs | 139 ++++++++++++----------- ws-client/src/jsonrpc_transport.rs | 160 --------------------------- ws-client/src/lib.rs | 2 - ws-client/src/manager.rs | 10 +- ws-client/src/transport.rs | 6 +- 10 files changed, 121 insertions(+), 310 deletions(-) delete mode 100644 ws-client/src/jsonrpc_transport.rs diff --git a/examples/examples/ws_subscription.rs b/examples/examples/ws_subscription.rs index cab947734f..fa15e7e3a9 100644 --- a/examples/examples/ws_subscription.rs +++ b/examples/examples/ws_subscription.rs @@ -25,7 +25,7 @@ // DEALINGS IN THE SOFTWARE. use futures::channel::oneshot::{self, Sender}; -use jsonrpsee_types::traits::SubscriptionClient; +use jsonrpsee_types::{traits::SubscriptionClient, v2::dummy::JsonRpcParams}; use jsonrpsee_ws_client::{WsClientBuilder, WsSubscription}; use jsonrpsee_ws_server::WsServer; use tokio::task; @@ -45,8 +45,9 @@ async fn main() -> Result<(), Box> { server_started_rx.await?; let client = WsClientBuilder::default().build(SERVER_URI).await?; + let params: JsonRpcParams = None.into(); let mut subscribe_hello: WsSubscription = - client.subscribe("subscribe_hello".into(), None.into(), "unsubscribe_hello".into()).await?; + client.subscribe("subscribe_hello", params, "unsubscribe_hello").await?; let mut i = 0; while i <= NUM_SUBSCRIPTION_RESPONSES { @@ -56,8 +57,9 @@ async fn main() -> Result<(), Box> { } drop(subscribe_hello); + drop(client); - loop {} + std::thread::sleep(std::time::Duration::from_secs(1)); Ok(()) } diff --git a/http-client/src/client.rs b/http-client/src/client.rs index 20bb152e6e..3faaabc118 100644 --- a/http-client/src/client.rs +++ b/http-client/src/client.rs @@ -4,7 +4,7 @@ use fnv::FnvHashMap; use jsonrpsee_types::{ error::{Error, Mismatch}, traits::Client, - v2::dummy::{JsonRpcCall, JsonRpcMethod, JsonRpcNotification, JsonRpcParams, JsonRpcResponse}, + v2::dummy::{JsonRpcCall, JsonRpcNotification, JsonRpcParams, JsonRpcResponse}, }; use serde::{de::DeserializeOwned, Serialize}; use std::sync::atomic::{AtomicU64, Ordering}; diff --git a/types/src/client.rs b/types/src/client.rs index b35ff2fed7..94da526d2e 100644 --- a/types/src/client.rs +++ b/types/src/client.rs @@ -1,7 +1,4 @@ -use crate::{ - error::Error, - v2::dummy::{JsonRpcMethod, JsonRpcMethodOwned, JsonRpcParams, SubscriptionId}, -}; +use crate::{error::Error, v2::dummy::SubscriptionId}; use core::marker::PhantomData; use futures::channel::{mpsc, oneshot}; use futures::prelude::*; @@ -22,20 +19,11 @@ pub struct Subscription { pub marker: PhantomData, } -/// Notification message. -#[derive(Debug)] -pub struct NotificationMessage { - /// Method for the notification. - pub method: JsonRpcMethodOwned, - /// Parameters to send to the server. - pub params: JsonRpcParamsOwned, -} - /// Batch request message. #[derive(Debug)] pub struct BatchMessage { /// Requests in the batch - pub requests: Vec<(JsonRpcMethodOwned, JsonRpcParamsOwned)>, + pub requests: Vec<(String, u64)>, /// One-shot channel over which we send back the result of this request. pub send_back: oneshot::Sender, Error>>, } @@ -43,10 +31,10 @@ pub struct BatchMessage { /// Request message. #[derive(Debug)] pub struct RequestMessage { - /// Method for the request. - pub method: JsonRpcMethodOwned, - /// Parameters of the request. - pub params: JsonRpcParamsOwned, + /// Serialized message. + pub raw: String, + /// Request ID. + pub raw_id: u64, /// One-shot channel over which we send back the result of this request. pub send_back: Option>>, } @@ -54,12 +42,12 @@ pub struct RequestMessage { /// Subscription message. #[derive(Debug)] pub struct SubscriptionMessage { - /// Method for the subscription request. - pub subscribe_method: JsonRpcMethodOwned, - /// Parameters to send for the subscription. - pub params: JsonRpcParamsOwned, + /// Serialized message. + pub raw: String, + /// Request ID of the serialized message. + pub raw_id: u64, /// Method to use to unsubscribe later. Used if the channel unexpectedly closes. - pub unsubscribe_method: JsonRpcMethodOwned, + pub unsubscribe_method: String, /// If the subscription succeeds, we return a [`mpsc::Receiver`] that will receive notifications. /// When we get a response from the server about that subscription, we send the result over /// this channel. @@ -69,14 +57,16 @@ pub struct SubscriptionMessage { /// Message that the Client can send to the background task. #[derive(Debug)] pub enum FrontToBack { - /// Send a batch request to the server. - Batch(BatchMessage), + // Send a batch request to the server. + //Batch(BatchMessage), /// Send a notification to the server. - Notification(NotificationMessage), + Notification(String), /// Send a request to the server. - StartRequest(RequestMessage), + Request(RequestMessage), /// Send a subscription request to the server. Subscribe(SubscriptionMessage), + /// Retrieve request ID. + RequestId(oneshot::Sender>), /// When a subscription channel is closed, we send this message to the background /// task to mark it ready for garbage collection. // NOTE: It is not possible to cancel pending subscriptions or pending requests. diff --git a/types/src/traits.rs b/types/src/traits.rs index bd5bc4ff2d..ab62ecc167 100644 --- a/types/src/traits.rs +++ b/types/src/traits.rs @@ -1,8 +1,5 @@ use crate::error::Error; -use crate::{ - client::Subscription, - v2::dummy::{JsonRpcMethod, JsonRpcParams}, -}; +use crate::{client::Subscription, v2::dummy::JsonRpcParams}; use async_trait::async_trait; use serde::{de::DeserializeOwned, Serialize}; @@ -12,12 +9,12 @@ pub trait Client { /// Send a [notification request](https://www.jsonrpc.org/specification#notification) async fn notification<'a, T>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result<(), Error> where - T: Serialize + std::fmt::Debug + PartialEq + Send + Sync + Clone; + T: Serialize + std::fmt::Debug + PartialEq + Send + Sync; /// Send a [method call request](https://www.jsonrpc.org/specification#request_object). async fn request<'a, T, R>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result where - T: Serialize + std::fmt::Debug + PartialEq + Send + Sync + Clone, + T: Serialize + std::fmt::Debug + PartialEq + Send + Sync, R: DeserializeOwned; /// Send a [batch request](https://www.jsonrpc.org/specification#batch). @@ -28,7 +25,7 @@ pub trait Client { /// Returns `Error` if any of the requests in batch fails. async fn batch_request<'a, T, R>(&self, batch: Vec<(&'a str, JsonRpcParams<'a, T>)>) -> Result, Error> where - T: Serialize + std::fmt::Debug + PartialEq + Send + Sync + Clone, + T: Serialize + std::fmt::Debug + PartialEq + Send + Sync, R: DeserializeOwned + Default + Clone; } @@ -51,5 +48,5 @@ pub trait SubscriptionClient: Client { ) -> Result, Error> where Notif: DeserializeOwned, - T: Serialize + std::fmt::Debug + PartialEq + Send + Sync + Clone; + T: Serialize + std::fmt::Debug + PartialEq + Send + Sync; } diff --git a/types/src/v2/dummy.rs b/types/src/v2/dummy.rs index 8ca07ad879..30f58bd92b 100644 --- a/types/src/v2/dummy.rs +++ b/types/src/v2/dummy.rs @@ -1,41 +1,9 @@ //! Client side JSON-RPC types. use crate::v2::TwoPointZero; +use alloc::collections::BTreeMap; use serde::{Deserialize, Serialize}; -#[derive(Debug)] -/// JSON-RPC method call. -pub struct JsonRpcMethodOwned(pub String); - -impl JsonRpcMethodOwned { - /// Get inner. - pub fn inner(&self) -> &str { - self.0.as_str() - } -} - -/// Serializable JSON-RPC method call. -#[derive(Serialize, Debug, PartialEq)] -pub struct JsonRpcMethod<'a>(&'a str); - -impl<'a> From<&'a str> for JsonRpcMethod<'a> { - fn from(raw: &'a str) -> Self { - Self(raw) - } -} - -impl<'a> JsonRpcMethod<'a> { - /// Get inner representation of the method. - pub fn inner(&self) -> &'a str { - self.0 - } - - /// To owned. - pub fn to_owned(&self) -> JsonRpcMethodOwned { - JsonRpcMethodOwned(self.0.to_owned()) - } -} - #[derive(Serialize, Debug, PartialEq)] #[serde(untagged)] pub enum JsonRpcParams<'a, T> @@ -44,8 +12,10 @@ where { NoParams, Array(Vec<&'a T>), + Map(BTreeMap<&'a str, &'a T>), } +// FIXME: this is a little weird but nice if `None.into()` works. impl<'a, T> From> for JsonRpcParams<'a, T> where T: Serialize + std::fmt::Debug + PartialEq, @@ -55,6 +25,15 @@ where } } +impl<'a, T> From> for JsonRpcParams<'a, T> +where + T: Serialize + std::fmt::Debug + PartialEq, +{ + fn from(map: BTreeMap<&'a str, &'a T>) -> Self { + Self::Map(map) + } +} + impl<'a, T> From> for JsonRpcParams<'a, T> where T: Serialize + std::fmt::Debug + PartialEq, @@ -138,10 +117,8 @@ where /// JSON-RPC version. pub jsonrpc: TwoPointZero, /// Name of the method to be invoked. - #[serde(borrow)] pub method: &'a str, /// Parameter values of the request. - #[serde(borrow)] pub params: JsonRpcParams<'a, T>, } diff --git a/ws-client/src/client.rs b/ws-client/src/client.rs index 943f1aa34d..95e6b5e304 100644 --- a/ws-client/src/client.rs +++ b/ws-client/src/client.rs @@ -25,8 +25,7 @@ // DEALINGS IN THE SOFTWARE. use crate::manager::{RequestManager, RequestStatus}; -use crate::transport::WsTransportClientBuilder; -use crate::{jsonrpc_transport, transport::parse_url}; +use crate::transport::{parse_url, Receiver as WsReceiver, Sender as WsSender, WsTransportClientBuilder}; use async_std::sync::Mutex; use async_trait::async_trait; use futures::{ @@ -36,10 +35,12 @@ use futures::{ sink::SinkExt, }; use jsonrpsee_types::{ - client::{BatchMessage, FrontToBack, NotificationMessage, RequestMessage, Subscription, SubscriptionMessage}, + client::{BatchMessage, FrontToBack, RequestMessage, Subscription, SubscriptionMessage}, error::Error, traits::{Client, SubscriptionClient}, - v2::dummy::{JsonRpcMethod, JsonRpcParams, JsonRpcResponse, JsonRpcResponseObject, SubscriptionId}, + v2::dummy::{ + JsonRpcCall, JsonRpcNotification, JsonRpcParams, JsonRpcResponse, JsonRpcResponseObject, SubscriptionId, + }, }; use serde::{de::DeserializeOwned, Serialize}; use serde_json::Value as JsonValue; @@ -85,9 +86,6 @@ impl ErrorFromBack { #[derive(Debug)] pub struct WsClient { /// Channel to send requests to the background task. - // - // NOTE(niklasad1): allow str slices instead of allocated Strings but it's hard to do because putting a lifetime on FrontToBack - // screws everything up. to_back: mpsc::Sender, /// If the background thread terminates the error is sent to this channel. // NOTE(niklasad1): This is a Mutex to circumvent that the async fns takes immutable references. @@ -202,8 +200,8 @@ impl<'a> WsClientBuilder<'a> { async_std::task::spawn(async move { background_task( - jsonrpc_transport::Sender::new(sender), - jsonrpc_transport::Receiver::new(receiver), + sender, + receiver, from_front, err_tx, max_capacity_per_subscription, @@ -229,47 +227,48 @@ impl WsClient { *err_lock = next_state; err } + + async fn next_request_id(&self) -> Result { + let (reqid_tx, reqid_rx) = oneshot::channel(); + if self.to_back.clone().send(FrontToBack::RequestId(reqid_tx)).await.is_err() { + return Err(self.read_error_from_backend().await); + } else { + // TODO: error handling. + let req_id = reqid_rx.await.unwrap().unwrap(); + Ok(req_id) + } + } } #[async_trait] impl Client for WsClient { async fn notification<'a, T>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result<(), Error> where - T: Serialize + std::fmt::Debug + PartialEq + Send + Sync + Clone, + T: Serialize + std::fmt::Debug + PartialEq + Send + Sync, { - todo!(); - /*log::trace!("[frontend]: send notification: method={:?}, params={:?}", method, params); - match self - .to_back - .clone() - .send(FrontToBack::Notification(NotificationMessage { - method: JsonRpcMethodOwned(method.to_string()), - params: params.to_owned(), - })) - .await - { + log::trace!("[frontend]: send notification: method={:?}, params={:?}", method, params); + let notif = JsonRpcNotification::new(method, params); + let raw = serde_json::to_string(¬if).map_err(Error::ParseError)?; + match self.to_back.clone().send(FrontToBack::Notification(raw)).await { Ok(()) => Ok(()), Err(_) => Err(self.read_error_from_backend().await), - }*/ + } } async fn request<'a, T, R>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result where - T: Serialize + std::fmt::Debug + PartialEq + Send + Sync + Clone, + T: Serialize + std::fmt::Debug + PartialEq + Send + Sync, R: DeserializeOwned, { - todo!(); - /*log::trace!("[frontend]: send request: method={:?}, params={:?}", method, params); + log::trace!("[frontend]: send request: method={:?}, params={:?}", method, params); let (send_back_tx, send_back_rx) = oneshot::channel(); + let req_id = self.next_request_id().await?; + let raw = serde_json::to_string(&JsonRpcCall::new(req_id, method, params)).map_err(Error::ParseError)?; if self .to_back .clone() - .send(FrontToBack::StartRequest(RequestMessage { - method: method.to_owned(), - params: params.to_owned(), - send_back: Some(send_back_tx), - })) + .send(FrontToBack::Request(RequestMessage { raw, raw_id: req_id, send_back: Some(send_back_tx) })) .await .is_err() { @@ -292,12 +291,12 @@ impl Client for WsClient { Ok(Err(err)) => return Err(err), Err(_) => return Err(self.read_error_from_backend().await), }; - serde_json::from_value(json_value).map_err(Error::ParseError)*/ + serde_json::from_value(json_value).map_err(Error::ParseError) } async fn batch_request<'a, T, R>(&self, batch: Vec<(&'a str, JsonRpcParams<'a, T>)>) -> Result, Error> where - T: Serialize + std::fmt::Debug + PartialEq + Send + Sync + Clone, + T: Serialize + std::fmt::Debug + PartialEq + Send + Sync, R: DeserializeOwned + Default + Clone, { todo!(); @@ -341,25 +340,27 @@ impl SubscriptionClient for WsClient { ) -> Result, Error> where N: DeserializeOwned, - T: Serialize + std::fmt::Debug + PartialEq + Send + Sync + Clone, + T: Serialize + std::fmt::Debug + PartialEq + Send + Sync, { - todo!(); - /*log::trace!("[frontend]: subscribe: {:?}, unsubscribe: {:?}", subscribe_method, unsubscribe_method); - let sub_method = subscribe_method.to_owned(); - let unsub_method = unsubscribe_method.to_owned(); + log::trace!("[frontend]: subscribe: {:?}, unsubscribe: {:?}", subscribe_method, unsubscribe_method); + let unsub_method = unsubscribe_method.to_owned(); if subscribe_method == unsubscribe_method { - return Err(Error::SubscriptionNameConflict(sub_method.0)); + return Err(Error::SubscriptionNameConflict(unsub_method)); } + let req_id = self.next_request_id().await?; + let raw = + serde_json::to_string(&JsonRpcCall::new(req_id, subscribe_method, params)).map_err(Error::ParseError)?; + let (send_back_tx, send_back_rx) = oneshot::channel(); if self .to_back .clone() .send(FrontToBack::Subscribe(SubscriptionMessage { - subscribe_method: sub_method, + raw, + raw_id: req_id, unsubscribe_method: unsub_method, - params: params.to_owned(), send_back: send_back_tx, })) .await @@ -373,14 +374,14 @@ impl SubscriptionClient for WsClient { Ok(Err(err)) => return Err(err), Err(_) => return Err(self.read_error_from_backend().await), }; - Ok(Subscription { to_back: self.to_back.clone(), notifs_rx, marker: PhantomData, id })*/ + Ok(Subscription { to_back: self.to_back.clone(), notifs_rx, marker: PhantomData, id }) } } /// Function being run in the background that processes messages from the frontend. async fn background_task( - mut sender: jsonrpc_transport::Sender, - receiver: jsonrpc_transport::Receiver, + mut sender: WsSender, + receiver: WsReceiver, mut frontend: mpsc::Receiver, front_error: oneshot::Sender, max_notifs_per_subscription: usize, @@ -409,35 +410,51 @@ async fn background_task( return; } - Either::Left((Some(FrontToBack::Batch(batch)), _)) => { + Either::Left((Some(FrontToBack::RequestId(send_back)), _)) => { + let req_id = manager.next_request_id(); + let _ = send_back.send(req_id); + } + + /*Either::Left((Some(FrontToBack::Batch(batch)), _)) => { log::trace!("[backend]: client prepares to send batch request: {:?}", batch); if let Err(e) = sender.start_batch_request(batch, &mut manager).await { log::warn!("[backend]: client batch request failed: {:?}", e); } - } - + }*/ // User called `notification` on the front-end Either::Left((Some(FrontToBack::Notification(notif)), _)) => { log::trace!("[backend]: client prepares to send notification: {:?}", notif); - if let Err(e) = sender.send_notification(notif).await { + if let Err(e) = sender.send(notif).await { log::warn!("[backend]: client notif failed: {:?}", e); } } // User called `request` on the front-end - Either::Left((Some(FrontToBack::StartRequest(request)), _)) => { + Either::Left((Some(FrontToBack::Request(request)), _)) => { log::trace!("[backend]: client prepares to send request={:?}", request); - if let Err(e) = sender.start_request(request, &mut manager).await { - log::warn!("[backend]: client request failed: {:?}", e); + match sender.send(request.raw).await { + Ok(_) => manager + .insert_pending_call(request.raw_id, request.send_back) + .expect("ID unused checked above; qed"), + Err(e) => { + manager.reclaim_request_id(request.raw_id); + log::warn!("[backend]: client request failed: {:?}", e); + let _ = request.send_back.map(|s| s.send(Err(Error::TransportError(Box::new(e))))); + } } } + // User called `subscribe` on the front-end. - Either::Left((Some(FrontToBack::Subscribe(subscribe)), _)) => { - log::trace!("[backend]: client prepares to start subscription: {:?}", subscribe); - if let Err(e) = sender.start_subscription(subscribe, &mut manager).await { + Either::Left((Some(FrontToBack::Subscribe(sub)), _)) => match sender.send(sub.raw).await { + Ok(_) => manager + .insert_pending_subscription(sub.raw_id, sub.send_back, sub.unsubscribe_method) + .expect("Request ID unused checked above; qed"), + Err(e) => { log::warn!("[backend]: client subscription failed: {:?}", e); + manager.reclaim_request_id(sub.raw_id); + let _ = sub.send_back.send(Err(Error::TransportError(Box::new(e)))); } - } + }, // User dropped a subscription. Either::Left((Some(FrontToBack::SubscriptionClosed(sub_id)), _)) => { log::trace!("Closing subscription: {:?}", sub_id); @@ -583,14 +600,11 @@ fn process_response( /// Sends an unsubscribe to request to server to indicate /// that the client is not interested in the subscription anymore. -async fn stop_subscription( - sender: &mut jsonrpc_transport::Sender, - manager: &mut RequestManager, - unsub: RequestMessage, -) { - if let Err(e) = sender.start_request(unsub, manager).await { +async fn stop_subscription(sender: &mut WsSender, manager: &mut RequestManager, unsub: RequestMessage) { + if let Err(e) = sender.send(unsub.raw).await { log::error!("Send unsubscribe request failed: {:?}", e); } + manager.reclaim_request_id(unsub.raw_id); } /// Builds an unsubscription message, semantically the same as an ordinary request. @@ -600,9 +614,8 @@ fn build_unsubscribe_message( sub_id: SubscriptionId, ) -> Option { let (_, unsub, sub_id) = manager.remove_subscription(req_id, sub_id)?; - manager.reclaim_request_id(req_id); // TODO(niklasad): better type for params or maybe a macro?!. let params: JsonRpcParams<_> = vec![&sub_id].into(); - todo!(); - //Some(RequestMessage { method: unsub, params, send_back: None }) + let raw = serde_json::to_string(&JsonRpcCall::new(req_id, &unsub, params)).unwrap(); + Some(RequestMessage { raw, raw_id: req_id, send_back: None }) } diff --git a/ws-client/src/jsonrpc_transport.rs b/ws-client/src/jsonrpc_transport.rs deleted file mode 100644 index 9f107e1157..0000000000 --- a/ws-client/src/jsonrpc_transport.rs +++ /dev/null @@ -1,160 +0,0 @@ -//! JSONRPC WebSocket Transport module. -//! -//! Wraps the underlying WebSocket transport with specific JSONRPC details. - -use crate::{ - manager::RequestManager, - transport::{self, WsConnectError}, -}; -use jsonrpsee_types::{ - client::{BatchMessage, NotificationMessage, RequestMessage, SubscriptionMessage}, - error::Error, - v2::dummy::{JsonRpcCall, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse}, -}; -use serde::de::DeserializeOwned; - -/// JSONRPC WebSocket sender. -#[derive(Debug)] -pub struct Sender { - transport: transport::Sender, -} - -impl Sender { - /// Creates a new JSONRPC sender. - pub fn new(transport: transport::Sender) -> Self { - Self { transport } - } - - /// Send a batch request. - pub async fn start_batch_request( - &mut self, - batch: BatchMessage, - request_manager: &mut RequestManager, - ) -> Result<(), Error> { - todo!(); - /*let req_id = request_manager.next_request_id()?; - let mut calls = Vec::with_capacity(batch.requests.len()); - let mut ids = Vec::with_capacity(batch.requests.len()); - - for (method, params) in batch.requests { - let batch_id = request_manager.next_batch_id(); - ids.push(batch_id); - calls.push(jsonrpc::Call::MethodCall(jsonrpc::MethodCall { - jsonrpc: jsonrpc::Version::V2, - method, - params, - id: jsonrpc::Id::Num(batch_id), - })); - } - - if let Err(send_back) = request_manager.insert_pending_batch(ids, batch.send_back, req_id) { - request_manager.reclaim_request_id(req_id); - let _ = send_back.send(Err(Error::InvalidRequestId)); - return Err(Error::InvalidRequestId); - }; - - let res = - self.transport.send_request(Request::Batch(calls)).await.map_err(|e| Error::TransportError(Box::new(e))); - - match res { - Ok(_) => Ok(()), - Err(e) => { - request_manager.reclaim_request_id(req_id); - Err(e) - } - }*/ - } - - /// Sends a request to the server but it doesn’t wait for a response. - /// Instead, you have keep the request ID and use the Receiver to get the response. - /// - /// Returns Ok() if the request was successfully sent otherwise Err(_). - pub async fn start_request( - &mut self, - request: RequestMessage, - request_manager: &mut RequestManager, - ) -> Result<(), Error> { - todo!(); - /*let id = match request_manager.next_request_id() { - Ok(id) => id, - Err(err) => { - let str_err = err.to_string(); - request.send_back.map(|tx| tx.send(Err(err))); - return Err(Error::Custom(str_err)); - } - }; - let req = JsonRpcCall::new(id, request.method.inner(), request.params.inner()); - match self.transport.send_request(req).await { - Ok(_) => { - request_manager.insert_pending_call(id, request.send_back).expect("ID unused checked above; qed"); - Ok(()) - } - Err(e) => { - let str_err = e.to_string(); - let _ = request.send_back.map(|tx| tx.send(Err(Error::TransportError(Box::new(e))))); - Err(Error::Custom(str_err)) - } - }*/ - } - - /// Sends a notification to the server. The notification doesn't need any response. - /// - /// Returns `Ok(())` if the notification was successfully sent otherwise `Err(_)`. - pub async fn send_notification<'a>(&mut self, notif: NotificationMessage) -> Result<(), Error> { - todo!(); - //let notif = JsonRpcNotification::new(notif.method.inner(), notif.params.inner()); - //self.transport.send_request(notif).await.map_err(|e| Error::TransportError(Box::new(e))) - } - - /// Sends a request to the server to start a new subscription but it doesn't wait for a response. - /// Instead, you have keep the request ID and use the [`Receiver`] to get the response. - /// - /// Returns `Ok()` if the request was successfully sent otherwise `Err(_)`. - pub async fn start_subscription( - &mut self, - sub: SubscriptionMessage, - request_manager: &mut RequestManager, - ) -> Result<(), Error> { - todo!(); - /* let id = match request_manager.next_request_id() { - Ok(id) => id, - Err(err) => { - let str_err = err.to_string(); - let _ = sub.send_back.send(Err(err)); - return Err(Error::Custom(str_err)); - } - }; - let req = JsonRpcCall::new(id, sub.subscribe_method.inner(), sub.params.inner()); - - if let Err(e) = self.transport.send_request(req).await { - let str_err = e.to_string(); - let _ = sub.send_back.send(Err(Error::TransportError(Box::new(e)))); - return Err(Error::Custom(str_err)); - } - request_manager - .insert_pending_subscription(id, sub.send_back, sub.unsubscribe_method) - .expect("Request ID unused checked above; qed");*/ - Ok(()) - } -} - -/// JSONRPC WebSocket receiver. -#[derive(Debug)] -pub struct Receiver { - transport: transport::Receiver, -} - -impl Receiver { - /// Create a new JSONRPC WebSocket receiver. - pub fn new(transport: transport::Receiver) -> Self { - Self { transport } - } - - /// Reads the next response, fails if the response ID was not a number. - pub async fn next_response(&mut self) -> Result, WsConnectError> - where - T: DeserializeOwned + std::fmt::Debug, - { - self.transport.next_response().await - } -} diff --git a/ws-client/src/lib.rs b/ws-client/src/lib.rs index 03febab4b1..04b5045f1a 100644 --- a/ws-client/src/lib.rs +++ b/ws-client/src/lib.rs @@ -6,8 +6,6 @@ /// WebSocket Client. pub mod client; -/// JSONRPC WebSocket transport. -pub mod jsonrpc_transport; /// Request manager. pub mod manager; /// Stream. diff --git a/ws-client/src/manager.rs b/ws-client/src/manager.rs index b33fd81946..772a33b543 100644 --- a/ws-client/src/manager.rs +++ b/ws-client/src/manager.rs @@ -8,10 +8,7 @@ use fnv::FnvHashMap; use futures::channel::{mpsc, oneshot}; -use jsonrpsee_types::{ - error::Error, - v2::dummy::{JsonRpcMethodOwned, SubscriptionId}, -}; +use jsonrpsee_types::{error::Error, v2::dummy::SubscriptionId}; use serde_json::Value as JsonValue; use std::collections::{ hash_map::{Entry, HashMap}, @@ -42,7 +39,7 @@ type PendingCallOneshot = Option>>; type PendingBatchOneshot = oneshot::Sender, Error>>; type PendingSubscriptionOneshot = oneshot::Sender, SubscriptionId), Error>>; type SubscriptionSink = mpsc::Sender; -type UnsubscribeMethod = JsonRpcMethodOwned; +type UnsubscribeMethod = String; /// Unique ID that are generated by the RequestManager. // TODO: new type for this https://github.com/paritytech/jsonrpsee/issues/249 type RequestId = u64; @@ -287,7 +284,8 @@ impl RequestManager { mod tests { use super::{Error, RequestManager}; use futures::channel::{mpsc, oneshot}; - use jsonrpsee_types::jsonrpc::{JsonValue, SubscriptionId}; + use jsonrpsee_types::v2::dummy::SubscriptionId; + use serde_json::Value as JsonValue; const TEST_LIMIT: usize = 10; #[test] diff --git a/ws-client/src/transport.rs b/ws-client/src/transport.rs index fad2ccf83c..9959733e03 100644 --- a/ws-client/src/transport.rs +++ b/ws-client/src/transport.rs @@ -159,11 +159,7 @@ pub enum WsConnectError { impl Sender { /// Sends out a request. Returns a `Future` that finishes when the request has been /// successfully sent. - pub async fn send_request<'a, T>(&mut self, request: impl Into>) -> Result<(), WsConnectError> - where - T: Serialize + std::fmt::Debug + PartialEq + 'a, - { - let body = serde_json::to_string(&request.into()).map_err(WsConnectError::Serialization)?; + pub async fn send(&mut self, body: String) -> Result<(), WsConnectError> { log::debug!("send: {}", body); self.inner.send_text(body).await?; self.inner.flush().await?; From 6c57ac634b68b57123f0b76cb3e43061d65e0304 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 13 Apr 2021 14:59:30 +0200 Subject: [PATCH 04/41] remove `PartialEq` bounds --- Cargo.toml | 2 +- benches/benches/bench.rs | 8 +++--- examples/examples/http.rs | 5 +--- http-client/src/client.rs | 6 ++--- http-client/src/lib.rs | 5 ++-- http-client/src/transport.rs | 26 +++++++++---------- http-server/src/tests.rs | 2 +- types/src/client.rs | 2 -- types/src/traits.rs | 8 +++--- types/src/v2/dummy.rs | 44 +++++++++++++++++++-------------- utils/src/http/hyper_helpers.rs | 4 +-- ws-client/src/client.rs | 8 +++--- ws-client/src/lib.rs | 5 ++-- ws-client/src/transport.rs | 6 +++-- ws-server/src/tests.rs | 4 +-- 15 files changed, 68 insertions(+), 67 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a8d8977794..4a6670bdc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ members = [ "http-client", "http-server", "test-utils", - "tests", + # "tests", "types", "utils", "ws-client", diff --git a/benches/benches/bench.rs b/benches/benches/bench.rs index 14bf840b73..d62af3c9b8 100644 --- a/benches/benches/bench.rs +++ b/benches/benches/bench.rs @@ -1,6 +1,6 @@ use criterion::*; use jsonrpsee_http_client::HttpClientBuilder; -use jsonrpsee_types::{jsonrpc::Params, traits::Client}; +use jsonrpsee_types::{traits::Client, v2::dummy::JsonRpcParams}; use jsonrpsee_ws_client::WsClientBuilder; use std::sync::Arc; use tokio::runtime::Runtime as TokioRuntime; @@ -30,7 +30,7 @@ fn run_round_trip(rt: &TokioRuntime, crit: &mut Criterion, client: Arc("say_hello".into(), None.into()).await.unwrap()); + black_box(client.request::("say_hello", JsonRpcParams::NoParams).await.unwrap()); }) }) }); @@ -50,7 +50,9 @@ fn run_concurrent_round_trip( for _ in 0..num_concurrent_tasks { let client_rc = client.clone(); let task = rt.spawn(async move { - let _ = black_box(client_rc.request::("say_hello".into(), None.into())).await; + let _ = black_box( + client_rc.request::("say_hello", JsonRpcParams::NoParams).await.unwrap(), + ); }); tasks.push(task); } diff --git a/examples/examples/http.rs b/examples/examples/http.rs index 2955886877..1421ff1620 100644 --- a/examples/examples/http.rs +++ b/examples/examples/http.rs @@ -28,10 +28,7 @@ use std::net::SocketAddr; use jsonrpsee_http_client::HttpClientBuilder; use jsonrpsee_http_server::HttpServerBuilder; -use jsonrpsee_types::{ - traits::Client, - v2::{dummy::JsonRpcParams, RawValue}, -}; +use jsonrpsee_types::{traits::Client, v2::dummy::JsonRpcParams}; #[tokio::main] async fn main() -> Result<(), Box> { diff --git a/http-client/src/client.rs b/http-client/src/client.rs index 3faaabc118..a5fcfa6f60 100644 --- a/http-client/src/client.rs +++ b/http-client/src/client.rs @@ -53,7 +53,7 @@ pub struct HttpClient { impl Client for HttpClient { async fn notification<'a, T>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result<(), Error> where - T: Serialize + std::fmt::Debug + PartialEq + Send + Sync, + T: Serialize + std::fmt::Debug + Send + Sync, { let notif = JsonRpcNotification::new(method, params); self.transport.send_notification(notif).await.map_err(|e| Error::TransportError(Box::new(e))) @@ -62,7 +62,7 @@ impl Client for HttpClient { /// Perform a request towards the server. async fn request<'a, T, R>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result where - T: Serialize + std::fmt::Debug + PartialEq + Send + Sync, + T: Serialize + std::fmt::Debug + Send + Sync, R: DeserializeOwned, { // NOTE: `fetch_add` wraps on overflow which is intended. @@ -85,7 +85,7 @@ impl Client for HttpClient { async fn batch_request<'a, T, R>(&self, batch: Vec<(&'a str, JsonRpcParams<'a, T>)>) -> Result, Error> where - T: Serialize + std::fmt::Debug + PartialEq + Send + Sync, + T: Serialize + std::fmt::Debug + Send + Sync, R: DeserializeOwned + Default + Clone, { let mut batch_request = Vec::with_capacity(batch.len()); diff --git a/http-client/src/lib.rs b/http-client/src/lib.rs index 13b0d1c998..927f169c49 100644 --- a/http-client/src/lib.rs +++ b/http-client/src/lib.rs @@ -39,8 +39,7 @@ extern crate hyper13_rustls as hyper_rustls; mod client; mod transport; -#[cfg(test)] -mod tests; - +//#[cfg(test)] +//mod tests; pub use client::{HttpClient, HttpClientBuilder}; pub use transport::HttpTransportClient; diff --git a/http-client/src/transport.rs b/http-client/src/transport.rs index 9967c1a4d2..5306dbdc74 100644 --- a/http-client/src/transport.rs +++ b/http-client/src/transport.rs @@ -13,8 +13,7 @@ use jsonrpsee_types::{ v2::dummy::{JsonRpcNotification, JsonRpcRequest, JsonRpcResponse}, }; use jsonrpsee_utils::http::hyper_helpers; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Serialize}; use thiserror::Error; const CONTENT_TYPE_JSON: &str = "application/json"; @@ -71,7 +70,7 @@ impl HttpTransportClient { /// Send notification. pub async fn send_notification<'a, T>(&self, notif: JsonRpcNotification<'a, T>) -> Result<(), Error> where - T: Serialize + std::fmt::Debug + PartialEq, + T: Serialize + std::fmt::Debug, { let body = serde_json::to_string(¬if).map_err(Error::Serialization)?; let _response = self.send(body).await?; @@ -84,7 +83,7 @@ impl HttpTransportClient { request: impl Into>, ) -> Result, Error> where - T: Serialize + std::fmt::Debug + PartialEq + 'a, + T: Serialize + std::fmt::Debug + 'a, R: DeserializeOwned, { let body = serde_json::to_string(&request.into()).map_err(Error::Serialization)?; @@ -151,7 +150,7 @@ where #[cfg(test)] mod tests { use super::{Error, HttpTransportClient}; - use jsonrpsee_types::jsonrpc::{Call, Id, MethodCall, Params, Request, Version}; + use jsonrpsee_types::v2::dummy::{JsonRpcCall, JsonRpcParams}; #[test] fn invalid_http_url_rejected() { @@ -165,15 +164,14 @@ mod tests { let client = HttpTransportClient::new("http://localhost:9933", 80).unwrap(); assert_eq!(client.max_request_body_size, eighty_bytes_limit); - let request = Request::Single(Call::MethodCall(MethodCall { - jsonrpc: Version::V2, - method: "request_larger_than_eightybytes".to_string(), - params: Params::None, - id: Id::Num(1), - })); - let bytes = serde_json::to_vec(&request).unwrap(); - assert_eq!(bytes.len(), 81); - let response = client.send_request(request).await.unwrap_err(); + let body = serde_json::to_string(&JsonRpcCall::new( + 1, + "request_larger_than_eightybytes", + JsonRpcParams::NoParams::, + )) + .unwrap(); + assert_eq!(body.len(), 81); + let response = client.send(body).await.unwrap_err(); assert!(matches!(response, Error::RequestTooLarge)); } } diff --git a/http-server/src/tests.rs b/http-server/src/tests.rs index 32f8b86ec0..95c80382cc 100644 --- a/http-server/src/tests.rs +++ b/http-server/src/tests.rs @@ -5,7 +5,7 @@ use std::net::SocketAddr; use crate::HttpServerBuilder; use jsonrpsee_test_utils::helpers::*; use jsonrpsee_test_utils::types::{Id, StatusCode}; -use jsonrpsee_types::jsonrpc::JsonValue; +use serde_json::Value as JsonValue; async fn server() -> SocketAddr { let mut server = HttpServerBuilder::default().build("127.0.0.1:0".parse().unwrap()).unwrap(); diff --git a/types/src/client.rs b/types/src/client.rs index 94da526d2e..3b65d12dd6 100644 --- a/types/src/client.rs +++ b/types/src/client.rs @@ -5,8 +5,6 @@ use futures::prelude::*; use serde::de::DeserializeOwned; use serde_json::Value as JsonValue; -type JsonRpcParamsOwned = String; - /// Active subscription on a Client. pub struct Subscription { /// Channel to send requests to the background task. diff --git a/types/src/traits.rs b/types/src/traits.rs index ab62ecc167..b662a2b931 100644 --- a/types/src/traits.rs +++ b/types/src/traits.rs @@ -9,12 +9,12 @@ pub trait Client { /// Send a [notification request](https://www.jsonrpc.org/specification#notification) async fn notification<'a, T>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result<(), Error> where - T: Serialize + std::fmt::Debug + PartialEq + Send + Sync; + T: Serialize + std::fmt::Debug + Send + Sync; /// Send a [method call request](https://www.jsonrpc.org/specification#request_object). async fn request<'a, T, R>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result where - T: Serialize + std::fmt::Debug + PartialEq + Send + Sync, + T: Serialize + std::fmt::Debug + Send + Sync, R: DeserializeOwned; /// Send a [batch request](https://www.jsonrpc.org/specification#batch). @@ -25,7 +25,7 @@ pub trait Client { /// Returns `Error` if any of the requests in batch fails. async fn batch_request<'a, T, R>(&self, batch: Vec<(&'a str, JsonRpcParams<'a, T>)>) -> Result, Error> where - T: Serialize + std::fmt::Debug + PartialEq + Send + Sync, + T: Serialize + std::fmt::Debug + Send + Sync, R: DeserializeOwned + Default + Clone; } @@ -48,5 +48,5 @@ pub trait SubscriptionClient: Client { ) -> Result, Error> where Notif: DeserializeOwned, - T: Serialize + std::fmt::Debug + PartialEq + Send + Sync; + T: Serialize + std::fmt::Debug + Send + Sync; } diff --git a/types/src/v2/dummy.rs b/types/src/v2/dummy.rs index 30f58bd92b..821dfcd4bb 100644 --- a/types/src/v2/dummy.rs +++ b/types/src/v2/dummy.rs @@ -4,21 +4,25 @@ use crate::v2::TwoPointZero; use alloc::collections::BTreeMap; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Debug, PartialEq)] +/// [JSON-RPC parameters](https://www.jsonrpc.org/specification#parameter_structures) +#[derive(Serialize, Debug)] #[serde(untagged)] pub enum JsonRpcParams<'a, T> where - T: Serialize + std::fmt::Debug + PartialEq, + T: Serialize + std::fmt::Debug, { + /// No params. NoParams, + /// Positional params. Array(Vec<&'a T>), + /// Params by name. Map(BTreeMap<&'a str, &'a T>), } // FIXME: this is a little weird but nice if `None.into()` works. impl<'a, T> From> for JsonRpcParams<'a, T> where - T: Serialize + std::fmt::Debug + PartialEq, + T: Serialize + std::fmt::Debug, { fn from(_raw: Option<&'a T>) -> Self { Self::NoParams @@ -27,7 +31,7 @@ where impl<'a, T> From> for JsonRpcParams<'a, T> where - T: Serialize + std::fmt::Debug + PartialEq, + T: Serialize + std::fmt::Debug, { fn from(map: BTreeMap<&'a str, &'a T>) -> Self { Self::Map(map) @@ -36,27 +40,31 @@ where impl<'a, T> From> for JsonRpcParams<'a, T> where - T: Serialize + std::fmt::Debug + PartialEq, + T: Serialize + std::fmt::Debug, { fn from(arr: Vec<&'a T>) -> Self { Self::Array(arr) } } +/// Serializable JSON-RPC request object which may be a notification, method call or batch. #[derive(Serialize, Debug)] #[serde(untagged)] pub enum JsonRpcRequest<'a, T> where - T: Serialize + std::fmt::Debug + PartialEq + 'a, + T: Serialize + std::fmt::Debug + 'a, { + /// Single method call. Single(JsonRpcCall<'a, T>), + /// Batch. Batch(Vec>), + /// Notification. Notif(JsonRpcNotification<'a, T>), } impl<'a, T> From> for JsonRpcRequest<'a, T> where - T: Serialize + std::fmt::Debug + PartialEq, + T: Serialize + std::fmt::Debug, { fn from(call: JsonRpcCall<'a, T>) -> Self { JsonRpcRequest::Single(call) @@ -64,7 +72,7 @@ where } impl<'a, T> From>> for JsonRpcRequest<'a, T> where - T: Serialize + std::fmt::Debug + PartialEq, + T: Serialize + std::fmt::Debug, { fn from(batch: Vec>) -> Self { JsonRpcRequest::Batch(batch) @@ -73,7 +81,7 @@ where impl<'a, T> From> for JsonRpcRequest<'a, T> where - T: Serialize + std::fmt::Debug + PartialEq, + T: Serialize + std::fmt::Debug, { fn from(notif: JsonRpcNotification<'a, T>) -> Self { JsonRpcRequest::Notif(notif) @@ -84,23 +92,21 @@ where #[derive(Serialize, Debug)] pub struct JsonRpcCall<'a, T> where - T: Serialize + std::fmt::Debug + PartialEq, + T: Serialize + std::fmt::Debug, { /// JSON-RPC version. pub jsonrpc: TwoPointZero, /// Name of the method to be invoked. - #[serde(borrow)] pub method: &'a str, /// Request ID pub id: u64, /// Parameter values of the request. - #[serde(borrow)] pub params: JsonRpcParams<'a, T>, } impl<'a, T> JsonRpcCall<'a, T> where - T: Serialize + std::fmt::Debug + PartialEq, + T: Serialize + std::fmt::Debug, { /// Create a new serializable JSON-RPC request. pub fn new(id: u64, method: &'a str, params: JsonRpcParams<'a, T>) -> Self { @@ -112,7 +118,7 @@ where #[derive(Serialize, Debug)] pub struct JsonRpcNotification<'a, T> where - T: Serialize + std::fmt::Debug + PartialEq, + T: Serialize + std::fmt::Debug, { /// JSON-RPC version. pub jsonrpc: TwoPointZero, @@ -124,7 +130,7 @@ where impl<'a, T> JsonRpcNotification<'a, T> where - T: Serialize + std::fmt::Debug + PartialEq, + T: Serialize + std::fmt::Debug, { /// Create a new serializable JSON-RPC request. pub fn new(method: &'a str, params: JsonRpcParams<'a, T>) -> Self { @@ -132,7 +138,7 @@ where } } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Deserialize, Debug)] pub struct JsonRpcResponseObject { /// JSON-RPC version. pub jsonrpc: TwoPointZero, @@ -143,7 +149,7 @@ pub struct JsonRpcResponseObject { } /// JSON-RPC parameter values for subscriptions. -#[derive(Serialize, Deserialize, Debug)] +#[derive(Deserialize, Debug)] pub struct JsonRpcNotificationParams { /// Subscription ID pub subscription: SubscriptionId, @@ -151,14 +157,14 @@ pub struct JsonRpcNotificationParams { pub result: T, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Deserialize, Debug)] pub struct JsonRpcResponseNotif { pub jsonrpc: TwoPointZero, pub params: JsonRpcNotificationParams, } /// Represent the different JSON-RPC responses. -#[derive(Serialize, Deserialize, Debug)] +#[derive(Deserialize, Debug)] #[serde(untagged)] pub enum JsonRpcResponse { /// Single response. diff --git a/utils/src/http/hyper_helpers.rs b/utils/src/http/hyper_helpers.rs index 05be83ff5b..91338fd575 100644 --- a/utils/src/http/hyper_helpers.rs +++ b/utils/src/http/hyper_helpers.rs @@ -95,13 +95,13 @@ mod tests { #[tokio::test] async fn body_to_request_works() { - let s = r#"[{"a":"hello"}]"#; + /*let s = r#"[{"a":"hello"}]"#; let expected: JsonRpcRequest = serde_json::from_str(s).unwrap(); let body = hyper::Body::from(s.to_owned()); let headers = hyper::header::HeaderMap::new(); let bytes = read_response_to_body(&headers, body, 10 * 1024 * 1024).await.unwrap(); let req: JsonRpcRequest = serde_json::from_slice(&bytes).unwrap(); - assert_eq!(req, expected); + assert_eq!(req, expected);*/ } #[tokio::test] diff --git a/ws-client/src/client.rs b/ws-client/src/client.rs index 95e6b5e304..34412a20c9 100644 --- a/ws-client/src/client.rs +++ b/ws-client/src/client.rs @@ -244,7 +244,7 @@ impl WsClient { impl Client for WsClient { async fn notification<'a, T>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result<(), Error> where - T: Serialize + std::fmt::Debug + PartialEq + Send + Sync, + T: Serialize + std::fmt::Debug + Send + Sync, { log::trace!("[frontend]: send notification: method={:?}, params={:?}", method, params); let notif = JsonRpcNotification::new(method, params); @@ -257,7 +257,7 @@ impl Client for WsClient { async fn request<'a, T, R>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result where - T: Serialize + std::fmt::Debug + PartialEq + Send + Sync, + T: Serialize + std::fmt::Debug + Send + Sync, R: DeserializeOwned, { log::trace!("[frontend]: send request: method={:?}, params={:?}", method, params); @@ -296,7 +296,7 @@ impl Client for WsClient { async fn batch_request<'a, T, R>(&self, batch: Vec<(&'a str, JsonRpcParams<'a, T>)>) -> Result, Error> where - T: Serialize + std::fmt::Debug + PartialEq + Send + Sync, + T: Serialize + std::fmt::Debug + Send + Sync, R: DeserializeOwned + Default + Clone, { todo!(); @@ -340,7 +340,7 @@ impl SubscriptionClient for WsClient { ) -> Result, Error> where N: DeserializeOwned, - T: Serialize + std::fmt::Debug + PartialEq + Send + Sync, + T: Serialize + std::fmt::Debug + Send + Sync, { log::trace!("[frontend]: subscribe: {:?}, unsubscribe: {:?}", subscribe_method, unsubscribe_method); diff --git a/ws-client/src/lib.rs b/ws-client/src/lib.rs index 04b5045f1a..3186a11c25 100644 --- a/ws-client/src/lib.rs +++ b/ws-client/src/lib.rs @@ -13,8 +13,7 @@ pub mod stream; /// WebSocket transport. pub mod transport; -#[cfg(test)] -mod tests; - +//#[cfg(test)] +//mod tests; pub use client::{WsClient, WsClientBuilder}; pub use jsonrpsee_types::client::Subscription as WsSubscription; diff --git a/ws-client/src/transport.rs b/ws-client/src/transport.rs index 9959733e03..45607665dc 100644 --- a/ws-client/src/transport.rs +++ b/ws-client/src/transport.rs @@ -28,8 +28,8 @@ use async_std::net::TcpStream; use async_tls::client::TlsStream; use futures::io::{BufReader, BufWriter}; use futures::prelude::*; -use jsonrpsee_types::v2::dummy::{JsonRpcNotification, JsonRpcRequest, JsonRpcResponse}; -use serde::{de::DeserializeOwned, Serialize}; +use jsonrpsee_types::v2::dummy::JsonRpcResponse; +use serde::de::DeserializeOwned; use soketto::connection; use soketto::handshake::client::{Client as WsRawClient, ServerResponse}; use std::{borrow::Cow, io, net::SocketAddr, time::Duration}; @@ -169,6 +169,8 @@ impl Sender { impl Receiver { /// Returns a `Future` resolving when the server sent us something back. + // + // TODO(niklasad1): return Vec instead to have a clean abstraction. pub async fn next_response(&mut self) -> Result, WsConnectError> where T: DeserializeOwned + std::fmt::Debug, diff --git a/ws-server/src/tests.rs b/ws-server/src/tests.rs index b93c704331..82d37a3141 100644 --- a/ws-server/src/tests.rs +++ b/ws-server/src/tests.rs @@ -4,7 +4,8 @@ use crate::WsServer; use futures::channel::oneshot::{self, Sender}; use jsonrpsee_test_utils::helpers::*; use jsonrpsee_test_utils::types::{Id, WebSocketTestClient}; -use jsonrpsee_types::{error::Error, jsonrpc::JsonValue}; +use jsonrpsee_types::error::Error; +use serde_json::Value as JsonValue; use std::net::SocketAddr; /// Spawns a dummy `JSONRPC v2 WebSocket` @@ -22,7 +23,6 @@ pub async fn server(server_started: Sender) { .register_method("add", |params| { let params: Vec = params.parse()?; let sum: u64 = params.into_iter().sum(); - Ok(sum) }) .unwrap(); From 738819191bed379da085edd511ecd0fdaaa5bbd7 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 13 Apr 2021 16:41:27 +0200 Subject: [PATCH 05/41] add naive benches types --- benches/Cargo.toml | 1 + benches/benches/bench.rs | 40 ++++++++++++++++++++++++++++++++++++++-- types/src/lib.rs | 2 +- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/benches/Cargo.toml b/benches/Cargo.toml index f216532e93..775bf95326 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -17,6 +17,7 @@ jsonrpsee-http-client = { path = "../http-client" } jsonrpsee-ws-client = { path = "../ws-client" } jsonrpsee-ws-server = { path = "../ws-server" } jsonrpsee-http-server = { path = "../http-server" } +serde_json = "1" num_cpus = "1" tokio = { version = "1", features = ["full"] } diff --git a/benches/benches/bench.rs b/benches/benches/bench.rs index d62af3c9b8..3d2360886a 100644 --- a/benches/benches/bench.rs +++ b/benches/benches/bench.rs @@ -1,15 +1,51 @@ use criterion::*; use jsonrpsee_http_client::HttpClientBuilder; -use jsonrpsee_types::{traits::Client, v2::dummy::JsonRpcParams}; +use jsonrpsee_types::{ + jsonrpc::{self, Params, Request}, + traits::Client, + v2::dummy::{JsonRpcCall, JsonRpcParams, JsonRpcRequest}, +}; use jsonrpsee_ws_client::WsClientBuilder; use std::sync::Arc; use tokio::runtime::Runtime as TokioRuntime; mod helpers; -criterion_group!(benches, http_requests, websocket_requests); +criterion_group!(benches, /*http_requests, websocket_requests,*/ jsonrpsee_types_v1, jsonrpsee_types_v2); criterion_main!(benches); +fn v1_serialize(req: Request) -> String { + serde_json::to_string(&req).unwrap() +} + +fn v2_serialize(req: JsonRpcRequest) -> String { + serde_json::to_string(&req).unwrap() +} + +pub fn jsonrpsee_types_v1(crit: &mut Criterion) { + crit.bench_function("jsonrpsee_types_v1", |b| { + b.iter(|| { + let request = jsonrpc::Request::Single(jsonrpc::Call::MethodCall(jsonrpc::MethodCall { + jsonrpc: jsonrpc::Version::V2, + method: "say_hello".to_string(), + params: Params::Array(vec![1_u64.into(), 2_u64.into()]), + id: jsonrpc::Id::Num(0), + })); + v1_serialize(request); + }) + }); +} + +pub fn jsonrpsee_types_v2(crit: &mut Criterion) { + crit.bench_function("jsonrpsee_types_v2", |b| { + b.iter(|| { + let params: JsonRpcParams<_> = vec![&1, &2].into(); + let request = JsonRpcRequest::Single(JsonRpcCall::new(0, "say_hello", params)); + v2_serialize(request); + }) + }); +} + pub fn http_requests(crit: &mut Criterion) { let rt = TokioRuntime::new().unwrap(); let url = rt.block_on(helpers::http_server()); diff --git a/types/src/lib.rs b/types/src/lib.rs index 1eb93567cf..d770dd070b 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -6,7 +6,7 @@ extern crate alloc; /// JSON-RPC 2.0 specification related types. -//pub mod jsonrpc; +pub mod jsonrpc; /// JSON-RPC 2.0 specification related types v2. pub mod v2; From 6765fb68a90ebf56179a20d9aa0fc469a265c554 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 13 Apr 2021 17:36:37 +0200 Subject: [PATCH 06/41] misc --- types/src/client.rs | 12 ++++++++---- types/src/v2/dummy.rs | 1 + ws-client/src/client.rs | 38 +++++++++++++++++++++++++++++--------- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/types/src/client.rs b/types/src/client.rs index 3b65d12dd6..0a6b8f8bfd 100644 --- a/types/src/client.rs +++ b/types/src/client.rs @@ -20,8 +20,10 @@ pub struct Subscription { /// Batch request message. #[derive(Debug)] pub struct BatchMessage { - /// Requests in the batch - pub requests: Vec<(String, u64)>, + /// Serialized batch request. + pub raw: String, + /// Request IDs. + pub raw_ids: Vec, /// One-shot channel over which we send back the result of this request. pub send_back: oneshot::Sender, Error>>, } @@ -55,8 +57,8 @@ pub struct SubscriptionMessage { /// Message that the Client can send to the background task. #[derive(Debug)] pub enum FrontToBack { - // Send a batch request to the server. - //Batch(BatchMessage), + /// Send a batch request to the server. + Batch(BatchMessage), /// Send a notification to the server. Notification(String), /// Send a request to the server. @@ -65,6 +67,8 @@ pub enum FrontToBack { Subscribe(SubscriptionMessage), /// Retrieve request ID. RequestId(oneshot::Sender>), + /// Fetch batch IDs for a batch request, + BatchIds(u64, oneshot::Sender, u64), Error>>), /// When a subscription channel is closed, we send this message to the background /// task to mark it ready for garbage collection. // NOTE: It is not possible to cancel pending subscriptions or pending requests. diff --git a/types/src/v2/dummy.rs b/types/src/v2/dummy.rs index 821dfcd4bb..d2ae194359 100644 --- a/types/src/v2/dummy.rs +++ b/types/src/v2/dummy.rs @@ -14,6 +14,7 @@ where /// No params. NoParams, /// Positional params. + // TODO(niklasad1): maybe smallvec here?! Array(Vec<&'a T>), /// Params by name. Map(BTreeMap<&'a str, &'a T>), diff --git a/ws-client/src/client.rs b/ws-client/src/client.rs index 34412a20c9..e48e995327 100644 --- a/ws-client/src/client.rs +++ b/ws-client/src/client.rs @@ -300,14 +300,19 @@ impl Client for WsClient { R: DeserializeOwned + Default + Clone, { todo!(); - /* + /*let mut batches = Vec::with_capacity(batch.len()); + let mut ids = Vec::with_capacity(batch.len()); + + let req_id = self.next_request_id().await?; + let (send_back_tx, send_back_rx) = oneshot::channel(); - let requests: Vec<(String, jsonrpc::Params)> = batch.into_iter().map(|(r, p)| (r.into(), p.into())).collect(); - log::trace!("[frontend]: send batch request: {:?}", requests); + + let raw = serde_json::to_string(&batches).map_err(Error::ParseError)?; + log::trace!("[frontend]: send batch request: {:?}", raw); if self .to_back .clone() - .send(FrontToBack::Batch(BatchMessage { requests, send_back: send_back_tx })) + .send(FrontToBack::Batch(BatchMessage { raw, raw_ids: ids, send_back: send_back_tx })) .await .is_err() { @@ -321,7 +326,7 @@ impl Client for WsClient { }; let values: Result<_, _> = - json_values.into_iter().map(|val| jsonrpc::from_value(val).map_err(Error::ParseError)).collect(); + json_values.into_iter().map(|val| serde_json::from_value(val).map_err(Error::ParseError)).collect(); Ok(values?)*/ } } @@ -415,12 +420,27 @@ async fn background_task( let _ = send_back.send(req_id); } - /*Either::Left((Some(FrontToBack::Batch(batch)), _)) => { - log::trace!("[backend]: client prepares to send batch request: {:?}", batch); - if let Err(e) = sender.start_batch_request(batch, &mut manager).await { + Either::Left((Some(FrontToBack::BatchIds(num_reqs, send_back)), _)) => { + let req_id = match manager.next_request_id() { + Ok(id) => id, + Err(e) => { + let _ = send_back.send(Err(e)); + continue; + } + }; + let mut batch_ids = Vec::with_capacity(num_reqs as usize); + for _ in 0..num_reqs { + batch_ids.push(manager.next_batch_id()); + } + let _ = send_back.send(Ok((batch_ids, req_id))); + } + + Either::Left((Some(FrontToBack::Batch(batch)), _)) => { + log::trace!("[backend]: client prepares to send batch request: {:?}", batch.raw); + if let Err(e) = sender.send(batch.raw).await { log::warn!("[backend]: client batch request failed: {:?}", e); } - }*/ + } // User called `notification` on the front-end Either::Left((Some(FrontToBack::Notification(notif)), _)) => { log::trace!("[backend]: client prepares to send notification: {:?}", notif); From 3314df5157356701533e2b361648cfe0446b5a9f Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 13 Apr 2021 17:42:54 +0200 Subject: [PATCH 07/41] remove useless lifetime --- http-client/src/transport.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http-client/src/transport.rs b/http-client/src/transport.rs index 882f6d626f..fd7e7ad66d 100644 --- a/http-client/src/transport.rs +++ b/http-client/src/transport.rs @@ -46,7 +46,7 @@ impl HttpTransportClient { } /// Send request. - async fn send<'a>(&self, body: String) -> Result, Error> { + async fn send(&self, body: String) -> Result, Error> { log::debug!("send: {}", body); if body.len() > self.max_request_body_size as usize { From 7c8885b0e6db11c42b90490a58d826e2e7d2ad37 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 14 Apr 2021 14:32:48 +0200 Subject: [PATCH 08/41] [ws client]: move request ID generation to client --- benches/benches/bench.rs | 5 +- types/src/client.rs | 4 - ws-client/src/client.rs | 158 +++++++++++++++++++++++---------------- ws-client/src/manager.rs | 58 ++------------ 4 files changed, 103 insertions(+), 122 deletions(-) diff --git a/benches/benches/bench.rs b/benches/benches/bench.rs index 3d2360886a..dad69c693a 100644 --- a/benches/benches/bench.rs +++ b/benches/benches/bench.rs @@ -11,7 +11,7 @@ use tokio::runtime::Runtime as TokioRuntime; mod helpers; -criterion_group!(benches, /*http_requests, websocket_requests,*/ jsonrpsee_types_v1, jsonrpsee_types_v2); +criterion_group!(benches, /*http_requests,*/ websocket_requests /*, jsonrpsee_types_v1, jsonrpsee_types_v2*/); criterion_main!(benches); fn v1_serialize(req: Request) -> String { @@ -57,7 +57,8 @@ pub fn http_requests(crit: &mut Criterion) { pub fn websocket_requests(crit: &mut Criterion) { let rt = TokioRuntime::new().unwrap(); let url = rt.block_on(helpers::ws_server()); - let client = Arc::new(rt.block_on(WsClientBuilder::default().build(&url)).unwrap()); + let client = + Arc::new(rt.block_on(WsClientBuilder::default().max_concurrent_requests(1024 * 1024).build(&url)).unwrap()); run_round_trip(&rt, crit, client.clone(), "ws_round_trip"); run_concurrent_round_trip(&rt, crit, client.clone(), "ws_concurrent_round_trip"); } diff --git a/types/src/client.rs b/types/src/client.rs index 0a6b8f8bfd..be7b6af156 100644 --- a/types/src/client.rs +++ b/types/src/client.rs @@ -65,10 +65,6 @@ pub enum FrontToBack { Request(RequestMessage), /// Send a subscription request to the server. Subscribe(SubscriptionMessage), - /// Retrieve request ID. - RequestId(oneshot::Sender>), - /// Fetch batch IDs for a batch request, - BatchIds(u64, oneshot::Sender, u64), Error>>), /// When a subscription channel is closed, we send this message to the background /// task to mark it ready for garbage collection. // NOTE: It is not possible to cancel pending subscriptions or pending requests. diff --git a/ws-client/src/client.rs b/ws-client/src/client.rs index e48e995327..a19c50aa56 100644 --- a/ws-client/src/client.rs +++ b/ws-client/src/client.rs @@ -44,9 +44,12 @@ use jsonrpsee_types::{ }; use serde::{de::DeserializeOwned, Serialize}; use serde_json::Value as JsonValue; -use std::borrow::Cow; -use std::marker::PhantomData; -use std::time::Duration; +use std::{ + borrow::Cow, + marker::PhantomData, + sync::atomic::{AtomicU64, AtomicUsize, Ordering}, + time::Duration, +}; /// Wrapper over a [`oneshot::Receiver`](futures::channel::oneshot::Receiver) that reads /// the underlying channel once and then stores the result in String. @@ -92,6 +95,62 @@ pub struct WsClient { error: Mutex, /// Request timeout request_timeout: Option, + /// Request ID manager. + id_guard: RequestIdGuard, +} + +#[derive(Debug)] +struct RequestIdGuard { + // Current pending requests. + current_pending: AtomicUsize, + /// Max concurrent pending requests allowed. + max_concurrent_requests: usize, + /// Get the next request ID. + current_id: AtomicU64, +} + +impl RequestIdGuard { + fn new(limit: usize) -> Self { + Self { current_pending: AtomicUsize::new(0), max_concurrent_requests: limit, current_id: AtomicU64::new(0) } + } + + fn get_slot(&self) -> Result<(), Error> { + if self.current_pending.load(Ordering::Relaxed) >= self.max_concurrent_requests { + Err(Error::MaxSlotsExceeded) + } else { + // NOTE: `fetch_add` wraps on overflow but can't occur because `current_pending` is checked above. + self.current_pending.fetch_add(1, Ordering::Relaxed); + Ok(()) + } + } + + /// Attempts to get the next request ID. + /// + /// Fails if request limit has been exceeded. + fn next_request_id(&self) -> Result { + self.get_slot()?; + let id = self.current_id.fetch_add(1, Ordering::Relaxed); + Ok(id) + } + + /// Attempts to get the next batch IDs that only counts as one request.. + /// + /// Fails if request limit has been exceeded. + fn next_batch_ids(&self, len: usize) -> Result, Error> { + self.get_slot()?; + let mut batch = Vec::with_capacity(len); + for _ in 0..len { + batch.push(self.current_id.fetch_add(1, Ordering::Relaxed)); + } + Ok(batch) + } + + fn reclaim_request_id(&self) { + let curr = self.current_pending.load(Ordering::Relaxed); + if curr > 0 { + self.current_pending.store(curr - 1, Ordering::Relaxed); + } + } } /// Configuration. @@ -199,17 +258,14 @@ impl<'a> WsClientBuilder<'a> { let (sender, receiver) = builder.build().await.map_err(|e| Error::TransportError(Box::new(e)))?; async_std::task::spawn(async move { - background_task( - sender, - receiver, - from_front, - err_tx, - max_capacity_per_subscription, - max_concurrent_requests, - ) - .await; + background_task(sender, receiver, from_front, err_tx, max_capacity_per_subscription).await; }); - Ok(WsClient { to_back, request_timeout, error: Mutex::new(ErrorFromBack::Unread(err_rx)) }) + Ok(WsClient { + to_back, + request_timeout, + error: Mutex::new(ErrorFromBack::Unread(err_rx)), + id_guard: RequestIdGuard::new(max_concurrent_requests), + }) } } @@ -227,17 +283,6 @@ impl WsClient { *err_lock = next_state; err } - - async fn next_request_id(&self) -> Result { - let (reqid_tx, reqid_rx) = oneshot::channel(); - if self.to_back.clone().send(FrontToBack::RequestId(reqid_tx)).await.is_err() { - return Err(self.read_error_from_backend().await); - } else { - // TODO: error handling. - let req_id = reqid_rx.await.unwrap().unwrap(); - Ok(req_id) - } - } } #[async_trait] @@ -262,7 +307,7 @@ impl Client for WsClient { { log::trace!("[frontend]: send request: method={:?}, params={:?}", method, params); let (send_back_tx, send_back_rx) = oneshot::channel(); - let req_id = self.next_request_id().await?; + let req_id = self.id_guard.next_request_id()?; let raw = serde_json::to_string(&JsonRpcCall::new(req_id, method, params)).map_err(Error::ParseError)?; if self @@ -272,6 +317,7 @@ impl Client for WsClient { .await .is_err() { + self.id_guard.reclaim_request_id(); return Err(self.read_error_from_backend().await); } @@ -280,12 +326,13 @@ impl Client for WsClient { futures::pin_mut!(send_back_rx, timeout); match future::select(send_back_rx, timeout).await { future::Either::Left((send_back_rx_out, _)) => send_back_rx_out, - future::Either::Right((_, _)) => return Err(Error::WsRequestTimeout), + future::Either::Right((_, _)) => Ok(Err(Error::WsRequestTimeout)), } } else { send_back_rx.await }; + self.id_guard.reclaim_request_id(); let json_value = match send_back_rx_out { Ok(Ok(v)) => v, Ok(Err(err)) => return Err(err), @@ -299,11 +346,12 @@ impl Client for WsClient { T: Serialize + std::fmt::Debug + Send + Sync, R: DeserializeOwned + Default + Clone, { - todo!(); - /*let mut batches = Vec::with_capacity(batch.len()); - let mut ids = Vec::with_capacity(batch.len()); + let batch_ids = self.id_guard.next_batch_ids(batch.len())?; + let mut batches = Vec::with_capacity(batch.len()); - let req_id = self.next_request_id().await?; + for (idx, (method, params)) in batch.into_iter().enumerate() { + batches.push(JsonRpcCall::new(batch_ids[idx], method, params)); + } let (send_back_tx, send_back_rx) = oneshot::channel(); @@ -312,14 +360,17 @@ impl Client for WsClient { if self .to_back .clone() - .send(FrontToBack::Batch(BatchMessage { raw, raw_ids: ids, send_back: send_back_tx })) + .send(FrontToBack::Batch(BatchMessage { raw, raw_ids: batch_ids, send_back: send_back_tx })) .await .is_err() { + self.id_guard.reclaim_request_id(); return Err(self.read_error_from_backend().await); } - let json_values = match send_back_rx.await { + let res = send_back_rx.await; + self.id_guard.reclaim_request_id(); + let json_values = match res { Ok(Ok(v)) => v, Ok(Err(err)) => return Err(err), Err(_) => return Err(self.read_error_from_backend().await), @@ -327,7 +378,7 @@ impl Client for WsClient { let values: Result<_, _> = json_values.into_iter().map(|val| serde_json::from_value(val).map_err(Error::ParseError)).collect(); - Ok(values?)*/ + Ok(values?) } } @@ -354,7 +405,7 @@ impl SubscriptionClient for WsClient { return Err(Error::SubscriptionNameConflict(unsub_method)); } - let req_id = self.next_request_id().await?; + let req_id = self.id_guard.next_request_id()?; let raw = serde_json::to_string(&JsonRpcCall::new(req_id, subscribe_method, params)).map_err(Error::ParseError)?; @@ -390,9 +441,8 @@ async fn background_task( mut frontend: mpsc::Receiver, front_error: oneshot::Sender, max_notifs_per_subscription: usize, - max_concurrent_requests: usize, ) { - let mut manager = RequestManager::new(max_concurrent_requests); + let mut manager = RequestManager::new(); let backend_event = futures::stream::unfold(receiver, |mut receiver| async { // TODO: fix JsonValue here. @@ -415,26 +465,6 @@ async fn background_task( return; } - Either::Left((Some(FrontToBack::RequestId(send_back)), _)) => { - let req_id = manager.next_request_id(); - let _ = send_back.send(req_id); - } - - Either::Left((Some(FrontToBack::BatchIds(num_reqs, send_back)), _)) => { - let req_id = match manager.next_request_id() { - Ok(id) => id, - Err(e) => { - let _ = send_back.send(Err(e)); - continue; - } - }; - let mut batch_ids = Vec::with_capacity(num_reqs as usize); - for _ in 0..num_reqs { - batch_ids.push(manager.next_batch_id()); - } - let _ = send_back.send(Ok((batch_ids, req_id))); - } - Either::Left((Some(FrontToBack::Batch(batch)), _)) => { log::trace!("[backend]: client prepares to send batch request: {:?}", batch.raw); if let Err(e) = sender.send(batch.raw).await { @@ -457,7 +487,6 @@ async fn background_task( .insert_pending_call(request.raw_id, request.send_back) .expect("ID unused checked above; qed"), Err(e) => { - manager.reclaim_request_id(request.raw_id); log::warn!("[backend]: client request failed: {:?}", e); let _ = request.send_back.map(|s| s.send(Err(Error::TransportError(Box::new(e))))); } @@ -471,7 +500,6 @@ async fn background_task( .expect("Request ID unused checked above; qed"), Err(e) => { log::warn!("[backend]: client subscription failed: {:?}", e); - manager.reclaim_request_id(sub.raw_id); let _ = sub.send_back.send(Err(Error::TransportError(Box::new(e)))); } }, @@ -484,13 +512,13 @@ async fn background_task( .get_request_id_by_subscription_id(&sub_id) .and_then(|req_id| build_unsubscribe_message(&mut manager, req_id, sub_id)) { - stop_subscription(&mut sender, &mut manager, unsub).await; + stop_subscription(&mut sender, unsub).await; } } Either::Right((Some(Ok(JsonRpcResponse::Single(response))), _)) => { match process_response(&mut manager, response, max_notifs_per_subscription) { Ok(Some(unsub)) => { - stop_subscription(&mut sender, &mut manager, unsub).await; + stop_subscription(&mut sender, unsub).await; } Ok(None) => (), Err(err) => { @@ -526,7 +554,6 @@ async fn background_task( .expect("All request IDs valid checked by RequestManager above; qed"); ordered_responses[pos] = rp; } - manager.reclaim_request_id(batch_state.request_id); let _ = batch_state.send_back.send(Ok(ordered_responses)); } Either::Right((Some(Ok(JsonRpcResponse::Subscription(notif))), _)) => { @@ -546,7 +573,7 @@ async fn background_task( log::error!("Dropping subscription {:?} error: {:?}", sub_id, e); let unsub_req = build_unsubscribe_message(&mut manager, request_id, sub_id) .expect("request ID and subscription ID valid checked above; qed"); - stop_subscription(&mut sender, &mut manager, unsub_req).await; + stop_subscription(&mut sender, unsub_req).await; } } None => { @@ -585,8 +612,6 @@ fn process_response( Some(None) => return Ok(None), None => return Err(Error::InvalidRequestId), }; - - manager.reclaim_request_id(response.id); let _ = send_back_oneshot.send(Ok(response.result)); Ok(None) } @@ -620,11 +645,12 @@ fn process_response( /// Sends an unsubscribe to request to server to indicate /// that the client is not interested in the subscription anymore. -async fn stop_subscription(sender: &mut WsSender, manager: &mut RequestManager, unsub: RequestMessage) { +// +// NOTE: we don't count this a concurrent request as it's part of a subscription. +async fn stop_subscription(sender: &mut WsSender, unsub: RequestMessage) { if let Err(e) = sender.send(unsub.raw).await { log::error!("Send unsubscribe request failed: {:?}", e); } - manager.reclaim_request_id(unsub.raw_id); } /// Builds an unsubscription message, semantically the same as an ordinary request. diff --git a/ws-client/src/manager.rs b/ws-client/src/manager.rs index 772a33b543..460dc19bc6 100644 --- a/ws-client/src/manager.rs +++ b/ws-client/src/manager.rs @@ -10,10 +10,7 @@ use fnv::FnvHashMap; use futures::channel::{mpsc, oneshot}; use jsonrpsee_types::{error::Error, v2::dummy::SubscriptionId}; use serde_json::Value as JsonValue; -use std::collections::{ - hash_map::{Entry, HashMap}, - VecDeque, -}; +use std::collections::hash_map::{Entry, HashMap}; #[derive(Debug)] enum Kind { @@ -61,10 +58,6 @@ pub struct BatchState { #[derive(Debug)] /// Manages and monitors JSONRPC v2 method calls and subscriptions. pub struct RequestManager { - /// Batch ID. - batch_id: BatchId, - /// Vacant requestIDs. - free_slots: VecDeque, /// List of requests that are waiting for a response from the server. // NOTE: FnvHashMap is used here because RequestId is not under the caller's control and is known to be a short key. requests: FnvHashMap, @@ -76,31 +69,8 @@ pub struct RequestManager { impl RequestManager { /// Create a new `RequestManager` with specified capacity. - pub fn new(slot_capacity: usize) -> Self { - Self { - batch_id: 0, - free_slots: (0..slot_capacity as u64).collect(), - requests: FnvHashMap::default(), - subscriptions: HashMap::new(), - batches: HashMap::default(), - } - } - - /// Get next batch ID. - pub fn next_batch_id(&mut self) -> BatchId { - let id = self.batch_id; - self.batch_id = self.batch_id.wrapping_add(1); - id - } - - /// Mark a used RequestID as free again. - pub fn reclaim_request_id(&mut self, request_id: RequestId) { - self.free_slots.push_back(request_id); - } - - /// Get the next available request ID. - pub fn next_request_id(&mut self) -> Result { - self.free_slots.pop_front().ok_or(Error::MaxSlotsExceeded) + pub fn new() -> Self { + Self { requests: FnvHashMap::default(), subscriptions: HashMap::new(), batches: HashMap::default() } } /// Tries to insert a new pending call. @@ -286,13 +256,12 @@ mod tests { use futures::channel::{mpsc, oneshot}; use jsonrpsee_types::v2::dummy::SubscriptionId; use serde_json::Value as JsonValue; - const TEST_LIMIT: usize = 10; #[test] fn insert_remove_pending_request_works() { let (request_tx, _) = oneshot::channel::>(); - let mut manager = RequestManager::new(TEST_LIMIT); + let mut manager = RequestManager::new(); assert!(manager.insert_pending_call(0, Some(request_tx)).is_ok()); assert!(manager.complete_pending_call(0).is_some()); } @@ -301,7 +270,7 @@ mod tests { fn insert_remove_subscription_works() { let (pending_sub_tx, _) = oneshot::channel::, SubscriptionId), Error>>(); let (sub_tx, _) = mpsc::channel::(1); - let mut manager = RequestManager::new(TEST_LIMIT); + let mut manager = RequestManager::new(); assert!(manager.insert_pending_subscription(1, pending_sub_tx, "unsubscribe_method".into()).is_ok()); let (_send_back_oneshot, unsubscribe_method) = manager.complete_pending_subscription(1).unwrap(); assert!(manager @@ -319,7 +288,7 @@ mod tests { let (pending_sub_tx, _) = oneshot::channel::, SubscriptionId), Error>>(); let (sub_tx, _) = mpsc::channel::(1); - let mut manager = RequestManager::new(TEST_LIMIT); + let mut manager = RequestManager::new(); assert!(manager.insert_pending_call(0, Some(request_tx1)).is_ok()); assert!(manager.insert_pending_call(0, Some(request_tx2)).is_err()); assert!(manager.insert_pending_subscription(0, pending_sub_tx, "beef".to_string()).is_err()); @@ -337,7 +306,7 @@ mod tests { let (pending_sub_tx2, _) = oneshot::channel::, SubscriptionId), Error>>(); let (sub_tx, _) = mpsc::channel::(1); - let mut manager = RequestManager::new(TEST_LIMIT); + let mut manager = RequestManager::new(); assert!(manager.insert_pending_subscription(99, pending_sub_tx1, "beef".to_string()).is_ok()); assert!(manager.insert_pending_call(99, Some(request_tx)).is_err()); assert!(manager.insert_pending_subscription(99, pending_sub_tx2, "vegan".to_string()).is_err()); @@ -356,7 +325,7 @@ mod tests { let (sub_tx1, _) = mpsc::channel::(1); let (sub_tx2, _) = mpsc::channel::(1); - let mut manager = RequestManager::new(TEST_LIMIT); + let mut manager = RequestManager::new(); assert!(manager.insert_subscription(3, SubscriptionId::Num(0), sub_tx1, "bibimbap".to_string()).is_ok()); assert!(manager.insert_subscription(3, SubscriptionId::Num(1), sub_tx2, "bibimbap".to_string()).is_err()); @@ -369,15 +338,4 @@ mod tests { assert!(manager.remove_subscription(3, SubscriptionId::Num(1)).is_none()); assert!(manager.remove_subscription(3, SubscriptionId::Num(0)).is_some()); } - - #[test] - fn request_manager_limit_works() { - let mut manager = RequestManager::new(TEST_LIMIT); - for id in 0..TEST_LIMIT { - assert_eq!(id as u64, manager.next_request_id().unwrap()); - } - assert!(matches!(manager.next_request_id().unwrap_err(), Error::MaxSlotsExceeded)); - manager.reclaim_request_id(5); - assert_eq!(5, manager.next_request_id().unwrap()); - } } From 1ae28b9942506fd8f3c15fc66d0aa0b29a8b6d22 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 14 Apr 2021 17:40:53 +0200 Subject: [PATCH 09/41] make tests compile again --- Cargo.toml | 2 +- http-client/src/lib.rs | 5 +- http-client/src/tests.rs | 50 +++++++++------ tests/src/lib.rs | 47 +++++++------- types/src/error.rs | 6 +- types/src/v2/mod.rs | 3 +- ws-client/src/lib.rs | 5 +- ws-client/src/tests.rs | 135 ++++++++++++++++----------------------- 8 files changed, 120 insertions(+), 133 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4a6670bdc0..a8d8977794 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ members = [ "http-client", "http-server", "test-utils", - # "tests", + "tests", "types", "utils", "ws-client", diff --git a/http-client/src/lib.rs b/http-client/src/lib.rs index 927f169c49..13b0d1c998 100644 --- a/http-client/src/lib.rs +++ b/http-client/src/lib.rs @@ -39,7 +39,8 @@ extern crate hyper13_rustls as hyper_rustls; mod client; mod transport; -//#[cfg(test)] -//mod tests; +#[cfg(test)] +mod tests; + pub use client::{HttpClient, HttpClientBuilder}; pub use transport::HttpTransportClient; diff --git a/http-client/src/tests.rs b/http-client/src/tests.rs index 79795e1695..c0fa786c49 100644 --- a/http-client/src/tests.rs +++ b/http-client/src/tests.rs @@ -1,12 +1,13 @@ use crate::client::HttpClientBuilder; +use jsonrpsee_test_utils::helpers::*; +use jsonrpsee_test_utils::types::Id; use jsonrpsee_types::{ error::Error, - jsonrpc::{self, ErrorCode, JsonValue, Params}, traits::Client, + v2::{dummy::JsonRpcParams, error::*}, }; - -use jsonrpsee_test_utils::helpers::*; -use jsonrpsee_test_utils::types::Id; +use serde::Serialize; +use serde_json::Value as JsonValue; #[tokio::test] async fn method_call_works() { @@ -20,7 +21,10 @@ 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", Params::None) + .notification( + "i_dont_care_about_the_response_because_the_server_should_not_respond", + JsonRpcParams::NoParams::, + ) .await .unwrap(); } @@ -34,31 +38,31 @@ async fn response_with_wrong_id() { #[tokio::test] async fn response_method_not_found() { let err = run_request_with_response(method_not_found(Id::Num(0))).await.unwrap_err(); - assert_jsonrpc_error_response(err, ErrorCode::MethodNotFound, METHOD_NOT_FOUND.into()); + assert_jsonrpc_error_response(err, METHOD_NOT_FOUND_CODE, METHOD_NOT_FOUND_MSG); } #[tokio::test] async fn response_parse_error() { let err = run_request_with_response(parse_error(Id::Num(0))).await.unwrap_err(); - assert_jsonrpc_error_response(err, ErrorCode::ParseError, PARSE_ERROR.into()); + assert_jsonrpc_error_response(err, PARSE_ERROR_CODE, PARSE_ERROR_MSG); } #[tokio::test] async fn invalid_request_works() { let err = run_request_with_response(invalid_request(Id::Num(0_u64))).await.unwrap_err(); - assert_jsonrpc_error_response(err, ErrorCode::InvalidRequest, INVALID_REQUEST.into()); + assert_jsonrpc_error_response(err, INVALID_REQUEST_CODE, INVALID_REQUEST_MSG); } #[tokio::test] async fn invalid_params_works() { let err = run_request_with_response(invalid_params(Id::Num(0_u64))).await.unwrap_err(); - assert_jsonrpc_error_response(err, ErrorCode::InvalidParams, INVALID_PARAMS.into()); + assert_jsonrpc_error_response(err, INVALID_PARAMS_CODE, INVALID_PARAMS_MSG); } #[tokio::test] async fn internal_error_works() { let err = run_request_with_response(internal_error(Id::Num(0_u64))).await.unwrap_err(); - assert_jsonrpc_error_response(err, ErrorCode::InternalError, INTERNAL_ERROR.into()); + assert_jsonrpc_error_response(err, INTERNAL_ERROR_CODE, INTERNAL_ERROR_MSG); } #[tokio::test] @@ -71,9 +75,9 @@ async fn subscription_response_to_request() { #[tokio::test] async fn batch_request_works() { let batch_request = vec![ - ("say_hello".to_string(), Params::None), - ("say_goodbye".to_string(), Params::Array(vec![0.into(), 1.into(), 2.into()])), - ("get_swag".to_string(), Params::None), + ("say_hello", JsonRpcParams::NoParams), + ("say_goodbye", JsonRpcParams::Array(vec![&0_u64, &1, &2])), + ("get_swag", JsonRpcParams::NoParams), ]; 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).await.unwrap(); @@ -83,16 +87,19 @@ async fn batch_request_works() { #[tokio::test] async fn batch_request_out_of_order_response() { let batch_request = vec![ - ("say_hello".to_string(), Params::None), - ("say_goodbye".to_string(), Params::Array(vec![0.into(), 1.into(), 2.into()])), - ("get_swag".to_string(), Params::None), + ("say_hello", JsonRpcParams::NoParams), + ("say_goodbye", JsonRpcParams::Array(vec![&0_u64, &1, &2])), + ("get_swag", JsonRpcParams::NoParams), ]; 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).await.unwrap(); assert_eq!(response, vec!["hello".to_string(), "goodbye".to_string(), "here's your swag".to_string()]); } -async fn run_batch_request_with_response(batch: Vec<(String, Params)>, response: String) -> Result, Error> { +async fn run_batch_request_with_response<'a, T: Serialize + std::fmt::Debug + Send + Sync>( + batch: Vec<(&'a str, JsonRpcParams<'a, T>)>, + response: String, +) -> Result, Error> { let server_addr = http_server_with_hardcoded_response(response).await; let uri = format!("http://{}", server_addr); let client = HttpClientBuilder::default().build(&uri).unwrap(); @@ -103,15 +110,16 @@ async fn run_request_with_response(response: String) -> Result let server_addr = http_server_with_hardcoded_response(response).await; let uri = format!("http://{}", server_addr); let client = HttpClientBuilder::default().build(&uri).unwrap(); - client.request("say_hello", Params::None).await + client.request::("say_hello", JsonRpcParams::NoParams).await } -fn assert_jsonrpc_error_response(response: Error, code: ErrorCode, message: String) { - let expected = jsonrpc::Error { code, message, data: None }; +fn assert_jsonrpc_error_response(response: Error, code: i32, message: &str) { + panic!("err: {:?}", response); + /*let expected = jsonrpc::Error { code, message, data: None }; match response { Error::Request(err) => { assert_eq!(err, expected); } e @ _ => panic!("Expected error: \"{}\", got: {:?}", expected, e), - }; + };*/ } diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 18663ed7b6..3cb4bfd011 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -27,7 +27,7 @@ #![cfg(test)] mod helpers; -mod proc_macros; +// mod proc_macros; use std::sync::Arc; use std::time::Duration; @@ -36,8 +36,8 @@ use helpers::{http_server, websocket_server, websocket_server_with_subscription} use jsonrpsee_http_client::HttpClientBuilder; use jsonrpsee_types::{ error::Error, - jsonrpc::{JsonValue, Params}, traits::{Client, SubscriptionClient}, + v2::{dummy::JsonRpcParams, JsonValue}, }; use jsonrpsee_ws_client::{WsClientBuilder, WsSubscription}; @@ -46,16 +46,16 @@ async fn ws_subscription_works() { let server_addr = websocket_server_with_subscription().await; let server_url = format!("ws://{}", server_addr); let client = WsClientBuilder::default().build(&server_url).await.unwrap(); - let mut hello_sub: WsSubscription = - client.subscribe("subscribe_hello", Params::None, "unsubscribe_hello").await.unwrap(); - let mut foo_sub: WsSubscription = - client.subscribe("subscribe_foo", Params::None, "unsubscribe_foo").await.unwrap(); + let mut hello_sub: WsSubscription = + client.subscribe("subscribe_hello", JsonRpcParams::NoParams::, "unsubscribe_hello").await.unwrap(); + let mut foo_sub: WsSubscription = + client.subscribe("subscribe_foo", JsonRpcParams::NoParams::, "unsubscribe_foo").await.unwrap(); for _ in 0..10 { let hello = hello_sub.next().await.unwrap(); let foo = foo_sub.next().await.unwrap(); - assert_eq!(hello, JsonValue::String("hello from subscription".to_owned())); - assert_eq!(foo, JsonValue::Number(1337_u64.into())); + assert_eq!(&hello, "hello from subscription"); + assert_eq!(foo, 1337); } } @@ -64,8 +64,8 @@ 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: JsonValue = client.request("say_hello", Params::None).await.unwrap(); - assert_eq!(response, JsonValue::String("hello".into())); + let response: String = client.request("say_hello", JsonRpcParams::NoParams::).await.unwrap(); + assert_eq!(&response, "hello"); } #[tokio::test] @@ -73,7 +73,7 @@ async fn http_method_call_works() { let server_addr = http_server().await; let uri = format!("http://{}", server_addr); let client = HttpClientBuilder::default().build(&uri).unwrap(); - let response: String = client.request("say_hello", Params::None).await.unwrap(); + let response: String = client.request("say_hello", JsonRpcParams::NoParams::).await.unwrap(); assert_eq!(&response, "hello"); } @@ -86,9 +86,9 @@ async fn ws_subscription_several_clients() { for _ in 0..10 { let client = WsClientBuilder::default().build(&server_url).await.unwrap(); let hello_sub: WsSubscription = - client.subscribe("subscribe_hello", Params::None, "unsubscribe_hello").await.unwrap(); + client.subscribe("subscribe_hello", JsonRpcParams::NoParams::, "unsubscribe_hello").await.unwrap(); let foo_sub: WsSubscription = - client.subscribe("subscribe_foo", Params::None, "unsubscribe_foo").await.unwrap(); + client.subscribe("subscribe_foo", JsonRpcParams::NoParams::, "unsubscribe_foo").await.unwrap(); clients.push((client, hello_sub, foo_sub)) } } @@ -103,9 +103,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: WsSubscription = - client.subscribe("subscribe_hello", Params::None, "unsubscribe_hello").await.unwrap(); + client.subscribe("subscribe_hello", JsonRpcParams::NoParams::, "unsubscribe_hello").await.unwrap(); let foo_sub: WsSubscription = - client.subscribe("subscribe_foo", Params::None, "unsubscribe_foo").await.unwrap(); + client.subscribe("subscribe_foo", JsonRpcParams::NoParams::, "unsubscribe_foo").await.unwrap(); clients.push((client, hello_sub, foo_sub)) } @@ -124,7 +124,7 @@ async fn ws_subscription_several_clients_with_drop() { drop(foo_sub); // Send this request to make sure that the client's background thread hasn't // been canceled. - let _r: String = client.request("say_hello", Params::None).await.unwrap(); + let _r: String = client.request("say_hello", JsonRpcParams::NoParams::).await.unwrap(); drop(client); } @@ -148,7 +148,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: WsSubscription = - client.subscribe("subscribe_hello", Params::None, "unsubscribe_hello").await.unwrap(); + client.subscribe("subscribe_hello", JsonRpcParams::NoParams::, "unsubscribe_hello").await.unwrap(); // don't poll the subscription stream for 2 seconds, should be full now. std::thread::sleep(Duration::from_secs(2)); @@ -162,11 +162,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", Params::None).await.unwrap(); + let _hello_req: JsonValue = client.request("say_hello", JsonRpcParams::NoParams::).await.unwrap(); // The same subscription should be possible to register again. let mut other_sub: WsSubscription = - client.subscribe("subscribe_hello", Params::None, "unsubscribe_hello").await.unwrap(); + client.subscribe("subscribe_hello", JsonRpcParams::NoParams::, "unsubscribe_hello").await.unwrap(); other_sub.next().await.unwrap(); } @@ -181,7 +181,8 @@ async fn ws_more_request_than_buffer_should_not_deadlock() { for _ in 0..6 { let c = client.clone(); - requests.push(tokio::spawn(async move { c.request::("say_hello", Params::None).await })); + requests + .push(tokio::spawn(async move { c.request::("say_hello", JsonRpcParams::NoParams).await })); } for req in requests { @@ -192,14 +193,14 @@ async fn ws_more_request_than_buffer_should_not_deadlock() { #[tokio::test] async fn https_works() { let client = HttpClientBuilder::default().build("https://kusama-rpc.polkadot.io").unwrap(); - let response: String = client.request("system_chain", Params::None).await.unwrap(); + let response: String = client.request("system_chain", JsonRpcParams::NoParams::).await.unwrap(); assert_eq!(&response, "Kusama"); } #[tokio::test] async fn wss_works() { let client = WsClientBuilder::default().build("wss://kusama-rpc.polkadot.io").await.unwrap(); - let response: String = client.request("system_chain", Params::None).await.unwrap(); + let response: String = client.request("system_chain", JsonRpcParams::NoParams::).await.unwrap(); assert_eq!(&response, "Kusama"); } @@ -212,6 +213,6 @@ async fn ws_with_non_ascii_url_doesnt_hang_or_panic() { #[tokio::test] async fn http_with_non_ascii_url_doesnt_hang_or_panic() { let client = HttpClientBuilder::default().build("http://♥♥♥♥♥♥∀∂").unwrap(); - let err: Result<(), Error> = client.request("system_chain", Params::None).await; + let err: Result<(), Error> = client.request("system_chain", JsonRpcParams::NoParams::).await; assert!(matches!(err, Err(Error::TransportError(_)))); } diff --git a/types/src/error.rs b/types/src/error.rs index e667be3094..3257f4348d 100644 --- a/types/src/error.rs +++ b/types/src/error.rs @@ -1,4 +1,6 @@ +use crate::v2::RpcError as JsonRpcError; use std::fmt; + /// Convenience type for displaying errors. #[derive(Clone, Debug, PartialEq)] pub struct Mismatch { @@ -21,8 +23,8 @@ pub enum Error { #[error("Networking or low-level protocol error: {0}")] TransportError(#[source] Box), /// JSON-RPC request error. - //#[error("JSON-RPC request error: {0:?}")] - //Request(#[source] jsonrpc::Error), + #[error("JSON-RPC request error: {0:?}")] + Request(#[source] JsonRpcError), /// Frontend/backend channel error. #[error("Frontend/backend channel error: {0}")] Internal(#[source] futures::channel::mpsc::SendError), diff --git a/types/src/v2/mod.rs b/types/src/v2/mod.rs index 5db4965521..b8c39d55df 100644 --- a/types/src/v2/mod.rs +++ b/types/src/v2/mod.rs @@ -10,10 +10,11 @@ pub mod error; /// Traits. pub mod traits; +// TODO(niklasad1): revisit re-exports. pub use beef::lean::Cow; pub use error::RpcError; -pub use serde_json::to_string; pub use serde_json::value::{to_raw_value, RawValue}; +pub use serde_json::Value as JsonValue; /// [JSON-RPC request object](https://www.jsonrpc.org/specification#request-object) #[derive(Deserialize, Debug)] diff --git a/ws-client/src/lib.rs b/ws-client/src/lib.rs index 3186a11c25..04b5045f1a 100644 --- a/ws-client/src/lib.rs +++ b/ws-client/src/lib.rs @@ -13,7 +13,8 @@ pub mod stream; /// WebSocket transport. pub mod transport; -//#[cfg(test)] -//mod tests; +#[cfg(test)] +mod tests; + pub use client::{WsClient, WsClientBuilder}; pub use jsonrpsee_types::client::Subscription as WsSubscription; diff --git a/ws-client/src/tests.rs b/ws-client/src/tests.rs index 9c20e6d447..18bdc28822 100644 --- a/ws-client/src/tests.rs +++ b/ws-client/src/tests.rs @@ -5,32 +5,16 @@ use jsonrpsee_test_utils::helpers::*; use jsonrpsee_test_utils::types::{Id, WebSocketTestServer}; use jsonrpsee_types::{ error::Error, - jsonrpc::{self, Params}, traits::{Client, SubscriptionClient}, + v2::{dummy::JsonRpcParams, error::*}, }; - -fn assert_error_response(response: Result, code: jsonrpc::ErrorCode, message: String) { - let expected = jsonrpc::Error { code, message, data: None }; - match response { - Err(Error::Request(err)) => { - assert_eq!(err, expected); - } - e @ _ => panic!("Expected error: \"{}\", got: {:?}", expected, e), - }; -} +use serde::Serialize; +use serde_json::Value as JsonValue; #[tokio::test] async fn method_call_works() { - let server = WebSocketTestServer::with_hardcoded_response( - "127.0.0.1:0".parse().unwrap(), - ok_response("hello".into(), Id::Num(0_u64)), - ) - .await; - let uri = to_ws_uri_string(server.local_addr()); - let client = WsClientBuilder::default().build(&uri).await.unwrap(); - let response: jsonrpc::JsonValue = client.request("say_hello", jsonrpc::Params::None).await.unwrap(); - let exp = jsonrpc::JsonValue::String("hello".to_string()); - assert_eq!(response, exp); + let result = run_request_with_response(ok_response("hello".into(), Id::Num(0))).await.unwrap(); + assert_eq!(JsonValue::String("hello".into()), result); } #[tokio::test] @@ -39,61 +23,43 @@ async fn notif_works() { let server = WebSocketTestServer::with_hardcoded_response("127.0.0.1:0".parse().unwrap(), String::new()).await; let uri = to_ws_uri_string(server.local_addr()); let client = WsClientBuilder::default().build(&uri).await.unwrap(); - assert!(client.notification("notif", jsonrpc::Params::None).await.is_ok()); + assert!(client.notification("notif", JsonRpcParams::NoParams::).await.is_ok()); } #[tokio::test] -async fn method_not_found_works() { - let server = - WebSocketTestServer::with_hardcoded_response("127.0.0.1:0".parse().unwrap(), method_not_found(Id::Num(0_u64))) - .await; - let uri = to_ws_uri_string(server.local_addr()); - let client = WsClientBuilder::default().build(&uri).await.unwrap(); - let response: Result = client.request("say_hello", jsonrpc::Params::None).await; - assert_error_response(response, jsonrpc::ErrorCode::MethodNotFound, METHOD_NOT_FOUND.into()); +async fn response_with_wrong_id() { + let err = run_request_with_response(ok_response("hello".into(), Id::Num(99))).await.unwrap_err(); + assert!(matches!(err, Error::InvalidRequestId)); +} + +#[tokio::test] +async fn response_method_not_found() { + let err = run_request_with_response(method_not_found(Id::Num(0))).await.unwrap_err(); + assert_error_response(err, METHOD_NOT_FOUND_CODE, METHOD_NOT_FOUND_MSG); } #[tokio::test] async fn parse_error_works() { - let server = - WebSocketTestServer::with_hardcoded_response("127.0.0.1:0".parse().unwrap(), parse_error(Id::Num(0_u64))).await; - let uri = to_ws_uri_string(server.local_addr()); - let client = WsClientBuilder::default().build(&uri).await.unwrap(); - let response: Result = client.request("say_hello", jsonrpc::Params::None).await; - assert_error_response(response, jsonrpc::ErrorCode::ParseError, PARSE_ERROR.into()); + let err = run_request_with_response(parse_error(Id::Num(0))).await.unwrap_err(); + assert_error_response(err, PARSE_ERROR_CODE, PARSE_ERROR_MSG); } #[tokio::test] async fn invalid_request_works() { - let server = - WebSocketTestServer::with_hardcoded_response("127.0.0.1:0".parse().unwrap(), invalid_request(Id::Num(0_u64))) - .await; - let uri = to_ws_uri_string(server.local_addr()); - let client = WsClientBuilder::default().build(&uri).await.unwrap(); - let response: Result = client.request("say_hello", jsonrpc::Params::None).await; - assert_error_response(response, jsonrpc::ErrorCode::InvalidRequest, INVALID_REQUEST.into()); + let err = run_request_with_response(invalid_request(Id::Num(0_u64))).await.unwrap_err(); + assert_error_response(err, INVALID_REQUEST_CODE, INVALID_REQUEST_MSG); } #[tokio::test] async fn invalid_params_works() { - let server = - WebSocketTestServer::with_hardcoded_response("127.0.0.1:0".parse().unwrap(), invalid_params(Id::Num(0_u64))) - .await; - let uri = to_ws_uri_string(server.local_addr()); - let client = WsClientBuilder::default().build(&uri).await.unwrap(); - let response: Result = client.request("say_hello", jsonrpc::Params::None).await; - assert_error_response(response, jsonrpc::ErrorCode::InvalidParams, INVALID_PARAMS.into()); + let err = run_request_with_response(invalid_params(Id::Num(0_u64))).await.unwrap_err(); + assert_error_response(err, INVALID_PARAMS_CODE, INVALID_PARAMS_MSG); } #[tokio::test] async fn internal_error_works() { - let server = - WebSocketTestServer::with_hardcoded_response("127.0.0.1:0".parse().unwrap(), internal_error(Id::Num(0_u64))) - .await; - let uri = to_ws_uri_string(server.local_addr()); - let client = WsClientBuilder::default().build(&uri).await.unwrap(); - let response: Result = client.request("say_hello", jsonrpc::Params::None).await; - assert_error_response(response, jsonrpc::ErrorCode::InternalError, INTERNAL_ERROR.into()); + let err = run_request_with_response(internal_error(Id::Num(0_u64))).await.unwrap_err(); + assert_error_response(err, INTERNAL_ERROR_CODE, INTERNAL_ERROR_MSG); } #[tokio::test] @@ -101,39 +67,26 @@ async fn subscription_works() { let server = WebSocketTestServer::with_hardcoded_subscription( "127.0.0.1:0".parse().unwrap(), server_subscription_id_response(Id::Num(0)), - server_subscription_response(jsonrpc::JsonValue::String("hello my friend".to_owned())), + server_subscription_response(JsonValue::String("hello my friend".to_owned())), ) .await; let uri = to_ws_uri_string(server.local_addr()); let client = WsClientBuilder::default().build(&uri).await.unwrap(); { let mut sub: WsSubscription = - client.subscribe("subscribe_hello", jsonrpc::Params::None, "unsubscribe_hello").await.unwrap(); + client.subscribe("subscribe_hello", JsonRpcParams::NoParams::, "unsubscribe_hello").await.unwrap(); let response: String = sub.next().await.unwrap().into(); assert_eq!("hello my friend".to_owned(), response); } } -#[tokio::test] -async fn response_with_wrong_id() { - let server = WebSocketTestServer::with_hardcoded_response( - "127.0.0.1:0".parse().unwrap(), - ok_response(jsonrpc::JsonValue::String("foo".into()), Id::Num(99_u64)), - ) - .await; - let uri = to_ws_uri_string(server.local_addr()); - let client = WsClientBuilder::default().build(&uri).await.unwrap(); - let err: Result = client.request("say_hello", jsonrpc::Params::None).await; - assert!(matches!(err, Err(Error::RestartNeeded(e)) if e.to_string().contains("Invalid request ID"))); -} - #[tokio::test] async fn batch_request_works() { let _ = env_logger::try_init(); let batch_request = vec![ - ("say_hello".to_string(), Params::None), - ("say_goodbye".to_string(), Params::Array(vec![0.into(), 1.into(), 2.into()])), - ("get_swag".to_string(), Params::None), + ("say_hello", JsonRpcParams::NoParams), + ("say_goodbye", JsonRpcParams::Array(vec![&0_u64, &1, &2])), + ("get_swag", JsonRpcParams::NoParams), ]; 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).await.unwrap(); @@ -143,9 +96,9 @@ async fn batch_request_works() { #[tokio::test] async fn batch_request_out_of_order_response() { let batch_request = vec![ - ("say_hello".to_string(), Params::None), - ("say_goodbye".to_string(), Params::Array(vec![0.into(), 1.into(), 2.into()])), - ("get_swag".to_string(), Params::None), + ("say_hello", JsonRpcParams::NoParams), + ("say_goodbye", JsonRpcParams::Array(vec![&0_u64, &1, &2])), + ("get_swag", JsonRpcParams::NoParams), ]; 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).await.unwrap(); @@ -156,19 +109,39 @@ async fn batch_request_out_of_order_response() { async fn is_connected_works() { let server = WebSocketTestServer::with_hardcoded_response( "127.0.0.1:0".parse().unwrap(), - ok_response(jsonrpc::JsonValue::String("foo".into()), Id::Num(99_u64)), + ok_response(JsonValue::String("foo".into()), Id::Num(99_u64)), ) .await; let uri = to_ws_uri_string(server.local_addr()); let client = WsClientBuilder::default().build(&uri).await.unwrap(); assert!(client.is_connected()); - client.request::("say_hello", jsonrpc::Params::None).await.unwrap_err(); + client.request::("say_hello", JsonRpcParams::NoParams).await.unwrap_err(); assert!(!client.is_connected()) } -async fn run_batch_request_with_response(batch: Vec<(String, Params)>, response: String) -> Result, Error> { +async fn run_batch_request_with_response<'a, T: Serialize + std::fmt::Debug + Send + Sync>( + batch: Vec<(&'a str, JsonRpcParams<'a, T>)>, + response: String, +) -> Result, Error> { let server = WebSocketTestServer::with_hardcoded_response("127.0.0.1:0".parse().unwrap(), response).await; let uri = to_ws_uri_string(server.local_addr()); let client = WsClientBuilder::default().build(&uri).await.unwrap(); client.batch_request(batch).await } + +async fn run_request_with_response(response: String) -> Result { + let server = WebSocketTestServer::with_hardcoded_response("127.0.0.1:0".parse().unwrap(), response).await; + let uri = format!("http://{}", server.local_addr()); + let client = WsClientBuilder::default().build(&uri).await.unwrap(); + client.request::("say_hello", JsonRpcParams::NoParams).await +} + +fn assert_error_response(response: Error, code: i32, message: &str) { + todo!(); + /*match response { + Err(Error::Request(err)) => { + assert_eq!(err, expected); + } + e @ _ => panic!("Expected error: \"{}\", got: {:?}", expected, e), + };*/ +} From 9819bb576f923ce619d7ade32ff5931eda6d1b8a Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 14 Apr 2021 22:28:56 +0200 Subject: [PATCH 10/41] [client transport]: kill leaky abstractions. --- http-client/src/client.rs | 21 ++++-- http-client/src/transport.rs | 33 ++------- types/src/v2/mod.rs | 2 +- ws-client/src/client.rs | 126 +++++++++++++++++++---------------- ws-client/src/transport.rs | 15 +---- 5 files changed, 90 insertions(+), 107 deletions(-) diff --git a/http-client/src/client.rs b/http-client/src/client.rs index a5fcfa6f60..7a36d49b22 100644 --- a/http-client/src/client.rs +++ b/http-client/src/client.rs @@ -56,7 +56,12 @@ impl Client for HttpClient { T: Serialize + std::fmt::Debug + Send + Sync, { let notif = JsonRpcNotification::new(method, params); - self.transport.send_notification(notif).await.map_err(|e| Error::TransportError(Box::new(e))) + let _ = self + .transport + .send(serde_json::to_string(¬if).map_err(Error::ParseError)?) + .await + .map_err(|e| Error::TransportError(Box::new(e))); + Ok(()) } /// Perform a request towards the server. @@ -69,12 +74,14 @@ impl Client for HttpClient { let id = self.request_id.fetch_add(1, Ordering::Relaxed); let request = JsonRpcCall::new(id, method, params); - let response = self + let body = self .transport - .send_request_and_wait_for_response(request) + .send_and_wait_for_response(serde_json::to_string(&request).map_err(Error::ParseError)?) .await .map_err(|e| Error::TransportError(Box::new(e)))?; + let response = serde_json::from_slice(&body).map_err(Error::ParseError)?; + match response { JsonRpcResponse::Single(response) if response.id == id => Ok(response.result), JsonRpcResponse::Single(_) => Err(Error::InvalidRequestId), @@ -100,17 +107,19 @@ impl Client for HttpClient { request_set.insert(id, pos); } - let response = self + let body = self .transport - .send_request_and_wait_for_response(batch_request) + .send_and_wait_for_response(serde_json::to_string(&batch_request).map_err(Error::ParseError)?) .await .map_err(|e| Error::TransportError(Box::new(e)))?; + let response = serde_json::from_slice(&body).map_err(Error::ParseError)?; + match response { JsonRpcResponse::Single(_response) => Err(invalid_response(BATCH_RESPONSE, SINGLE_RESPONSE)), JsonRpcResponse::Subscription(_notif) => Err(invalid_response(BATCH_RESPONSE, SUBSCRIPTION_RESPONSE)), JsonRpcResponse::Batch(rps) => { - // NOTE: `T::default` is placeholder and will be replaced in loop below. + // NOTE: `R::default` is placeholder and will be replaced in loop below. let mut responses = vec![R::default(); ordered_requests.len()]; for rp in rps { let pos = match request_set.get(&rp.id) { diff --git a/http-client/src/transport.rs b/http-client/src/transport.rs index fd7e7ad66d..43a4efcee8 100644 --- a/http-client/src/transport.rs +++ b/http-client/src/transport.rs @@ -45,8 +45,8 @@ impl HttpTransportClient { } } - /// Send request. - async fn send(&self, body: String) -> Result, Error> { + /// Send serialized message. + pub async fn send(&self, body: String) -> Result, Error> { log::debug!("send: {}", body); if body.len() > self.max_request_body_size as usize { @@ -67,35 +67,12 @@ impl HttpTransportClient { } } - /// Send notification. - pub async fn send_notification<'a, T>(&self, notif: JsonRpcNotification<'a, T>) -> Result<(), Error> - where - T: Serialize + std::fmt::Debug, - { - let body = serde_json::to_string(¬if).map_err(Error::Serialization)?; - let _response = self.send(body).await?; - Ok(()) - } - - /// Send request and wait for response. - pub async fn send_request_and_wait_for_response<'a, T, R>( - &self, - request: impl Into>, - ) -> Result, Error> - where - T: Serialize + std::fmt::Debug + 'a, - R: DeserializeOwned, - { - let body = serde_json::to_string(&request.into()).map_err(Error::Serialization)?; + /// Send serialized message and wait until all bytes from the body is read. + pub async fn send_and_wait_for_response(&self, body: String) -> Result, Error> { let response = self.send(body).await?; let (parts, body) = response.into_parts(); let body = hyper_helpers::read_response_to_body(&parts.headers, body, self.max_request_body_size).await?; - - // Note that we don't check the Content-Type of the request. This is deemed - // unnecessary, as a parsing error while happen anyway. - let response = serde_json::from_slice(&body).map_err(Error::ParseError)?; - //log::debug!("recv: {}", serde_json::to_string(&response).expect("valid JSON; qed")); - Ok(response) + Ok(body) } } diff --git a/types/src/v2/mod.rs b/types/src/v2/mod.rs index b8c39d55df..64e076de4f 100644 --- a/types/src/v2/mod.rs +++ b/types/src/v2/mod.rs @@ -10,7 +10,7 @@ pub mod error; /// Traits. pub mod traits; -// TODO(niklasad1): revisit re-exports. +// TODO: revisit re-exports. pub use beef::lean::Cow; pub use error::RpcError; pub use serde_json::value::{to_raw_value, RawValue}; diff --git a/ws-client/src/client.rs b/ws-client/src/client.rs index a19c50aa56..a4df6b54da 100644 --- a/ws-client/src/client.rs +++ b/ws-client/src/client.rs @@ -445,8 +445,7 @@ async fn background_task( let mut manager = RequestManager::new(); let backend_event = futures::stream::unfold(receiver, |mut receiver| async { - // TODO: fix JsonValue here. - let res = receiver.next_response::().await; + let res = receiver.next_response().await; Some((res, receiver)) }); @@ -515,69 +514,78 @@ async fn background_task( stop_subscription(&mut sender, unsub).await; } } - Either::Right((Some(Ok(JsonRpcResponse::Single(response))), _)) => { - match process_response(&mut manager, response, max_notifs_per_subscription) { - Ok(Some(unsub)) => { - stop_subscription(&mut sender, unsub).await; - } - Ok(None) => (), - Err(err) => { - let _ = front_error.send(err); - return; - } - } - } - Either::Right((Some(Ok(JsonRpcResponse::Batch(batch))), _)) => { - let mut digest = Vec::with_capacity(batch.len()); - let mut ordered_responses = vec![JsonValue::Null; batch.len()]; - let mut rps_unordered: Vec<_> = Vec::with_capacity(batch.len()); - - for rp in batch { - digest.push(rp.id); - rps_unordered.push((rp.id, rp.result)); - } - - digest.sort_unstable(); - let batch_state = match manager.complete_pending_batch(digest) { - Some(state) => state, - None => { - log::warn!("Received unknown batch response"); - continue; - } + Either::Right((Some(Ok(raw)), _)) => { + let response: JsonRpcResponse = match serde_json::from_slice(&raw) { + Ok(response) => response, + Err(_e) => continue, }; - for (id, rp) in rps_unordered { - let pos = batch_state - .order - .get(&id) - .copied() - .expect("All request IDs valid checked by RequestManager above; qed"); - ordered_responses[pos] = rp; - } - let _ = batch_state.send_back.send(Ok(ordered_responses)); - } - Either::Right((Some(Ok(JsonRpcResponse::Subscription(notif))), _)) => { - log::info!("notif: {:?}", notif); - let sub_id = notif.params.subscription; - let request_id = match manager.get_request_id_by_subscription_id(&sub_id) { - Some(r) => r, - None => { - log::error!("Subscription ID: {:?} not found", sub_id); - continue; + match response { + JsonRpcResponse::Single(call) => { + match process_response(&mut manager, call, max_notifs_per_subscription) { + Ok(Some(unsub)) => { + stop_subscription(&mut sender, unsub).await; + } + Ok(None) => (), + Err(err) => { + let _ = front_error.send(err); + return; + } + } } - }; + JsonRpcResponse::Batch(batch) => { + let mut digest = Vec::with_capacity(batch.len()); + let mut ordered_responses = vec![JsonValue::Null; batch.len()]; + let mut rps_unordered: Vec<_> = Vec::with_capacity(batch.len()); + + for rp in batch { + digest.push(rp.id); + rps_unordered.push((rp.id, rp.result)); + } - match manager.as_subscription_mut(&request_id) { - Some(send_back_sink) => { - if let Err(e) = send_back_sink.try_send(notif.params.result) { - log::error!("Dropping subscription {:?} error: {:?}", sub_id, e); - let unsub_req = build_unsubscribe_message(&mut manager, request_id, sub_id) - .expect("request ID and subscription ID valid checked above; qed"); - stop_subscription(&mut sender, unsub_req).await; + digest.sort_unstable(); + let batch_state = match manager.complete_pending_batch(digest) { + Some(state) => state, + None => { + log::warn!("Received unknown batch response"); + continue; + } + }; + + for (id, rp) in rps_unordered { + let pos = batch_state + .order + .get(&id) + .copied() + .expect("All request IDs valid checked by RequestManager above; qed"); + ordered_responses[pos] = rp; } + let _ = batch_state.send_back.send(Ok(ordered_responses)); } - None => { - log::error!("Subscription ID: {:?} not an active subscription", sub_id); + JsonRpcResponse::Subscription(notif) => { + log::info!("notif: {:?}", notif); + let sub_id = notif.params.subscription; + let request_id = match manager.get_request_id_by_subscription_id(&sub_id) { + Some(r) => r, + None => { + log::error!("Subscription ID: {:?} not found", sub_id); + continue; + } + }; + + match manager.as_subscription_mut(&request_id) { + Some(send_back_sink) => { + if let Err(e) = send_back_sink.try_send(notif.params.result) { + log::error!("Dropping subscription {:?} error: {:?}", sub_id, e); + let unsub_req = build_unsubscribe_message(&mut manager, request_id, sub_id) + .expect("request ID and subscription ID valid checked above; qed"); + stop_subscription(&mut sender, unsub_req).await; + } + } + None => { + log::error!("Subscription ID: {:?} not an active subscription", sub_id); + } + } } } } diff --git a/ws-client/src/transport.rs b/ws-client/src/transport.rs index f836e1d627..e3bb69e6f7 100644 --- a/ws-client/src/transport.rs +++ b/ws-client/src/transport.rs @@ -28,8 +28,6 @@ use async_std::net::TcpStream; use async_tls::client::TlsStream; use futures::io::{BufReader, BufWriter}; use futures::prelude::*; -use jsonrpsee_types::v2::dummy::JsonRpcResponse; -use serde::de::DeserializeOwned; use soketto::connection; use soketto::handshake::client::{Client as WsRawClient, ServerResponse}; use std::{borrow::Cow, io, net::SocketAddr, time::Duration}; @@ -169,19 +167,10 @@ impl Sender { impl Receiver { /// Returns a `Future` resolving when the server sent us something back. - // - // TODO(niklasad1): return Vec instead to have a clean abstraction. - pub async fn next_response(&mut self) -> Result, WsConnectError> - where - T: DeserializeOwned + std::fmt::Debug, - { + pub async fn next_response(&mut self) -> Result, WsConnectError> { let mut message = Vec::new(); self.inner.receive_data(&mut message).await?; - - let response = serde_json::from_slice(&message).map_err(WsConnectError::ParseError)?; - log::debug!("recv: {:?}", response); - - Ok(response) + Ok(message) } } From 0b07e5df36c2c2e1e4c41e5ad641fa59d77eb085 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 15 Apr 2021 10:43:45 +0200 Subject: [PATCH 11/41] [http client transport]: minor changes in the API. --- http-client/src/client.rs | 10 ++++----- http-client/src/transport.rs | 42 +++++++++++------------------------- 2 files changed, 16 insertions(+), 36 deletions(-) diff --git a/http-client/src/client.rs b/http-client/src/client.rs index 7a36d49b22..9c6e5e6b8a 100644 --- a/http-client/src/client.rs +++ b/http-client/src/client.rs @@ -56,12 +56,10 @@ impl Client for HttpClient { T: Serialize + std::fmt::Debug + Send + Sync, { let notif = JsonRpcNotification::new(method, params); - let _ = self - .transport + self.transport .send(serde_json::to_string(¬if).map_err(Error::ParseError)?) .await - .map_err(|e| Error::TransportError(Box::new(e))); - Ok(()) + .map_err(|e| Error::TransportError(Box::new(e))) } /// Perform a request towards the server. @@ -76,7 +74,7 @@ impl Client for HttpClient { let body = self .transport - .send_and_wait_for_response(serde_json::to_string(&request).map_err(Error::ParseError)?) + .send_and_read_body(serde_json::to_string(&request).map_err(Error::ParseError)?) .await .map_err(|e| Error::TransportError(Box::new(e)))?; @@ -109,7 +107,7 @@ impl Client for HttpClient { let body = self .transport - .send_and_wait_for_response(serde_json::to_string(&batch_request).map_err(Error::ParseError)?) + .send_and_read_body(serde_json::to_string(&batch_request).map_err(Error::ParseError)?) .await .map_err(|e| Error::TransportError(Box::new(e)))?; diff --git a/http-client/src/transport.rs b/http-client/src/transport.rs index 43a4efcee8..77f5ae2821 100644 --- a/http-client/src/transport.rs +++ b/http-client/src/transport.rs @@ -8,12 +8,8 @@ use hyper::client::{Client, HttpConnector}; use hyper_rustls::HttpsConnector; -use jsonrpsee_types::{ - error::GenericTransportError, - v2::dummy::{JsonRpcNotification, JsonRpcRequest, JsonRpcResponse}, -}; +use jsonrpsee_types::error::GenericTransportError; use jsonrpsee_utils::hyper_helpers; -use serde::{de::DeserializeOwned, Serialize}; use thiserror::Error; const CONTENT_TYPE_JSON: &str = "application/json"; @@ -45,8 +41,7 @@ impl HttpTransportClient { } } - /// Send serialized message. - pub async fn send(&self, body: String) -> Result, Error> { + async fn inner_send(&self, body: String) -> Result, Error> { log::debug!("send: {}", body); if body.len() > self.max_request_body_size as usize { @@ -67,13 +62,19 @@ impl HttpTransportClient { } } - /// Send serialized message and wait until all bytes from the body is read. - pub async fn send_and_wait_for_response(&self, body: String) -> Result, Error> { - let response = self.send(body).await?; + /// Send serialized message and wait until all bytes from the HTTP message body is read. + pub async fn send_and_read_body(&self, body: String) -> Result, Error> { + let response = self.inner_send(body).await?; let (parts, body) = response.into_parts(); let body = hyper_helpers::read_response_to_body(&parts.headers, body, self.max_request_body_size).await?; Ok(body) } + + /// Send serialized message without reading the HTTP message body. + pub async fn send(&self, body: String) -> Result<(), Error> { + let _ = self.inner_send(body).await?; + Ok(()) + } } /// Error that can happen during a request. @@ -83,15 +84,6 @@ pub enum Error { #[error("Invalid Url: {0}")] Url(String), - /// Error while serializing the request. - // TODO: can that happen? - #[error("Error while serializing the request")] - Serialization(#[source] serde_json::error::Error), - - /// Response given by the server failed to decode as UTF-8. - #[error("Response body is not UTF-8")] - Utf8(#[source] std::string::FromUtf8Error), - /// Error during the HTTP request, including networking errors and HTTP protocol errors. #[error("Error while performing the HTTP request")] Http(Box), @@ -103,10 +95,6 @@ pub enum Error { status_code: u16, }, - /// Failed to parse the JSON returned by the server into a JSON-RPC response. - #[error("Error while parsing the response body")] - ParseError(#[source] serde_json::error::Error), - /// Request body too large. #[error("The request body was too large")] RequestTooLarge, @@ -127,7 +115,6 @@ where #[cfg(test)] mod tests { use super::{Error, HttpTransportClient}; - use jsonrpsee_types::v2::dummy::{JsonRpcCall, JsonRpcParams}; #[test] fn invalid_http_url_rejected() { @@ -141,12 +128,7 @@ mod tests { let client = HttpTransportClient::new("http://localhost:9933", 80).unwrap(); assert_eq!(client.max_request_body_size, eighty_bytes_limit); - let body = serde_json::to_string(&JsonRpcCall::new( - 1, - "request_larger_than_eightybytes", - JsonRpcParams::NoParams::, - )) - .unwrap(); + let body = "a".repeat(81); assert_eq!(body.len(), 81); let response = client.send(body).await.unwrap_err(); assert!(matches!(response, Error::RequestTooLarge)); From ccf09cb9c54fc05015403f9153fe448277a20951 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 15 Apr 2021 11:46:38 +0200 Subject: [PATCH 12/41] [ws client]: fix batch requests. --- ws-client/src/client.rs | 12 +++++++++++- ws-client/src/manager.rs | 18 +++++------------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/ws-client/src/client.rs b/ws-client/src/client.rs index a4df6b54da..5e4113f882 100644 --- a/ws-client/src/client.rs +++ b/ws-client/src/client.rs @@ -466,8 +466,16 @@ async fn background_task( Either::Left((Some(FrontToBack::Batch(batch)), _)) => { log::trace!("[backend]: client prepares to send batch request: {:?}", batch.raw); + // NOTE(niklasad1): annoying allocation. + if let Err(send_back) = manager.insert_pending_batch(batch.raw_ids.clone(), batch.send_back) { + log::warn!("[backend]: batch request: {:?} already pending", batch.raw_ids); + let _ = send_back.send(Err(Error::InvalidRequestId)); + continue; + } + if let Err(e) = sender.send(batch.raw).await { log::warn!("[backend]: client batch request failed: {:?}", e); + manager.complete_pending_batch(batch.raw_ids); } } // User called `notification` on the front-end @@ -522,6 +530,7 @@ async fn background_task( match response { JsonRpcResponse::Single(call) => { + log::debug!("[backend]: recv method_call {:?}", call); match process_response(&mut manager, call, max_notifs_per_subscription) { Ok(Some(unsub)) => { stop_subscription(&mut sender, unsub).await; @@ -534,6 +543,7 @@ async fn background_task( } } JsonRpcResponse::Batch(batch) => { + log::debug!("[backend]: recv batch {:?}", batch); let mut digest = Vec::with_capacity(batch.len()); let mut ordered_responses = vec![JsonValue::Null; batch.len()]; let mut rps_unordered: Vec<_> = Vec::with_capacity(batch.len()); @@ -563,7 +573,7 @@ async fn background_task( let _ = batch_state.send_back.send(Ok(ordered_responses)); } JsonRpcResponse::Subscription(notif) => { - log::info!("notif: {:?}", notif); + log::debug!("[backend]: recv subscription response {:?}", notif); let sub_id = notif.params.subscription; let request_id = match manager.get_request_id_by_subscription_id(&sub_id) { Some(r) => r, diff --git a/ws-client/src/manager.rs b/ws-client/src/manager.rs index 460dc19bc6..f67d303dd3 100644 --- a/ws-client/src/manager.rs +++ b/ws-client/src/manager.rs @@ -37,20 +37,13 @@ type PendingBatchOneshot = oneshot::Sender, Error>>; type PendingSubscriptionOneshot = oneshot::Sender, SubscriptionId), Error>>; type SubscriptionSink = mpsc::Sender; type UnsubscribeMethod = String; -/// Unique ID that are generated by the RequestManager. -// TODO: new type for this https://github.com/paritytech/jsonrpsee/issues/249 type RequestId = u64; -/// Wrapping counter for requests within a batch request. -// TODO: new type for this https://github.com/paritytech/jsonrpsee/issues/249 -type BatchId = u64; #[derive(Debug)] /// Batch state. pub struct BatchState { /// Order that the request was performed in. - pub order: FnvHashMap, - /// Request ID fetch from the `RequestManager` - pub request_id: RequestId, + pub order: FnvHashMap, /// Oneshot send back. pub send_back: PendingBatchOneshot, } @@ -64,7 +57,7 @@ pub struct RequestManager { /// Reverse lookup, to find a request ID in constant time by `subscription ID` instead of looking through all requests. subscriptions: HashMap, /// Pending batch requests - batches: FnvHashMap, BatchState>, + batches: FnvHashMap, BatchState>, } impl RequestManager { @@ -94,9 +87,8 @@ impl RequestManager { /// Returns `Ok` if the pending request was successfully inserted otherwise `Err`. pub fn insert_pending_batch( &mut self, - mut batch: Vec, + mut batch: Vec, send_back: PendingBatchOneshot, - request_id: RequestId, ) -> Result<(), PendingBatchOneshot> { let mut order = FnvHashMap::with_capacity_and_hasher(batch.len(), Default::default()); for (idx, batch_id) in batch.iter().enumerate() { @@ -104,7 +96,7 @@ impl RequestManager { } batch.sort_unstable(); if let Entry::Vacant(v) = self.batches.entry(batch) { - v.insert(BatchState { order, request_id, send_back }); + v.insert(BatchState { order, send_back }); Ok(()) } else { Err(send_back) @@ -171,7 +163,7 @@ impl RequestManager { /// Tries to complete a pending batch request /// /// Returns `Some` if the subscription was completed otherwise `None`. - pub fn complete_pending_batch(&mut self, batch: Vec) -> Option { + pub fn complete_pending_batch(&mut self, batch: Vec) -> Option { match self.batches.entry(batch) { Entry::Occupied(request) => { let (_digest, state) = request.remove_entry(); From 9b74206eb7099e6d499ce4ccef9eeff080975ab5 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 15 Apr 2021 13:17:18 +0200 Subject: [PATCH 13/41] fix nits --- http-client/src/client.rs | 9 ++- types/src/error.rs | 11 ++-- types/src/v2/error.rs | 112 ++++++++++++++++++++++++++++++++++++++ ws-client/src/client.rs | 4 +- ws-client/src/tests.rs | 2 +- 5 files changed, 130 insertions(+), 8 deletions(-) diff --git a/http-client/src/client.rs b/http-client/src/client.rs index 9c6e5e6b8a..43b69e8a5d 100644 --- a/http-client/src/client.rs +++ b/http-client/src/client.rs @@ -5,6 +5,7 @@ use jsonrpsee_types::{ error::{Error, Mismatch}, traits::Client, v2::dummy::{JsonRpcCall, JsonRpcNotification, JsonRpcParams, JsonRpcResponse}, + v2::error::JsonRpcError, }; use serde::{de::DeserializeOwned, Serialize}; use std::sync::atomic::{AtomicU64, Ordering}; @@ -78,7 +79,13 @@ impl Client for HttpClient { .await .map_err(|e| Error::TransportError(Box::new(e)))?; - let response = serde_json::from_slice(&body).map_err(Error::ParseError)?; + let response = match serde_json::from_slice(&body) { + Ok(response) => response, + Err(_) => { + let err: JsonRpcError = serde_json::from_slice(&body).map_err(Error::ParseError)?; + return Err(Error::Request(err)); + } + }; match response { JsonRpcResponse::Single(response) if response.id == id => Ok(response.result), diff --git a/types/src/error.rs b/types/src/error.rs index 3257f4348d..9fa058ec9d 100644 --- a/types/src/error.rs +++ b/types/src/error.rs @@ -1,4 +1,7 @@ -use crate::v2::RpcError as JsonRpcError; +use crate::v2::error::JsonRpcError; +use serde::de::Deserializer; +use serde::ser::Serializer; +use serde::{Deserialize, Serialize}; use std::fmt; /// Convenience type for displaying errors. @@ -52,9 +55,9 @@ pub enum Error { /// Subscribe and unsubscribe method names are the same. #[error("Cannot use the same method name for subscribe and unsubscribe, used: {0}")] SubscriptionNameConflict(String), - /// Websocket request timeout - #[error("Websocket request timeout")] - WsRequestTimeout, + /// Request timeout + #[error("Request timeout")] + RequestTimeout, /// Configured max number of request slots exceeded. #[error("Configured max number of request slots exceeded")] MaxSlotsExceeded, diff --git a/types/src/v2/error.rs b/types/src/v2/error.rs index 6b4a8caaf1..75646c9d50 100644 --- a/types/src/v2/error.rs +++ b/types/src/v2/error.rs @@ -1,3 +1,6 @@ +use serde::{de::Deserializer, ser::Serializer, Deserialize, Serialize}; +use serde_json::Value as JsonValue; +use std::fmt; use thiserror::Error; /// Error. @@ -11,6 +14,24 @@ pub enum RpcError { InvalidParams, } +/// [JSON-RPC Error object](https://www.jsonrpc.org/specification#error_object) +#[derive(Error, Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct JsonRpcError { + /// Code + pub code: ErrorCode, + /// Message + pub message: String, + /// Optional data + pub data: Option, +} + +impl fmt::Display for JsonRpcError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}: {}", self.code.to_string(), self.message) + } +} + /// Parse error code. pub const PARSE_ERROR_CODE: i32 = -32700; /// Internal error code. @@ -21,6 +42,10 @@ pub const INVALID_PARAMS_CODE: i32 = -32602; pub const INVALID_REQUEST_CODE: i32 = -32600; /// Method not found error code. pub const METHOD_NOT_FOUND_CODE: i32 = -32601; +/// Reserved for implementation-defined server-errors. +pub const SERVER_ERROR_CODE_RANGE_START: i32 = -32000; +/// Reserved for implementation-defined server-errors. +pub const SERVER_ERROR_CODE_RANGE_END: i32 = 32099; /// Parse error message pub const PARSE_ERROR_MSG: &str = "Parse error"; @@ -32,3 +57,90 @@ pub const INVALID_PARAMS_MSG: &str = "Invalid params"; pub const INVALID_REQUEST_MSG: &str = "Invalid request"; /// Method not found error message. pub const METHOD_NOT_FOUND_MSG: &str = "Method not found"; +/// Reserved for implementation-defined server-errors. +pub const SERVER_ERROR_MSG: &str = "Server error"; +/// Application defined error which is not in the reserved space (-32000..=-32768) +pub const APPLICATION_ERROR_MSG: &str = "Application error"; + +/// JSONRPC error code +#[derive(Error, Debug, PartialEq, Copy, Clone)] +pub enum ErrorCode { + /// Invalid JSON was received by the server. + /// An error occurred on the server while parsing the JSON text. + ParseError, + /// The JSON sent is not a valid Request object. + InvalidRequest, + /// The method does not exist / is not available. + MethodNotFound, + /// Invalid method parameter(s). + InvalidParams, + /// Internal JSON-RPC error. + InternalError, + /// Reserved for implementation-defined server-errors. + ServerError(i32), + /// Application defined error which is not in the reserved space (-32000..=-32768) + ApplicationError(i32), +} + +impl ErrorCode { + /// Returns integer code value + pub fn code(&self) -> i32 { + match *self { + ErrorCode::ParseError => PARSE_ERROR_CODE, + ErrorCode::InvalidRequest => INVALID_REQUEST_CODE, + ErrorCode::MethodNotFound => METHOD_NOT_FOUND_CODE, + ErrorCode::InvalidParams => INVALID_PARAMS_CODE, + ErrorCode::InternalError => INTERNAL_ERROR_CODE, + ErrorCode::ServerError(code) => code, + ErrorCode::ApplicationError(code) => code, + } + } +} + +impl fmt::Display for ErrorCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let err = match *self { + ErrorCode::ParseError => PARSE_ERROR_MSG, + ErrorCode::InvalidRequest => INVALID_REQUEST_MSG, + ErrorCode::MethodNotFound => METHOD_NOT_FOUND_MSG, + ErrorCode::InvalidParams => INVALID_PARAMS_MSG, + ErrorCode::InternalError => INTERNAL_ERROR_MSG, + ErrorCode::ServerError(_) => SERVER_ERROR_MSG, + ErrorCode::ApplicationError(_) => APPLICATION_ERROR_MSG, + }; + f.write_str(err) + } +} + +impl From for ErrorCode { + fn from(code: i32) -> Self { + match code { + PARSE_ERROR_CODE => ErrorCode::ParseError, + INVALID_REQUEST_CODE => ErrorCode::InvalidRequest, + METHOD_NOT_FOUND_CODE => ErrorCode::MethodNotFound, + INVALID_PARAMS_CODE => ErrorCode::InvalidParams, + INTERNAL_ERROR_CODE => ErrorCode::InternalError, + SERVER_ERROR_CODE_RANGE_START..=SERVER_ERROR_CODE_RANGE_END => ErrorCode::ServerError(code), + code => ErrorCode::ApplicationError(code), + } + } +} + +impl<'a> serde::Deserialize<'a> for ErrorCode { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'a>, + { + let code: i32 = serde::Deserialize::deserialize(deserializer)?; + Ok(ErrorCode::from(code)) + } +} + +impl serde::Serialize for ErrorCode { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_i32(self.code()) + } +} diff --git a/ws-client/src/client.rs b/ws-client/src/client.rs index 5e4113f882..719763ad56 100644 --- a/ws-client/src/client.rs +++ b/ws-client/src/client.rs @@ -118,7 +118,7 @@ impl RequestIdGuard { if self.current_pending.load(Ordering::Relaxed) >= self.max_concurrent_requests { Err(Error::MaxSlotsExceeded) } else { - // NOTE: `fetch_add` wraps on overflow but can't occur because `current_pending` is checked above. + // NOTE: `fetch_add` wraps on overflow but that can't occur because `current_pending` is checked above. self.current_pending.fetch_add(1, Ordering::Relaxed); Ok(()) } @@ -326,7 +326,7 @@ impl Client for WsClient { futures::pin_mut!(send_back_rx, timeout); match future::select(send_back_rx, timeout).await { future::Either::Left((send_back_rx_out, _)) => send_back_rx_out, - future::Either::Right((_, _)) => Ok(Err(Error::WsRequestTimeout)), + future::Either::Right((_, _)) => Ok(Err(Error::RequestTimeout)), } } else { send_back_rx.await diff --git a/ws-client/src/tests.rs b/ws-client/src/tests.rs index 18bdc28822..ace5954c82 100644 --- a/ws-client/src/tests.rs +++ b/ws-client/src/tests.rs @@ -131,7 +131,7 @@ async fn run_batch_request_with_response<'a, T: Serialize + std::fmt::Debug + Se async fn run_request_with_response(response: String) -> Result { let server = WebSocketTestServer::with_hardcoded_response("127.0.0.1:0".parse().unwrap(), response).await; - let uri = format!("http://{}", server.local_addr()); + let uri = format!("ws://{}", server.local_addr()); let client = WsClientBuilder::default().build(&uri).await.unwrap(); client.request::("say_hello", JsonRpcParams::NoParams).await } From c1094c38719c7ad7c9eee7ca85b9c2031874abde Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 15 Apr 2021 18:34:59 +0200 Subject: [PATCH 14/41] [ws client]: generate two request IDs for subscrib --- jsonrpsee/tests/integration_tests.rs | 2 +- types/src/client.rs | 10 +++-- ws-client/src/client.rs | 62 +++++++++++++++++----------- ws-client/src/manager.rs | 59 +++++++++++++++----------- 4 files changed, 79 insertions(+), 54 deletions(-) diff --git a/jsonrpsee/tests/integration_tests.rs b/jsonrpsee/tests/integration_tests.rs index d06e8977a8..9284bc9e59 100644 --- a/jsonrpsee/tests/integration_tests.rs +++ b/jsonrpsee/tests/integration_tests.rs @@ -119,7 +119,7 @@ async fn ws_subscription_several_clients_with_drop() { drop(foo_sub); // Send this request to make sure that the client's background thread hasn't // been canceled. - let _r: String = client.request("say_hello", JsonRpcParams::NoParams::).await.unwrap(); + assert!(client.is_connected()); drop(client); } diff --git a/types/src/client.rs b/types/src/client.rs index be7b6af156..58c00ea083 100644 --- a/types/src/client.rs +++ b/types/src/client.rs @@ -23,7 +23,7 @@ pub struct BatchMessage { /// Serialized batch request. pub raw: String, /// Request IDs. - pub raw_ids: Vec, + pub ids: Vec, /// One-shot channel over which we send back the result of this request. pub send_back: oneshot::Sender, Error>>, } @@ -34,7 +34,7 @@ pub struct RequestMessage { /// Serialized message. pub raw: String, /// Request ID. - pub raw_id: u64, + pub id: u64, /// One-shot channel over which we send back the result of this request. pub send_back: Option>>, } @@ -44,8 +44,10 @@ pub struct RequestMessage { pub struct SubscriptionMessage { /// Serialized message. pub raw: String, - /// Request ID of the serialized message. - pub raw_id: u64, + /// Request ID of the subscribe message. + pub subscribe_id: u64, + /// Request ID of the unsubscribe message. + pub unsubscribe_id: u64, /// Method to use to unsubscribe later. Used if the channel unexpectedly closes. pub unsubscribe_method: String, /// If the subscription succeeds, we return a [`mpsc::Receiver`] that will receive notifications. diff --git a/ws-client/src/client.rs b/ws-client/src/client.rs index 719763ad56..5e24e74826 100644 --- a/ws-client/src/client.rs +++ b/ws-client/src/client.rs @@ -133,10 +133,10 @@ impl RequestIdGuard { Ok(id) } - /// Attempts to get the next batch IDs that only counts as one request.. + /// Attempts to get the `n` number next IDs that only counts as one request. /// /// Fails if request limit has been exceeded. - fn next_batch_ids(&self, len: usize) -> Result, Error> { + fn next_request_ids(&self, len: usize) -> Result, Error> { self.get_slot()?; let mut batch = Vec::with_capacity(len); for _ in 0..len { @@ -313,7 +313,7 @@ impl Client for WsClient { if self .to_back .clone() - .send(FrontToBack::Request(RequestMessage { raw, raw_id: req_id, send_back: Some(send_back_tx) })) + .send(FrontToBack::Request(RequestMessage { raw, id: req_id, send_back: Some(send_back_tx) })) .await .is_err() { @@ -346,7 +346,7 @@ impl Client for WsClient { T: Serialize + std::fmt::Debug + Send + Sync, R: DeserializeOwned + Default + Clone, { - let batch_ids = self.id_guard.next_batch_ids(batch.len())?; + let batch_ids = self.id_guard.next_request_ids(batch.len())?; let mut batches = Vec::with_capacity(batch.len()); for (idx, (method, params)) in batch.into_iter().enumerate() { @@ -360,7 +360,7 @@ impl Client for WsClient { if self .to_back .clone() - .send(FrontToBack::Batch(BatchMessage { raw, raw_ids: batch_ids, send_back: send_back_tx })) + .send(FrontToBack::Batch(BatchMessage { raw, ids: batch_ids, send_back: send_back_tx })) .await .is_err() { @@ -405,9 +405,9 @@ impl SubscriptionClient for WsClient { return Err(Error::SubscriptionNameConflict(unsub_method)); } - let req_id = self.id_guard.next_request_id()?; + let ids = self.id_guard.next_request_ids(2)?; let raw = - serde_json::to_string(&JsonRpcCall::new(req_id, subscribe_method, params)).map_err(Error::ParseError)?; + serde_json::to_string(&JsonRpcCall::new(ids[0], subscribe_method, params)).map_err(Error::ParseError)?; let (send_back_tx, send_back_rx) = oneshot::channel(); if self @@ -415,7 +415,8 @@ impl SubscriptionClient for WsClient { .clone() .send(FrontToBack::Subscribe(SubscriptionMessage { raw, - raw_id: req_id, + subscribe_id: ids[0], + unsubscribe_id: ids[1], unsubscribe_method: unsub_method, send_back: send_back_tx, })) @@ -467,15 +468,15 @@ async fn background_task( Either::Left((Some(FrontToBack::Batch(batch)), _)) => { log::trace!("[backend]: client prepares to send batch request: {:?}", batch.raw); // NOTE(niklasad1): annoying allocation. - if let Err(send_back) = manager.insert_pending_batch(batch.raw_ids.clone(), batch.send_back) { - log::warn!("[backend]: batch request: {:?} already pending", batch.raw_ids); + if let Err(send_back) = manager.insert_pending_batch(batch.ids.clone(), batch.send_back) { + log::warn!("[backend]: batch request: {:?} already pending", batch.ids); let _ = send_back.send(Err(Error::InvalidRequestId)); continue; } if let Err(e) = sender.send(batch.raw).await { log::warn!("[backend]: client batch request failed: {:?}", e); - manager.complete_pending_batch(batch.raw_ids); + manager.complete_pending_batch(batch.ids); } } // User called `notification` on the front-end @@ -491,7 +492,7 @@ async fn background_task( log::trace!("[backend]: client prepares to send request={:?}", request); match sender.send(request.raw).await { Ok(_) => manager - .insert_pending_call(request.raw_id, request.send_back) + .insert_pending_call(request.id, request.send_back) .expect("ID unused checked above; qed"), Err(e) => { log::warn!("[backend]: client request failed: {:?}", e); @@ -503,7 +504,12 @@ async fn background_task( // User called `subscribe` on the front-end. Either::Left((Some(FrontToBack::Subscribe(sub)), _)) => match sender.send(sub.raw).await { Ok(_) => manager - .insert_pending_subscription(sub.raw_id, sub.send_back, sub.unsubscribe_method) + .insert_pending_subscription( + sub.subscribe_id, + sub.unsubscribe_id, + sub.send_back, + sub.unsubscribe_method, + ) .expect("Request ID unused checked above; qed"), Err(e) => { log::warn!("[backend]: client subscription failed: {:?}", e); @@ -519,7 +525,7 @@ async fn background_task( .get_request_id_by_subscription_id(&sub_id) .and_then(|req_id| build_unsubscribe_message(&mut manager, req_id, sub_id)) { - stop_subscription(&mut sender, unsub).await; + stop_subscription(&mut sender, &mut manager, unsub).await; } } Either::Right((Some(Ok(raw)), _)) => { @@ -533,7 +539,7 @@ async fn background_task( log::debug!("[backend]: recv method_call {:?}", call); match process_response(&mut manager, call, max_notifs_per_subscription) { Ok(Some(unsub)) => { - stop_subscription(&mut sender, unsub).await; + stop_subscription(&mut sender, &mut manager, unsub).await; } Ok(None) => (), Err(err) => { @@ -587,9 +593,9 @@ async fn background_task( Some(send_back_sink) => { if let Err(e) = send_back_sink.try_send(notif.params.result) { log::error!("Dropping subscription {:?} error: {:?}", sub_id, e); - let unsub_req = build_unsubscribe_message(&mut manager, request_id, sub_id) + let unsub_msg = build_unsubscribe_message(&mut manager, request_id, sub_id) .expect("request ID and subscription ID valid checked above; qed"); - stop_subscription(&mut sender, unsub_req).await; + stop_subscription(&mut sender, &mut manager, unsub_msg).await; } } None => { @@ -634,7 +640,7 @@ fn process_response( Ok(None) } RequestStatus::PendingSubscription => { - let (send_back_oneshot, unsubscribe_method) = + let (unsub_id, send_back_oneshot, unsubscribe_method) = manager.complete_pending_subscription(response.id).ok_or(Error::InvalidRequestId)?; let sub_id: SubscriptionId = match serde_json::from_value(response.result) { @@ -647,7 +653,10 @@ fn process_response( let response_id = response.id; let (subscribe_tx, subscribe_rx) = mpsc::channel(max_capacity_per_subscription); - if manager.insert_subscription(response_id, sub_id.clone(), subscribe_tx, unsubscribe_method).is_ok() { + if manager + .insert_subscription(response_id, unsub_id, sub_id.clone(), subscribe_tx, unsubscribe_method) + .is_ok() + { match send_back_oneshot.send(Ok((subscribe_rx, sub_id.clone()))) { Ok(_) => Ok(None), Err(_) => Ok(build_unsubscribe_message(manager, response_id, sub_id)), @@ -665,21 +674,26 @@ fn process_response( /// that the client is not interested in the subscription anymore. // // NOTE: we don't count this a concurrent request as it's part of a subscription. -async fn stop_subscription(sender: &mut WsSender, unsub: RequestMessage) { +async fn stop_subscription(sender: &mut WsSender, manager: &mut RequestManager, unsub: RequestMessage) { if let Err(e) = sender.send(unsub.raw).await { log::error!("Send unsubscribe request failed: {:?}", e); + let _ = manager.complete_pending_call(unsub.id); } } /// Builds an unsubscription message, semantically the same as an ordinary request. fn build_unsubscribe_message( manager: &mut RequestManager, - req_id: u64, + sub_req_id: u64, sub_id: SubscriptionId, ) -> Option { - let (_, unsub, sub_id) = manager.remove_subscription(req_id, sub_id)?; + let (unsub_req_id, _, unsub, sub_id) = manager.remove_subscription(sub_req_id, sub_id)?; + if manager.insert_pending_call(unsub_req_id, None).is_err() { + log::warn!("Unsubscribe message failed to get slot in the RequestManager"); + return None; + } // TODO(niklasad): better type for params or maybe a macro?!. let params: JsonRpcParams<_> = vec![&sub_id].into(); - let raw = serde_json::to_string(&JsonRpcCall::new(req_id, &unsub, params)).unwrap(); - Some(RequestMessage { raw, raw_id: req_id, send_back: None }) + let raw = serde_json::to_string(&JsonRpcCall::new(unsub_req_id, &unsub, params)).unwrap(); + Some(RequestMessage { raw, id: unsub_req_id, send_back: None }) } diff --git a/ws-client/src/manager.rs b/ws-client/src/manager.rs index f67d303dd3..041758a604 100644 --- a/ws-client/src/manager.rs +++ b/ws-client/src/manager.rs @@ -15,8 +15,8 @@ use std::collections::hash_map::{Entry, HashMap}; #[derive(Debug)] enum Kind { PendingMethodCall(PendingCallOneshot), - PendingSubscription((PendingSubscriptionOneshot, UnsubscribeMethod)), - Subscription((SubscriptionSink, UnsubscribeMethod)), + PendingSubscription((RequestId, PendingSubscriptionOneshot, UnsubscribeMethod)), + Subscription((RequestId, SubscriptionSink, UnsubscribeMethod)), } #[derive(Debug)] @@ -107,12 +107,13 @@ impl RequestManager { /// Returns `Ok` if the pending request was successfully inserted otherwise `Err`. pub fn insert_pending_subscription( &mut self, - id: RequestId, + sub_req_id: RequestId, + unsub_req_id: RequestId, send_back: PendingSubscriptionOneshot, unsubscribe_method: UnsubscribeMethod, ) -> Result<(), PendingSubscriptionOneshot> { - if let Entry::Vacant(v) = self.requests.entry(id) { - v.insert(Kind::PendingSubscription((send_back, unsubscribe_method))); + if let Entry::Vacant(v) = self.requests.entry(sub_req_id) { + v.insert(Kind::PendingSubscription((unsub_req_id, send_back, unsubscribe_method))); Ok(()) } else { Err(send_back) @@ -124,16 +125,17 @@ impl RequestManager { /// Returns `Ok` if the pending request was successfully inserted otherwise `Err`. pub fn insert_subscription( &mut self, - request_id: RequestId, + sub_req_id: RequestId, + unsub_req_id: RequestId, subscription_id: SubscriptionId, send_back: SubscriptionSink, unsubscribe_method: UnsubscribeMethod, ) -> Result<(), SubscriptionSink> { if let (Entry::Vacant(request), Entry::Vacant(subscription)) = - (self.requests.entry(request_id), self.subscriptions.entry(subscription_id)) + (self.requests.entry(sub_req_id), self.subscriptions.entry(subscription_id)) { - request.insert(Kind::Subscription((send_back, unsubscribe_method))); - subscription.insert(request_id); + request.insert(Kind::Subscription((unsub_req_id, send_back, unsubscribe_method))); + subscription.insert(sub_req_id); Ok(()) } else { Err(send_back) @@ -146,7 +148,7 @@ impl RequestManager { pub fn complete_pending_subscription( &mut self, request_id: RequestId, - ) -> Option<(PendingSubscriptionOneshot, UnsubscribeMethod)> { + ) -> Option<(RequestId, PendingSubscriptionOneshot, UnsubscribeMethod)> { match self.requests.entry(request_id) { Entry::Occupied(request) if matches!(request.get(), Kind::PendingSubscription(_)) => { let (_req_id, kind) = request.remove_entry(); @@ -197,15 +199,15 @@ impl RequestManager { &mut self, request_id: RequestId, subscription_id: SubscriptionId, - ) -> Option<(SubscriptionSink, UnsubscribeMethod, SubscriptionId)> { + ) -> Option<(RequestId, SubscriptionSink, UnsubscribeMethod, SubscriptionId)> { match (self.requests.entry(request_id), self.subscriptions.entry(subscription_id)) { (Entry::Occupied(request), Entry::Occupied(subscription)) if matches!(request.get(), Kind::Subscription(_)) => { let (_req_id, kind) = request.remove_entry(); let (sub_id, _req_id) = subscription.remove_entry(); - if let Kind::Subscription((send_back, unsub)) = kind { - Some((send_back, unsub, sub_id)) + if let Kind::Subscription((unsub_req_id, send_back, unsub)) = kind { + Some((unsub_req_id, send_back, unsub, sub_id)) } else { unreachable!("Subscription is Subscription checked above; qed"); } @@ -227,7 +229,7 @@ impl RequestManager { /// /// Returns `Some` if the `request_id` was registered as a subscription otherwise `None`. pub fn as_subscription_mut(&mut self, request_id: &RequestId) -> Option<&mut SubscriptionSink> { - if let Some(Kind::Subscription((sink, _))) = self.requests.get_mut(request_id) { + if let Some(Kind::Subscription((_, sink, _))) = self.requests.get_mut(request_id) { Some(sink) } else { None @@ -263,10 +265,17 @@ mod tests { let (pending_sub_tx, _) = oneshot::channel::, SubscriptionId), Error>>(); let (sub_tx, _) = mpsc::channel::(1); let mut manager = RequestManager::new(); - assert!(manager.insert_pending_subscription(1, pending_sub_tx, "unsubscribe_method".into()).is_ok()); - let (_send_back_oneshot, unsubscribe_method) = manager.complete_pending_subscription(1).unwrap(); + assert!(manager.insert_pending_subscription(1, 2, pending_sub_tx, "unsubscribe_method".into()).is_ok()); + let (unsub_req_id, _send_back_oneshot, unsubscribe_method) = manager.complete_pending_subscription(1).unwrap(); + assert_eq!(unsub_req_id, 2); assert!(manager - .insert_subscription(1, SubscriptionId::Str("uniq_id_from_server".to_string()), sub_tx, unsubscribe_method) + .insert_subscription( + 1, + 2, + SubscriptionId::Str("uniq_id_from_server".to_string()), + sub_tx, + unsubscribe_method + ) .is_ok()); assert!(manager.as_subscription_mut(&1).is_some()); @@ -283,8 +292,8 @@ mod tests { let mut manager = RequestManager::new(); assert!(manager.insert_pending_call(0, Some(request_tx1)).is_ok()); assert!(manager.insert_pending_call(0, Some(request_tx2)).is_err()); - assert!(manager.insert_pending_subscription(0, pending_sub_tx, "beef".to_string()).is_err()); - assert!(manager.insert_subscription(0, SubscriptionId::Num(137), sub_tx, "bibimbap".to_string()).is_err()); + assert!(manager.insert_pending_subscription(0, 1, pending_sub_tx, "beef".to_string()).is_err()); + assert!(manager.insert_subscription(0, 99, SubscriptionId::Num(137), sub_tx, "bibimbap".to_string()).is_err()); assert!(manager.remove_subscription(0, SubscriptionId::Num(137)).is_none()); assert!(manager.complete_pending_subscription(0).is_none()); @@ -299,11 +308,11 @@ mod tests { let (sub_tx, _) = mpsc::channel::(1); let mut manager = RequestManager::new(); - assert!(manager.insert_pending_subscription(99, pending_sub_tx1, "beef".to_string()).is_ok()); + assert!(manager.insert_pending_subscription(99, 100, pending_sub_tx1, "beef".to_string()).is_ok()); assert!(manager.insert_pending_call(99, Some(request_tx)).is_err()); - assert!(manager.insert_pending_subscription(99, pending_sub_tx2, "vegan".to_string()).is_err()); + assert!(manager.insert_pending_subscription(99, 1337, pending_sub_tx2, "vegan".to_string()).is_err()); - assert!(manager.insert_subscription(99, SubscriptionId::Num(0), sub_tx, "bibimbap".to_string()).is_err()); + assert!(manager.insert_subscription(99, 100, SubscriptionId::Num(0), sub_tx, "bibimbap".to_string()).is_err()); assert!(manager.remove_subscription(99, SubscriptionId::Num(0)).is_none()); assert!(manager.complete_pending_call(99).is_none()); @@ -319,9 +328,9 @@ mod tests { let mut manager = RequestManager::new(); - assert!(manager.insert_subscription(3, SubscriptionId::Num(0), sub_tx1, "bibimbap".to_string()).is_ok()); - assert!(manager.insert_subscription(3, SubscriptionId::Num(1), sub_tx2, "bibimbap".to_string()).is_err()); - assert!(manager.insert_pending_subscription(3, pending_sub_tx, "beef".to_string()).is_err()); + assert!(manager.insert_subscription(3, 4, SubscriptionId::Num(0), sub_tx1, "bibimbap".to_string()).is_ok()); + assert!(manager.insert_subscription(3, 4, SubscriptionId::Num(1), sub_tx2, "bibimbap".to_string()).is_err()); + assert!(manager.insert_pending_subscription(3, 4, pending_sub_tx, "beef".to_string()).is_err()); assert!(manager.insert_pending_call(3, Some(request_tx)).is_err()); assert!(manager.remove_subscription(3, SubscriptionId::Num(7)).is_none()); From 16e4a9185aa97cac6d708a2be99e83d021839fa1 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 15 Apr 2021 21:41:01 +0200 Subject: [PATCH 15/41] fix tests --- http-client/src/client.rs | 8 +++++++- http-client/src/tests.rs | 15 +++++++-------- types/src/v2/dummy.rs | 11 ++++++++++- types/src/v2/error.rs | 19 +++++++++++++++++-- types/src/v2/mod.rs | 2 +- ws-client/src/client.rs | 22 +++++++++++++++++++++- ws-client/src/tests.rs | 16 ++++++++-------- 7 files changed, 71 insertions(+), 22 deletions(-) diff --git a/http-client/src/client.rs b/http-client/src/client.rs index 43b69e8a5d..eefa9c1681 100644 --- a/http-client/src/client.rs +++ b/http-client/src/client.rs @@ -118,7 +118,13 @@ impl Client for HttpClient { .await .map_err(|e| Error::TransportError(Box::new(e)))?; - let response = serde_json::from_slice(&body).map_err(Error::ParseError)?; + let response = match serde_json::from_slice(&body) { + Ok(response) => response, + Err(_) => { + let err: JsonRpcError = serde_json::from_slice(&body).map_err(Error::ParseError)?; + return Err(Error::Request(err)); + } + }; match response { JsonRpcResponse::Single(_response) => Err(invalid_response(BATCH_RESPONSE, SINGLE_RESPONSE)), diff --git a/http-client/src/tests.rs b/http-client/src/tests.rs index c0fa786c49..5290438cbe 100644 --- a/http-client/src/tests.rs +++ b/http-client/src/tests.rs @@ -4,7 +4,7 @@ use jsonrpsee_test_utils::types::Id; use jsonrpsee_types::{ error::Error, traits::Client, - v2::{dummy::JsonRpcParams, error::*}, + v2::{dummy::JsonRpcParams, error::*, TwoPointZero}, }; use serde::Serialize; use serde_json::Value as JsonValue; @@ -113,13 +113,12 @@ async fn run_request_with_response(response: String) -> Result client.request::("say_hello", JsonRpcParams::NoParams).await } -fn assert_jsonrpc_error_response(response: Error, code: i32, message: &str) { - panic!("err: {:?}", response); - /*let expected = jsonrpc::Error { code, message, data: None }; - match response { +fn assert_jsonrpc_error_response(error: Error, code: i32, message: &str) { + match &error { Error::Request(err) => { - assert_eq!(err, expected); + assert_eq!(err.inner.code.code(), code); + assert_eq!(&err.inner.message, message); } - e @ _ => panic!("Expected error: \"{}\", got: {:?}", expected, e), - };*/ + e @ _ => panic!("Expected error: \"{}\", got: {:?}", error, e), + }; } diff --git a/types/src/v2/dummy.rs b/types/src/v2/dummy.rs index d2ae194359..4e9a37faf0 100644 --- a/types/src/v2/dummy.rs +++ b/types/src/v2/dummy.rs @@ -1,6 +1,6 @@ //! Client side JSON-RPC types. -use crate::v2::TwoPointZero; +use crate::v2::{error::JsonRpcError, TwoPointZero}; use alloc::collections::BTreeMap; use serde::{Deserialize, Serialize}; @@ -186,3 +186,12 @@ pub enum SubscriptionId { /// String id Str(String), } + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::Value; + + #[test] + fn deser_error() {} +} diff --git a/types/src/v2/error.rs b/types/src/v2/error.rs index dc6d6bca9d..f0b7c44562 100644 --- a/types/src/v2/error.rs +++ b/types/src/v2/error.rs @@ -1,3 +1,4 @@ +use super::TwoPointZero; use serde::{de::Deserializer, ser::Serializer, Deserialize}; use serde_json::Value as JsonValue; use std::fmt; @@ -14,10 +15,24 @@ pub enum RpcError { InvalidParams, } +#[derive(Error, Debug, Deserialize, PartialEq)] +pub struct JsonRpcError { + pub jsonrpc: TwoPointZero, + #[serde(rename = "error")] + pub inner: JsonRpcErrorObject, + pub id: u64, +} + +impl fmt::Display for JsonRpcError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.inner) + } +} + /// [JSON-RPC Error object](https://www.jsonrpc.org/specification#error_object) #[derive(Error, Debug, PartialEq, Deserialize)] #[serde(deny_unknown_fields)] -pub struct JsonRpcError { +pub struct JsonRpcErrorObject { /// Code pub code: ErrorCode, /// Message @@ -26,7 +41,7 @@ pub struct JsonRpcError { pub data: Option, } -impl fmt::Display for JsonRpcError { +impl fmt::Display for JsonRpcErrorObject { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}: {}", self.code.to_string(), self.message) } diff --git a/types/src/v2/mod.rs b/types/src/v2/mod.rs index 64e076de4f..de4cb27b55 100644 --- a/types/src/v2/mod.rs +++ b/types/src/v2/mod.rs @@ -93,7 +93,7 @@ pub struct JsonRpcErrorParams<'a> { } /// JSON-RPC v2 marker type. -#[derive(Debug, Default)] +#[derive(Debug, Default, PartialEq)] pub struct TwoPointZero; struct TwoPointZeroVisitor; diff --git a/ws-client/src/client.rs b/ws-client/src/client.rs index 5e24e74826..c57b012d99 100644 --- a/ws-client/src/client.rs +++ b/ws-client/src/client.rs @@ -41,6 +41,7 @@ use jsonrpsee_types::{ v2::dummy::{ JsonRpcCall, JsonRpcNotification, JsonRpcParams, JsonRpcResponse, JsonRpcResponseObject, SubscriptionId, }, + v2::error::JsonRpcError, }; use serde::{de::DeserializeOwned, Serialize}; use serde_json::Value as JsonValue; @@ -531,7 +532,26 @@ async fn background_task( Either::Right((Some(Ok(raw)), _)) => { let response: JsonRpcResponse = match serde_json::from_slice(&raw) { Ok(response) => response, - Err(_e) => continue, + Err(_) => { + let err: Result = serde_json::from_slice(&raw).map_err(Error::ParseError); + if let Ok(err) = err { + match manager.request_status(&err.id) { + RequestStatus::PendingMethodCall => { + let send_back = + manager.complete_pending_call(err.id).expect("State checked above; qed"); + let _ = send_back.map(|s| s.send(Err(Error::Request(err)))); + } + RequestStatus::PendingSubscription => { + let (_, send_back, _) = manager + .complete_pending_subscription(err.id) + .expect("State checked above; qed"); + let _ = send_back.send(Err(Error::Request(err))); + } + _ => (), + } + } + continue; + } }; match response { diff --git a/ws-client/src/tests.rs b/ws-client/src/tests.rs index 38d31651e6..c4a2e29205 100644 --- a/ws-client/src/tests.rs +++ b/ws-client/src/tests.rs @@ -24,7 +24,7 @@ async fn notif_works() { #[tokio::test] async fn response_with_wrong_id() { let err = run_request_with_response(ok_response("hello".into(), Id::Num(99))).await.unwrap_err(); - assert!(matches!(err, Error::InvalidRequestId)); + assert!(matches!(err, Error::RestartNeeded(_))); } #[tokio::test] @@ -131,12 +131,12 @@ async fn run_request_with_response(response: String) -> Result client.request::("say_hello", JsonRpcParams::NoParams).await } -fn assert_error_response(response: Error, code: i32, message: &str) { - todo!(); - /*match response { - Err(Error::Request(err)) => { - assert_eq!(err, expected); +fn assert_error_response(error: Error, code: i32, message: &str) { + match &error { + Error::Request(err) => { + assert_eq!(err.inner.code.code(), code); + assert_eq!(&err.inner.message, message); } - e @ _ => panic!("Expected error: \"{}\", got: {:?}", expected, e), - };*/ + e @ _ => panic!("Expected error: \"{}\", got: {:?}", error, e), + }; } From 32444b17a7a2c6bc7c24d293074c16845dae0e76 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 16 Apr 2021 08:53:50 +0200 Subject: [PATCH 16/41] remove unused types + less alloc for params. --- benches/bench.rs | 6 ++--- examples/http.rs | 2 +- http-client/src/lib.rs | 2 +- http-client/src/tests.rs | 6 ++--- types/src/v2/dummy.rs | 53 ++++++---------------------------------- ws-client/src/client.rs | 3 ++- ws-client/src/lib.rs | 2 +- ws-client/src/tests.rs | 4 +-- 8 files changed, 20 insertions(+), 58 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index 821119e59c..34edd6c8c9 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -1,6 +1,6 @@ use criterion::*; use jsonrpsee::{ - http_client::{jsonrpc, Client, HttpClientBuilder, JsonRpcCall, JsonRpcParams, JsonRpcRequest}, + http_client::{jsonrpc, Client, HttpClientBuilder, JsonRpcCall, JsonRpcParams}, ws_client::WsClientBuilder, }; use std::sync::Arc; @@ -15,7 +15,7 @@ fn v1_serialize(req: jsonrpc::Request) -> String { serde_json::to_string(&req).unwrap() } -fn v2_serialize(req: JsonRpcRequest) -> String { +fn v2_serialize(req: JsonRpcCall) -> String { serde_json::to_string(&req).unwrap() } @@ -37,7 +37,7 @@ pub fn jsonrpsee_types_v2(crit: &mut Criterion) { crit.bench_function("jsonrpsee_types_v2", |b| { b.iter(|| { let params: JsonRpcParams<_> = vec![&1, &2].into(); - let request = JsonRpcRequest::Single(JsonRpcCall::new(0, "say_hello", params)); + let request = JsonRpcCall::new(0, "say_hello", params); v2_serialize(request); }) }); diff --git a/examples/http.rs b/examples/http.rs index aa36d5707d..d4880e8943 100644 --- a/examples/http.rs +++ b/examples/http.rs @@ -37,7 +37,7 @@ async fn main() -> anyhow::Result<()> { let server_addr = run_server().await?; let url = format!("http://{}", server_addr); - let params: JsonRpcParams<_> = vec![&1_u64, &2, &3].into(); + let params = JsonRpcParams::Array(&[1_u64, 2, 3]); let client = HttpClientBuilder::default().build(url)?; let response: Result = client.request("say_hello", params).await; diff --git a/http-client/src/lib.rs b/http-client/src/lib.rs index 945153b6b5..b9043b1622 100644 --- a/http-client/src/lib.rs +++ b/http-client/src/lib.rs @@ -48,5 +48,5 @@ pub use jsonrpsee_types::{ error::Error, jsonrpc, traits::Client, - v2::dummy::{JsonRpcCall, JsonRpcNotification, JsonRpcParams, JsonRpcRequest}, + v2::dummy::{JsonRpcCall, JsonRpcNotification, JsonRpcParams}, }; diff --git a/http-client/src/tests.rs b/http-client/src/tests.rs index 5290438cbe..ea6d3c2901 100644 --- a/http-client/src/tests.rs +++ b/http-client/src/tests.rs @@ -4,7 +4,7 @@ use jsonrpsee_test_utils::types::Id; use jsonrpsee_types::{ error::Error, traits::Client, - v2::{dummy::JsonRpcParams, error::*, TwoPointZero}, + v2::{dummy::JsonRpcParams, error::*}, }; use serde::Serialize; use serde_json::Value as JsonValue; @@ -76,7 +76,7 @@ async fn subscription_response_to_request() { async fn batch_request_works() { let batch_request = vec![ ("say_hello", JsonRpcParams::NoParams), - ("say_goodbye", JsonRpcParams::Array(vec![&0_u64, &1, &2])), + ("say_goodbye", JsonRpcParams::Array(&[0_u64, 1, 2])), ("get_swag", JsonRpcParams::NoParams), ]; 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(); @@ -88,7 +88,7 @@ async fn batch_request_works() { async fn batch_request_out_of_order_response() { let batch_request = vec![ ("say_hello", JsonRpcParams::NoParams), - ("say_goodbye", JsonRpcParams::Array(vec![&0_u64, &1, &2])), + ("say_goodbye", JsonRpcParams::Array(&[0_u64, 1, 2])), ("get_swag", JsonRpcParams::NoParams), ]; 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(); diff --git a/types/src/v2/dummy.rs b/types/src/v2/dummy.rs index 4e9a37faf0..b44e058773 100644 --- a/types/src/v2/dummy.rs +++ b/types/src/v2/dummy.rs @@ -1,6 +1,6 @@ //! Client side JSON-RPC types. -use crate::v2::{error::JsonRpcError, TwoPointZero}; +use crate::v2::TwoPointZero; use alloc::collections::BTreeMap; use serde::{Deserialize, Serialize}; @@ -14,8 +14,7 @@ where /// No params. NoParams, /// Positional params. - // TODO(niklasad1): maybe smallvec here?! - Array(Vec<&'a T>), + Array(&'a [T]), /// Params by name. Map(BTreeMap<&'a str, &'a T>), } @@ -39,56 +38,15 @@ where } } -impl<'a, T> From> for JsonRpcParams<'a, T> +impl<'a, T> From<&'a [T]> for JsonRpcParams<'a, T> where T: Serialize + std::fmt::Debug, { - fn from(arr: Vec<&'a T>) -> Self { + fn from(arr: &'a [T]) -> Self { Self::Array(arr) } } -/// Serializable JSON-RPC request object which may be a notification, method call or batch. -#[derive(Serialize, Debug)] -#[serde(untagged)] -pub enum JsonRpcRequest<'a, T> -where - T: Serialize + std::fmt::Debug + 'a, -{ - /// Single method call. - Single(JsonRpcCall<'a, T>), - /// Batch. - Batch(Vec>), - /// Notification. - Notif(JsonRpcNotification<'a, T>), -} - -impl<'a, T> From> for JsonRpcRequest<'a, T> -where - T: Serialize + std::fmt::Debug, -{ - fn from(call: JsonRpcCall<'a, T>) -> Self { - JsonRpcRequest::Single(call) - } -} -impl<'a, T> From>> for JsonRpcRequest<'a, T> -where - T: Serialize + std::fmt::Debug, -{ - fn from(batch: Vec>) -> Self { - JsonRpcRequest::Batch(batch) - } -} - -impl<'a, T> From> for JsonRpcRequest<'a, T> -where - T: Serialize + std::fmt::Debug, -{ - fn from(notif: JsonRpcNotification<'a, T>) -> Self { - JsonRpcRequest::Notif(notif) - } -} - /// Serializable [JSON-RPC object](https://www.jsonrpc.org/specification#request-object) #[derive(Serialize, Debug)] pub struct JsonRpcCall<'a, T> @@ -158,9 +116,12 @@ pub struct JsonRpcNotificationParams { pub result: T, } +/// JSON-RPC notification response. #[derive(Deserialize, Debug)] pub struct JsonRpcResponseNotif { + /// JSON-RPC version. pub jsonrpc: TwoPointZero, + /// Params. pub params: JsonRpcNotificationParams, } diff --git a/ws-client/src/client.rs b/ws-client/src/client.rs index c57b012d99..ec85dd5f45 100644 --- a/ws-client/src/client.rs +++ b/ws-client/src/client.rs @@ -708,12 +708,13 @@ fn build_unsubscribe_message( sub_id: SubscriptionId, ) -> Option { let (unsub_req_id, _, unsub, sub_id) = manager.remove_subscription(sub_req_id, sub_id)?; + let sub_id_slice = &[sub_id]; if manager.insert_pending_call(unsub_req_id, None).is_err() { log::warn!("Unsubscribe message failed to get slot in the RequestManager"); return None; } // TODO(niklasad): better type for params or maybe a macro?!. - let params: JsonRpcParams<_> = vec![&sub_id].into(); + let params = JsonRpcParams::Array(sub_id_slice); let raw = serde_json::to_string(&JsonRpcCall::new(unsub_req_id, &unsub, params)).unwrap(); Some(RequestMessage { raw, id: unsub_req_id, send_back: None }) } diff --git a/ws-client/src/lib.rs b/ws-client/src/lib.rs index 4590e7a1fd..cdf1987d13 100644 --- a/ws-client/src/lib.rs +++ b/ws-client/src/lib.rs @@ -22,6 +22,6 @@ pub use jsonrpsee_types::{ error::Error, jsonrpc, traits::{Client, SubscriptionClient}, - v2::dummy::{JsonRpcCall, JsonRpcNotification, JsonRpcParams, JsonRpcRequest}, + v2::dummy::{JsonRpcCall, JsonRpcNotification, JsonRpcParams}, v2::error, }; diff --git a/ws-client/src/tests.rs b/ws-client/src/tests.rs index c4a2e29205..5e125be45a 100644 --- a/ws-client/src/tests.rs +++ b/ws-client/src/tests.rs @@ -80,7 +80,7 @@ async fn batch_request_works() { let _ = env_logger::try_init(); let batch_request = vec![ ("say_hello", JsonRpcParams::NoParams), - ("say_goodbye", JsonRpcParams::Array(vec![&0_u64, &1, &2])), + ("say_goodbye", JsonRpcParams::Array(&[0_u64, 1, 2])), ("get_swag", JsonRpcParams::NoParams), ]; 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(); @@ -92,7 +92,7 @@ async fn batch_request_works() { async fn batch_request_out_of_order_response() { let batch_request = vec![ ("say_hello", JsonRpcParams::NoParams), - ("say_goodbye", JsonRpcParams::Array(vec![&0_u64, &1, &2])), + ("say_goodbye", JsonRpcParams::Array(&[0_u64, 1, 2])), ("get_swag", JsonRpcParams::NoParams), ]; 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(); From 0bcb54db1e4a8f6b5d7cc9bea3f8896d8d59fff6 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 16 Apr 2021 09:14:59 +0200 Subject: [PATCH 17/41] fix nits --- benches/bench.rs | 2 +- types/src/v2/dummy.rs | 7 ++++--- types/src/v2/error.rs | 6 +++++- utils/src/hyper_helpers.rs | 3 +-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index 34edd6c8c9..eb148e640d 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -36,7 +36,7 @@ pub fn jsonrpsee_types_v1(crit: &mut Criterion) { pub fn jsonrpsee_types_v2(crit: &mut Criterion) { crit.bench_function("jsonrpsee_types_v2", |b| { b.iter(|| { - let params: JsonRpcParams<_> = vec![&1, &2].into(); + let params = JsonRpcParams::Array(&[1, 2]); let request = JsonRpcCall::new(0, "say_hello", params); v2_serialize(request); }) diff --git a/types/src/v2/dummy.rs b/types/src/v2/dummy.rs index b44e058773..d49636edd0 100644 --- a/types/src/v2/dummy.rs +++ b/types/src/v2/dummy.rs @@ -16,10 +16,12 @@ where /// Positional params. Array(&'a [T]), /// Params by name. + // + // TODO(niklasad1): maybe take a reference here but BTreeMap needs allocation anyway. Map(BTreeMap<&'a str, &'a T>), } -// FIXME: this is a little weird but nice if `None.into()` works. +// TODO(niklasad1): this is a little weird but nice if `None.into()` works. impl<'a, T> From> for JsonRpcParams<'a, T> where T: Serialize + std::fmt::Debug, @@ -97,6 +99,7 @@ where } } +/// [Successful JSON-RPC object](https://www.jsonrpc.org/specification#response_object). #[derive(Deserialize, Debug)] pub struct JsonRpcResponseObject { /// JSON-RPC version. @@ -150,8 +153,6 @@ pub enum SubscriptionId { #[cfg(test)] mod tests { - use super::*; - use serde_json::Value; #[test] fn deser_error() {} diff --git a/types/src/v2/error.rs b/types/src/v2/error.rs index f0b7c44562..1b85f82818 100644 --- a/types/src/v2/error.rs +++ b/types/src/v2/error.rs @@ -15,11 +15,15 @@ pub enum RpcError { InvalidParams, } +/// [Failed JSON-RPC response object](https://www.jsonrpc.org/specification#response_object). #[derive(Error, Debug, Deserialize, PartialEq)] pub struct JsonRpcError { + /// JSON-RPC version. pub jsonrpc: TwoPointZero, #[serde(rename = "error")] + /// Error object. pub inner: JsonRpcErrorObject, + /// Request ID. pub id: u64, } @@ -33,7 +37,7 @@ impl fmt::Display for JsonRpcError { #[derive(Error, Debug, PartialEq, Deserialize)] #[serde(deny_unknown_fields)] pub struct JsonRpcErrorObject { - /// Code + /// Error code pub code: ErrorCode, /// Message pub message: String, diff --git a/utils/src/hyper_helpers.rs b/utils/src/hyper_helpers.rs index 91338fd575..0d1acb535e 100644 --- a/utils/src/hyper_helpers.rs +++ b/utils/src/hyper_helpers.rs @@ -38,7 +38,7 @@ pub async fn read_response_to_body( mut body: hyper::Body, max_request_body_size: u32, ) -> Result, GenericTransportError> { - // NOTE(niklasad1): Values bigger than `u32::MAX` will be turned into zero here. This is unlikely to occur in practise + // NOTE(niklasad1): Values bigger than `u32::MAX` will be turned into zero here. This is unlikely to occur in practice // and for that case we fallback to allocating in the while-loop below instead of pre-allocating. let body_size = read_header_content_length(&headers).unwrap_or(0); @@ -91,7 +91,6 @@ pub fn read_header_values<'a>( #[cfg(test)] mod tests { use super::{read_header_content_length, read_response_to_body}; - use jsonrpsee_types::v2::JsonRpcRequest; #[tokio::test] async fn body_to_request_works() { From 222df5d3680f4eb6022392797b5fb12ba27744b9 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 16 Apr 2021 12:36:52 +0200 Subject: [PATCH 18/41] more tweaks. --- http-client/src/client.rs | 59 +++++------ http-client/src/tests.rs | 2 +- types/src/v2/dummy.rs | 12 --- types/src/v2/mod.rs | 8 +- ws-client/src/client.rs | 217 ++++++++------------------------------ ws-client/src/helpers.rs | 188 +++++++++++++++++++++++++++++++++ ws-client/src/lib.rs | 2 + 7 files changed, 269 insertions(+), 219 deletions(-) create mode 100644 ws-client/src/helpers.rs diff --git a/http-client/src/client.rs b/http-client/src/client.rs index eefa9c1681..e1c003a4c7 100644 --- a/http-client/src/client.rs +++ b/http-client/src/client.rs @@ -2,18 +2,15 @@ use crate::transport::HttpTransportClient; use async_trait::async_trait; use fnv::FnvHashMap; use jsonrpsee_types::{ - error::{Error, Mismatch}, + error::Error, traits::Client, - v2::dummy::{JsonRpcCall, JsonRpcNotification, JsonRpcParams, JsonRpcResponse}, + v2::dummy::{JsonRpcCall, JsonRpcNotification, JsonRpcParams}, v2::error::JsonRpcError, + v2::{JsonRpcResponse, RawValue}, }; use serde::{de::DeserializeOwned, Serialize}; use std::sync::atomic::{AtomicU64, Ordering}; -const SINGLE_RESPONSE: &str = "Single Response"; -const BATCH_RESPONSE: &str = "Batch response"; -const SUBSCRIPTION_RESPONSE: &str = "Subscription response"; - /// Http Client Builder. #[derive(Debug)] pub struct HttpClientBuilder { @@ -79,7 +76,7 @@ impl Client for HttpClient { .await .map_err(|e| Error::TransportError(Box::new(e)))?; - let response = match serde_json::from_slice(&body) { + let response: JsonRpcResponse<_> = match serde_json::from_slice(&body) { Ok(response) => response, Err(_) => { let err: JsonRpcError = serde_json::from_slice(&body).map_err(Error::ParseError)?; @@ -87,11 +84,12 @@ impl Client for HttpClient { } }; - match response { - JsonRpcResponse::Single(response) if response.id == id => Ok(response.result), - JsonRpcResponse::Single(_) => Err(Error::InvalidRequestId), - JsonRpcResponse::Batch(_rps) => Err(invalid_response(SINGLE_RESPONSE, BATCH_RESPONSE)), - JsonRpcResponse::Subscription(_notif) => Err(invalid_response(SINGLE_RESPONSE, SUBSCRIPTION_RESPONSE)), + let response_id = parse_request_id(response.id)?; + + if response_id == id { + Ok(response.result) + } else { + Err(Error::InvalidRequestId) } } @@ -118,7 +116,7 @@ impl Client for HttpClient { .await .map_err(|e| Error::TransportError(Box::new(e)))?; - let response = match serde_json::from_slice(&body) { + let rps: Vec> = match serde_json::from_slice(&body) { Ok(response) => response, Err(_) => { let err: JsonRpcError = serde_json::from_slice(&body).map_err(Error::ParseError)?; @@ -126,25 +124,26 @@ impl Client for HttpClient { } }; - match response { - JsonRpcResponse::Single(_response) => Err(invalid_response(BATCH_RESPONSE, SINGLE_RESPONSE)), - JsonRpcResponse::Subscription(_notif) => Err(invalid_response(BATCH_RESPONSE, SUBSCRIPTION_RESPONSE)), - JsonRpcResponse::Batch(rps) => { - // NOTE: `R::default` is placeholder and will be replaced in loop below. - let mut responses = vec![R::default(); ordered_requests.len()]; - for rp in rps { - let pos = match request_set.get(&rp.id) { - Some(pos) => *pos, - None => return Err(Error::InvalidRequestId), - }; - responses[pos] = rp.result - } - Ok(responses) - } + // NOTE: `R::default` is placeholder and will be replaced in loop below. + let mut responses = vec![R::default(); ordered_requests.len()]; + for rp in rps { + let response_id = parse_request_id(rp.id)?; + let pos = match request_set.get(&response_id) { + Some(pos) => *pos, + None => return Err(Error::InvalidRequestId), + }; + responses[pos] = rp.result } + Ok(responses) } } -fn invalid_response(expected: impl Into, got: impl Into) -> Error { - Error::InvalidResponse(Mismatch { expected: expected.into(), got: got.into() }) +fn parse_request_id(raw: Option<&RawValue>) -> Result { + match raw { + None => Err(Error::InvalidRequestId), + Some(id) => { + let id = serde_json::from_str(id.get()).map_err(Error::ParseError)?; + Ok(id) + } + } } diff --git a/http-client/src/tests.rs b/http-client/src/tests.rs index ea6d3c2901..b7ed7ac653 100644 --- a/http-client/src/tests.rs +++ b/http-client/src/tests.rs @@ -69,7 +69,7 @@ async fn internal_error_works() { async fn subscription_response_to_request() { let req = r#"{"jsonrpc":"2.0","method":"subscribe_hello","params":{"subscription":"3px4FrtxSYQ1zBKW154NoVnrDhrq764yQNCXEgZyM6Mu","result":"hello my friend"}}"#.to_string(); let err = run_request_with_response(req).await.unwrap_err(); - assert!(matches!(err, Error::InvalidResponse(_))); + assert!(matches!(err, Error::ParseError(_))); } #[tokio::test] diff --git a/types/src/v2/dummy.rs b/types/src/v2/dummy.rs index d49636edd0..6cdaa2039c 100644 --- a/types/src/v2/dummy.rs +++ b/types/src/v2/dummy.rs @@ -128,18 +128,6 @@ pub struct JsonRpcResponseNotif { pub params: JsonRpcNotificationParams, } -/// Represent the different JSON-RPC responses. -#[derive(Deserialize, Debug)] -#[serde(untagged)] -pub enum JsonRpcResponse { - /// Single response. - Single(JsonRpcResponseObject), - /// Batch response. - Batch(Vec>), - /// Notification response used for subscriptions. - Subscription(JsonRpcResponseNotif), -} - /// Id of a subscription, communicated by the server. #[derive(Debug, PartialEq, Clone, Hash, Eq, Deserialize, Serialize)] #[serde(deny_unknown_fields)] diff --git a/types/src/v2/mod.rs b/types/src/v2/mod.rs index de4cb27b55..5369ba679e 100644 --- a/types/src/v2/mod.rs +++ b/types/src/v2/mod.rs @@ -42,7 +42,7 @@ pub struct JsonRpcInvalidRequest<'a> { } /// JSON-RPC notification (a request object without a request ID). -#[derive(Serialize, Debug)] +#[derive(Serialize, Deserialize, Debug)] pub struct JsonRpcNotification<'a> { /// JSON-RPC version. pub jsonrpc: TwoPointZero, @@ -53,22 +53,24 @@ pub struct JsonRpcNotification<'a> { } /// JSON-RPC parameter values for subscriptions. -#[derive(Serialize, Debug)] +#[derive(Serialize, Deserialize, Debug)] pub struct JsonRpcNotificationParams<'a> { /// Subscription ID pub subscription: u64, /// Result. + #[serde(borrow)] pub result: &'a RawValue, } /// JSON-RPC successful response object. -#[derive(Serialize, Debug)] +#[derive(Serialize, Deserialize, Debug)] pub struct JsonRpcResponse<'a, T> { /// JSON-RPC version. pub jsonrpc: TwoPointZero, /// Result. pub result: T, /// Request ID + #[serde(borrow)] pub id: Option<&'a RawValue>, } diff --git a/ws-client/src/client.rs b/ws-client/src/client.rs index ec85dd5f45..274f35d0a3 100644 --- a/ws-client/src/client.rs +++ b/ws-client/src/client.rs @@ -24,7 +24,11 @@ // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::manager::{RequestManager, RequestStatus}; +use crate::helpers::{ + build_unsubscribe_message, process_batch_response, process_error_response, process_single_response, + process_subscription_response, stop_subscription, +}; +use crate::manager::RequestManager; use crate::transport::{parse_url, Receiver as WsReceiver, Sender as WsSender, WsTransportClientBuilder}; use async_std::sync::Mutex; use async_trait::async_trait; @@ -38,13 +42,11 @@ use jsonrpsee_types::{ client::{BatchMessage, FrontToBack, RequestMessage, Subscription, SubscriptionMessage}, error::Error, traits::{Client, SubscriptionClient}, - v2::dummy::{ - JsonRpcCall, JsonRpcNotification, JsonRpcParams, JsonRpcResponse, JsonRpcResponseObject, SubscriptionId, - }, + v2::dummy::{JsonRpcCall, JsonRpcNotification, JsonRpcParams, JsonRpcResponseNotif}, v2::error::JsonRpcError, + v2::JsonRpcResponse, }; use serde::{de::DeserializeOwned, Serialize}; -use serde_json::Value as JsonValue; use std::{ borrow::Cow, marker::PhantomData, @@ -530,100 +532,49 @@ async fn background_task( } } Either::Right((Some(Ok(raw)), _)) => { - let response: JsonRpcResponse = match serde_json::from_slice(&raw) { - Ok(response) => response, - Err(_) => { - let err: Result = serde_json::from_slice(&raw).map_err(Error::ParseError); - if let Ok(err) = err { - match manager.request_status(&err.id) { - RequestStatus::PendingMethodCall => { - let send_back = - manager.complete_pending_call(err.id).expect("State checked above; qed"); - let _ = send_back.map(|s| s.send(Err(Error::Request(err)))); - } - RequestStatus::PendingSubscription => { - let (_, send_back, _) = manager - .complete_pending_subscription(err.id) - .expect("State checked above; qed"); - let _ = send_back.send(Err(Error::Request(err))); - } - _ => (), - } + // Single response to a request. + if let Ok(single) = serde_json::from_slice::>(&raw) { + log::debug!("[backend]: recv method_call {:?}", single); + match process_single_response(&mut manager, single, max_notifs_per_subscription) { + Ok(Some(unsub)) => { + stop_subscription(&mut sender, &mut manager, unsub).await; } - continue; - } - }; - - match response { - JsonRpcResponse::Single(call) => { - log::debug!("[backend]: recv method_call {:?}", call); - match process_response(&mut manager, call, max_notifs_per_subscription) { - Ok(Some(unsub)) => { - stop_subscription(&mut sender, &mut manager, unsub).await; - } - Ok(None) => (), - Err(err) => { - let _ = front_error.send(err); - return; - } + Ok(None) => (), + Err(err) => { + let _ = front_error.send(err); + return; } } - JsonRpcResponse::Batch(batch) => { - log::debug!("[backend]: recv batch {:?}", batch); - let mut digest = Vec::with_capacity(batch.len()); - let mut ordered_responses = vec![JsonValue::Null; batch.len()]; - let mut rps_unordered: Vec<_> = Vec::with_capacity(batch.len()); - - for rp in batch { - digest.push(rp.id); - rps_unordered.push((rp.id, rp.result)); - } - - digest.sort_unstable(); - let batch_state = match manager.complete_pending_batch(digest) { - Some(state) => state, - None => { - log::warn!("Received unknown batch response"); - continue; - } - }; - - for (id, rp) in rps_unordered { - let pos = batch_state - .order - .get(&id) - .copied() - .expect("All request IDs valid checked by RequestManager above; qed"); - ordered_responses[pos] = rp; - } - let _ = batch_state.send_back.send(Ok(ordered_responses)); + } + // Subscription response. + else if let Ok(notif) = serde_json::from_slice::>(&raw) { + log::debug!("[backend]: recv subscription {:?}", notif); + if let Err(Some(unsub)) = process_subscription_response(&mut manager, notif) { + let _ = stop_subscription(&mut sender, &mut manager, unsub).await; } - JsonRpcResponse::Subscription(notif) => { - log::debug!("[backend]: recv subscription response {:?}", notif); - let sub_id = notif.params.subscription; - let request_id = match manager.get_request_id_by_subscription_id(&sub_id) { - Some(r) => r, - None => { - log::error!("Subscription ID: {:?} not found", sub_id); - continue; - } - }; - - match manager.as_subscription_mut(&request_id) { - Some(send_back_sink) => { - if let Err(e) = send_back_sink.try_send(notif.params.result) { - log::error!("Dropping subscription {:?} error: {:?}", sub_id, e); - let unsub_msg = build_unsubscribe_message(&mut manager, request_id, sub_id) - .expect("request ID and subscription ID valid checked above; qed"); - stop_subscription(&mut sender, &mut manager, unsub_msg).await; - } - } - None => { - log::error!("Subscription ID: {:?} not an active subscription", sub_id); - } - } + } + // Batch response. + else if let Ok(batch) = serde_json::from_slice::>>(&raw) { + log::debug!("[backend]: recv batch {:?}", batch); + if let Err(e) = process_batch_response(&mut manager, batch) { + let _ = front_error.send(e); + break; + } + } + // Error response + else if let Ok(err) = serde_json::from_slice::(&raw) { + log::debug!("[backend]: recv error response {:?}", err); + if let Err(e) = process_error_response(&mut manager, err) { + let _ = front_error.send(e); + break; } } + // Unparsable response + else { + log::debug!("[backend]: recv unparseable message"); + let _ = front_error.send(Error::InvalidRequestId); + return; + } } Either::Right((Some(Err(e)), _)) => { log::error!("Error: {:?} terminating client", e); @@ -638,83 +589,3 @@ async fn background_task( } } } - -/// Process a response from the server. -/// -/// Returns `Ok(None)` if the response was successful -/// Returns `Ok(Some(_))` if the response got an error but could be handled. -/// Returns `Err(_)` if the response couldn't be handled. -fn process_response( - manager: &mut RequestManager, - response: JsonRpcResponseObject, - max_capacity_per_subscription: usize, -) -> Result, Error> { - match manager.request_status(&response.id) { - RequestStatus::PendingMethodCall => { - let send_back_oneshot = match manager.complete_pending_call(response.id) { - Some(Some(send)) => send, - Some(None) => return Ok(None), - None => return Err(Error::InvalidRequestId), - }; - let _ = send_back_oneshot.send(Ok(response.result)); - Ok(None) - } - RequestStatus::PendingSubscription => { - let (unsub_id, send_back_oneshot, unsubscribe_method) = - manager.complete_pending_subscription(response.id).ok_or(Error::InvalidRequestId)?; - - let sub_id: SubscriptionId = match serde_json::from_value(response.result) { - Ok(sub_id) => sub_id, - Err(_) => { - let _ = send_back_oneshot.send(Err(Error::InvalidSubscriptionId)); - return Ok(None); - } - }; - - let response_id = response.id; - let (subscribe_tx, subscribe_rx) = mpsc::channel(max_capacity_per_subscription); - if manager - .insert_subscription(response_id, unsub_id, sub_id.clone(), subscribe_tx, unsubscribe_method) - .is_ok() - { - match send_back_oneshot.send(Ok((subscribe_rx, sub_id.clone()))) { - Ok(_) => Ok(None), - Err(_) => Ok(build_unsubscribe_message(manager, response_id, sub_id)), - } - } else { - let _ = send_back_oneshot.send(Err(Error::InvalidSubscriptionId)); - Ok(None) - } - } - RequestStatus::Subscription | RequestStatus::Invalid => Err(Error::InvalidRequestId), - } -} - -/// Sends an unsubscribe to request to server to indicate -/// that the client is not interested in the subscription anymore. -// -// NOTE: we don't count this a concurrent request as it's part of a subscription. -async fn stop_subscription(sender: &mut WsSender, manager: &mut RequestManager, unsub: RequestMessage) { - if let Err(e) = sender.send(unsub.raw).await { - log::error!("Send unsubscribe request failed: {:?}", e); - let _ = manager.complete_pending_call(unsub.id); - } -} - -/// Builds an unsubscription message, semantically the same as an ordinary request. -fn build_unsubscribe_message( - manager: &mut RequestManager, - sub_req_id: u64, - sub_id: SubscriptionId, -) -> Option { - let (unsub_req_id, _, unsub, sub_id) = manager.remove_subscription(sub_req_id, sub_id)?; - let sub_id_slice = &[sub_id]; - if manager.insert_pending_call(unsub_req_id, None).is_err() { - log::warn!("Unsubscribe message failed to get slot in the RequestManager"); - return None; - } - // TODO(niklasad): better type for params or maybe a macro?!. - let params = JsonRpcParams::Array(sub_id_slice); - let raw = serde_json::to_string(&JsonRpcCall::new(unsub_req_id, &unsub, params)).unwrap(); - Some(RequestMessage { raw, id: unsub_req_id, send_back: None }) -} diff --git a/ws-client/src/helpers.rs b/ws-client/src/helpers.rs new file mode 100644 index 0000000000..448f3e1f8a --- /dev/null +++ b/ws-client/src/helpers.rs @@ -0,0 +1,188 @@ +use crate::manager::{RequestManager, RequestStatus}; +use crate::transport::Sender as WsSender; +use futures::channel::mpsc; +use jsonrpsee_types::{ + client::RequestMessage, + error::Error, + v2::dummy::{JsonRpcCall, JsonRpcParams, JsonRpcResponseNotif, SubscriptionId}, + v2::error::JsonRpcError, + v2::{JsonRpcResponse, RawValue}, +}; +use serde_json::Value as JsonValue; + +/// Attempts to process a batch response. +/// +/// On success the result is sent to the frontend. +pub fn process_batch_response<'a>( + manager: &mut RequestManager, + rps: Vec>, +) -> Result<(), Error> { + let mut digest = Vec::with_capacity(rps.len()); + let mut ordered_responses = vec![JsonValue::Null; rps.len()]; + let mut rps_unordered: Vec<_> = Vec::with_capacity(rps.len()); + + for rp in rps { + let id = parse_request_id(rp.id)?; + digest.push(id); + rps_unordered.push((id, rp.result)); + } + + digest.sort_unstable(); + let batch_state = match manager.complete_pending_batch(digest) { + Some(state) => state, + None => { + log::warn!("Received unknown batch response"); + return Err(Error::InvalidRequestId); + } + }; + + for (id, rp) in rps_unordered { + let pos = + batch_state.order.get(&id).copied().expect("All request IDs valid checked by RequestManager above; qed"); + ordered_responses[pos] = rp; + } + let _ = batch_state.send_back.send(Ok(ordered_responses)); + Ok(()) +} + +/// Attempts to process a subscription response. +/// +/// Returns `Ok()` if the response was successfully sent to the frontend. +/// Return `Err(None)` if the subscription was not found. +/// Returns `Err(Some(msg))` if the subscription was full. +pub fn process_subscription_response( + manager: &mut RequestManager, + notif: JsonRpcResponseNotif, +) -> Result<(), Option> { + let sub_id = notif.params.subscription; + let request_id = match manager.get_request_id_by_subscription_id(&sub_id) { + Some(request_id) => request_id, + None => return Err(None), + }; + + match manager.as_subscription_mut(&request_id) { + Some(send_back_sink) => match send_back_sink.try_send(notif.params.result) { + Ok(()) => Ok(()), + Err(err) => { + log::error!("Dropping subscription {:?} error: {:?}", sub_id, err); + let msg = build_unsubscribe_message(manager, request_id, sub_id) + .expect("request ID and subscription ID valid checked above; qed"); + Err(Some(msg)) + } + }, + None => { + log::error!("Subscription ID: {:?} not an active subscription", sub_id); + Err(None) + } + } +} + +/// Process a response from the server. +/// +/// Returns `Ok(None)` if the response was successfully sent. +/// Returns `Ok(Some(_))` if the response got an error but could be handled. +/// Returns `Err(_)` if the response couldn't be handled. +pub fn process_single_response( + manager: &mut RequestManager, + response: JsonRpcResponse, + max_capacity_per_subscription: usize, +) -> Result, Error> { + let response_id = parse_request_id(response.id)?; + match manager.request_status(&response_id) { + RequestStatus::PendingMethodCall => { + let send_back_oneshot = match manager.complete_pending_call(response_id) { + Some(Some(send)) => send, + Some(None) => return Ok(None), + None => return Err(Error::InvalidRequestId), + }; + let _ = send_back_oneshot.send(Ok(response.result)); + Ok(None) + } + RequestStatus::PendingSubscription => { + let (unsub_id, send_back_oneshot, unsubscribe_method) = + manager.complete_pending_subscription(response_id).ok_or(Error::InvalidRequestId)?; + + let sub_id: SubscriptionId = match serde_json::from_value(response.result) { + Ok(sub_id) => sub_id, + Err(_) => { + let _ = send_back_oneshot.send(Err(Error::InvalidSubscriptionId)); + return Ok(None); + } + }; + + let (subscribe_tx, subscribe_rx) = mpsc::channel(max_capacity_per_subscription); + if manager + .insert_subscription(response_id, unsub_id, sub_id.clone(), subscribe_tx, unsubscribe_method) + .is_ok() + { + match send_back_oneshot.send(Ok((subscribe_rx, sub_id.clone()))) { + Ok(_) => Ok(None), + Err(_) => Ok(build_unsubscribe_message(manager, response_id, sub_id)), + } + } else { + let _ = send_back_oneshot.send(Err(Error::InvalidSubscriptionId)); + Ok(None) + } + } + RequestStatus::Subscription | RequestStatus::Invalid => Err(Error::InvalidRequestId), + } +} + +/// Sends an unsubscribe to request to server to indicate +/// that the client is not interested in the subscription anymore. +// +// NOTE: we don't count this a concurrent request as it's part of a subscription. +pub async fn stop_subscription(sender: &mut WsSender, manager: &mut RequestManager, unsub: RequestMessage) { + if let Err(e) = sender.send(unsub.raw).await { + log::error!("Send unsubscribe request failed: {:?}", e); + let _ = manager.complete_pending_call(unsub.id); + } +} + +/// Builds an unsubscription message, semantically the same as an ordinary request. +pub fn build_unsubscribe_message( + manager: &mut RequestManager, + sub_req_id: u64, + sub_id: SubscriptionId, +) -> Option { + let (unsub_req_id, _, unsub, sub_id) = manager.remove_subscription(sub_req_id, sub_id)?; + let sub_id_slice = &[sub_id]; + if manager.insert_pending_call(unsub_req_id, None).is_err() { + log::warn!("Unsubscribe message failed to get slot in the RequestManager"); + return None; + } + // TODO(niklasad): better type for params or maybe a macro?!. + let params = JsonRpcParams::Array(sub_id_slice); + let raw = serde_json::to_string(&JsonRpcCall::new(unsub_req_id, &unsub, params)).unwrap(); + Some(RequestMessage { raw, id: unsub_req_id, send_back: None }) +} + +fn parse_request_id(raw: Option<&RawValue>) -> Result { + match raw { + None => Err(Error::InvalidRequestId), + Some(id) => { + let id = serde_json::from_str(id.get()).map_err(Error::ParseError)?; + Ok(id) + } + } +} + +/// Attempts to process an error response. +/// +/// Returns `Ok` if the response was successfully sent. +/// Returns `Err(_)` if the response ID was not found. +pub fn process_error_response(manager: &mut RequestManager, err: JsonRpcError) -> Result<(), Error> { + match manager.request_status(&err.id) { + RequestStatus::PendingMethodCall => { + let send_back = manager.complete_pending_call(err.id).expect("State checked above; qed"); + let _ = send_back.map(|s| s.send(Err(Error::Request(err)))); + Ok(()) + } + RequestStatus::PendingSubscription => { + let (_, send_back, _) = manager.complete_pending_subscription(err.id).expect("State checked above; qed"); + let _ = send_back.send(Err(Error::Request(err))); + Ok(()) + } + _ => Err(Error::InvalidRequestId), + } +} diff --git a/ws-client/src/lib.rs b/ws-client/src/lib.rs index cdf1987d13..cd0ce72907 100644 --- a/ws-client/src/lib.rs +++ b/ws-client/src/lib.rs @@ -6,6 +6,8 @@ /// WebSocket Client. pub mod client; +/// Helpers. +pub mod helpers; /// Request manager. pub mod manager; /// Stream. From d949990c048b56df92998c786b4d63456f689888 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 16 Apr 2021 12:53:09 +0200 Subject: [PATCH 19/41] remove unused code --- types/src/v2/dummy.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/types/src/v2/dummy.rs b/types/src/v2/dummy.rs index 6cdaa2039c..34d324ce9c 100644 --- a/types/src/v2/dummy.rs +++ b/types/src/v2/dummy.rs @@ -99,17 +99,6 @@ where } } -/// [Successful JSON-RPC object](https://www.jsonrpc.org/specification#response_object). -#[derive(Deserialize, Debug)] -pub struct JsonRpcResponseObject { - /// JSON-RPC version. - pub jsonrpc: TwoPointZero, - /// Result. - pub result: T, - /// Request ID - pub id: u64, -} - /// JSON-RPC parameter values for subscriptions. #[derive(Deserialize, Debug)] pub struct JsonRpcNotificationParams { @@ -120,6 +109,8 @@ pub struct JsonRpcNotificationParams { } /// JSON-RPC notification response. +// NOTE(niklasad1): basically the same as Maciej version but I wanted to support Strings too. +// Maybe make subscription ID generic?! #[derive(Deserialize, Debug)] pub struct JsonRpcResponseNotif { /// JSON-RPC version. From d29833422e40af1cddf869e94c0e1ef9a27675b6 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 16 Apr 2021 13:00:43 +0200 Subject: [PATCH 20/41] fix more nits --- benches/bench.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index eb148e640d..7b9954b70a 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -8,7 +8,7 @@ use tokio::runtime::Runtime as TokioRuntime; mod helpers; -criterion_group!(benches, /*http_requests,*/ websocket_requests /*, jsonrpsee_types_v1, jsonrpsee_types_v2*/); +criterion_group!(benches, http_requests, websocket_requests, jsonrpsee_types_v1, jsonrpsee_types_v2); criterion_main!(benches); fn v1_serialize(req: jsonrpc::Request) -> String { @@ -36,7 +36,7 @@ pub fn jsonrpsee_types_v1(crit: &mut Criterion) { pub fn jsonrpsee_types_v2(crit: &mut Criterion) { crit.bench_function("jsonrpsee_types_v2", |b| { b.iter(|| { - let params = JsonRpcParams::Array(&[1, 2]); + let params = JsonRpcParams::Array(&[1_u64, 2]); let request = JsonRpcCall::new(0, "say_hello", params); v2_serialize(request); }) From ee31b7ddd2c860ccaa9cf3dca6f14b0881f519c5 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 16 Apr 2021 13:09:19 +0200 Subject: [PATCH 21/41] remove unused legacy types --- benches/bench.rs | 22 +- http-client/src/lib.rs | 2 +- jsonrpsee/tests/integration_tests.rs | 2 +- types/src/jsonrpc/error.rs | 203 ----------- types/src/jsonrpc/id.rs | 93 ----- types/src/jsonrpc/mod.rs | 56 --- types/src/jsonrpc/params.rs | 132 ------- types/src/jsonrpc/request.rs | 307 ----------------- types/src/jsonrpc/response.rs | 297 ---------------- types/src/jsonrpc/version.rs | 76 ---- types/src/jsonrpc/wrapped/batch.rs | 357 ------------------- types/src/jsonrpc/wrapped/batches.rs | 400 ---------------------- types/src/jsonrpc/wrapped/mod.rs | 9 - types/src/jsonrpc/wrapped/notification.rs | 64 ---- types/src/jsonrpc/wrapped/params.rs | 176 ---------- types/src/lib.rs | 3 - ws-client/src/lib.rs | 3 +- ws-client/src/tests.rs | 2 + 18 files changed, 7 insertions(+), 2197 deletions(-) delete mode 100644 types/src/jsonrpc/error.rs delete mode 100644 types/src/jsonrpc/id.rs delete mode 100644 types/src/jsonrpc/mod.rs delete mode 100644 types/src/jsonrpc/params.rs delete mode 100644 types/src/jsonrpc/request.rs delete mode 100644 types/src/jsonrpc/response.rs delete mode 100644 types/src/jsonrpc/version.rs delete mode 100644 types/src/jsonrpc/wrapped/batch.rs delete mode 100644 types/src/jsonrpc/wrapped/batches.rs delete mode 100644 types/src/jsonrpc/wrapped/mod.rs delete mode 100644 types/src/jsonrpc/wrapped/notification.rs delete mode 100644 types/src/jsonrpc/wrapped/params.rs diff --git a/benches/bench.rs b/benches/bench.rs index 7b9954b70a..3dc69ee598 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -1,6 +1,6 @@ use criterion::*; use jsonrpsee::{ - http_client::{jsonrpc, Client, HttpClientBuilder, JsonRpcCall, JsonRpcParams}, + http_client::{Client, HttpClientBuilder, JsonRpcCall, JsonRpcParams}, ws_client::WsClientBuilder, }; use std::sync::Arc; @@ -8,31 +8,13 @@ use tokio::runtime::Runtime as TokioRuntime; mod helpers; -criterion_group!(benches, http_requests, websocket_requests, jsonrpsee_types_v1, jsonrpsee_types_v2); +criterion_group!(benches, http_requests, websocket_requests, jsonrpsee_types_v2); criterion_main!(benches); -fn v1_serialize(req: jsonrpc::Request) -> String { - serde_json::to_string(&req).unwrap() -} - fn v2_serialize(req: JsonRpcCall) -> String { serde_json::to_string(&req).unwrap() } -pub fn jsonrpsee_types_v1(crit: &mut Criterion) { - crit.bench_function("jsonrpsee_types_v1", |b| { - b.iter(|| { - let request = jsonrpc::Request::Single(jsonrpc::Call::MethodCall(jsonrpc::MethodCall { - jsonrpc: jsonrpc::Version::V2, - method: "say_hello".to_string(), - params: jsonrpc::Params::Array(vec![1_u64.into(), 2_u64.into()]), - id: jsonrpc::Id::Num(0), - })); - v1_serialize(request); - }) - }); -} - pub fn jsonrpsee_types_v2(crit: &mut Criterion) { crit.bench_function("jsonrpsee_types_v2", |b| { b.iter(|| { diff --git a/http-client/src/lib.rs b/http-client/src/lib.rs index b9043b1622..cb81a9a865 100644 --- a/http-client/src/lib.rs +++ b/http-client/src/lib.rs @@ -46,7 +46,7 @@ pub use client::{HttpClient, HttpClientBuilder}; pub use jsonrpsee_types::{ error::Error, - jsonrpc, traits::Client, v2::dummy::{JsonRpcCall, JsonRpcNotification, JsonRpcParams}, + v2::JsonValue, }; diff --git a/jsonrpsee/tests/integration_tests.rs b/jsonrpsee/tests/integration_tests.rs index 9284bc9e59..f929707516 100644 --- a/jsonrpsee/tests/integration_tests.rs +++ b/jsonrpsee/tests/integration_tests.rs @@ -31,7 +31,7 @@ mod helpers; use helpers::{http_server, websocket_server, websocket_server_with_subscription}; use jsonrpsee::{ http_client::{Client, Error, HttpClientBuilder}, - ws_client::{jsonrpc::JsonValue, JsonRpcParams, SubscriptionClient, WsClientBuilder, WsSubscription}, + ws_client::{JsonRpcParams, JsonValue, SubscriptionClient, WsClientBuilder, WsSubscription}, }; use std::sync::Arc; use std::time::Duration; diff --git a/types/src/jsonrpc/error.rs b/types/src/jsonrpc/error.rs deleted file mode 100644 index 184435a325..0000000000 --- a/types/src/jsonrpc/error.rs +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2019 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. - -use super::JsonValue; - -use alloc::{ - borrow::ToOwned as _, - format, - string::{String, ToString as _}, -}; -use core::fmt; -use serde::de::Deserializer; -use serde::ser::Serializer; -use serde::{Deserialize, Serialize}; - -/// JSONRPC error code -#[derive(Debug, PartialEq, Clone)] -pub enum ErrorCode { - /// Invalid JSON was received by the server. - /// An error occurred on the server while parsing the JSON text. - ParseError, - /// The JSON sent is not a valid Request object. - InvalidRequest, - /// The method does not exist / is not available. - MethodNotFound, - /// Invalid method parameter(s). - InvalidParams, - /// Internal JSON-RPC error. - InternalError, - /// Reserved for implementation-defined server-errors. - ServerError(i64), - /// Error returned by called method - MethodError(i64), -} - -impl ErrorCode { - /// Returns integer code value - pub fn code(&self) -> i64 { - match *self { - ErrorCode::ParseError => -32700, - ErrorCode::InvalidRequest => -32600, - ErrorCode::MethodNotFound => -32601, - ErrorCode::InvalidParams => -32602, - ErrorCode::InternalError => -32603, - ErrorCode::ServerError(code) => code, - ErrorCode::MethodError(code) => code, - } - } - - /// Returns human-readable description - pub fn description(&self) -> String { - let desc = match *self { - ErrorCode::ParseError => "Parse error", - ErrorCode::InvalidRequest => "Invalid request", - ErrorCode::MethodNotFound => "Method not found", - ErrorCode::InvalidParams => "Invalid params", - ErrorCode::InternalError => "Internal error", - ErrorCode::ServerError(_) => "Server error", - ErrorCode::MethodError(_) => "Method error", - }; - desc.to_string() - } -} - -impl From for ErrorCode { - fn from(code: i64) -> Self { - match code { - -32700 => ErrorCode::ParseError, - -32600 => ErrorCode::InvalidRequest, - -32601 => ErrorCode::MethodNotFound, - -32602 => ErrorCode::InvalidParams, - -32603 => ErrorCode::InternalError, - -32099..=-32000 => ErrorCode::ServerError(code), - code => ErrorCode::MethodError(code), - } - } -} - -impl<'a> serde::Deserialize<'a> for ErrorCode { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'a>, - { - let code: i64 = serde::Deserialize::deserialize(deserializer)?; - Ok(ErrorCode::from(code)) - } -} - -impl serde::Serialize for ErrorCode { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_i64(self.code()) - } -} - -/// Error object as defined in Spec -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Error { - /// Code - pub code: ErrorCode, - /// Message - pub message: String, - /// Optional data - #[serde(skip_serializing_if = "Option::is_none")] - pub data: Option, -} - -impl Error { - /// Wraps given `ErrorCode` - pub fn new(code: ErrorCode) -> Self { - Error { message: code.description(), code, data: None } - } - - /// Creates new `ParseError` - pub fn parse_error() -> Self { - Self::new(ErrorCode::ParseError) - } - - /// Creates new `InvalidRequest` - pub fn invalid_request() -> Self { - Self::new(ErrorCode::InvalidRequest) - } - - /// Creates new `MethodNotFound` - pub fn method_not_found() -> Self { - Self::new(ErrorCode::MethodNotFound) - } - - /// Creates new `InvalidParams` - pub fn invalid_params(message: M) -> Self - where - M: Into, - { - Error { code: ErrorCode::InvalidParams, message: message.into(), data: None } - } - - /// Creates `InvalidParams` for given parameter, with details. - pub fn invalid_params_with_details(message: M, details: T) -> Error - where - M: Into, - T: fmt::Debug, - { - Error { - code: ErrorCode::InvalidParams, - message: format!("Invalid parameters: {}", message.into()), - data: Some(JsonValue::String(format!("{:?}", details))), - } - } - - /// Creates new `InternalError` - pub fn internal_error() -> Self { - Self::new(ErrorCode::InternalError) - } - - /// Creates new `InvalidRequest` with invalid version description - pub fn invalid_version() -> Self { - Error { - code: ErrorCode::InvalidRequest, - message: "Unsupported JSON-RPC protocol version".to_owned(), - data: None, - } - } -} - -impl From for Error { - fn from(code: ErrorCode) -> Error { - Error::new(code) - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}: {}", self.code.description(), self.message) - } -} - -impl std::error::Error for Error {} diff --git a/types/src/jsonrpc/id.rs b/types/src/jsonrpc/id.rs deleted file mode 100644 index 5321bef9ca..0000000000 --- a/types/src/jsonrpc/id.rs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2019 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. - -use alloc::string::String; -use serde::{Deserialize, Serialize}; - -/// Request Id -#[derive(Debug, PartialEq, Clone, Hash, Eq, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -#[serde(untagged)] -pub enum Id { - /// No id (notification) - Null, - /// Numeric id - Num(u64), - /// String id - Str(String), -} - -impl Id { - /// If the `Id` is a number, returns the associated number. Returns None - /// otherwise. - pub fn as_number(&self) -> Option<&u64> { - match self { - Self::Num(n) => Some(n), - _ => None, - } - } - - /// If the `Id` is a String, returns the associated &str. Returns None - /// otherwise. - pub fn as_str(&self) -> Option<&str> { - match self { - Self::Str(s) => Some(s), - _ => None, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use serde_json; - - #[test] - fn id_deserialization() { - let s = r#""2""#; - let deserialized: Id = serde_json::from_str(s).unwrap(); - assert_eq!(deserialized, Id::Str("2".into())); - - let s = r#"2"#; - let deserialized: Id = serde_json::from_str(s).unwrap(); - assert_eq!(deserialized, Id::Num(2)); - - let s = r#""2x""#; - let deserialized: Id = serde_json::from_str(s).unwrap(); - assert_eq!(deserialized, Id::Str("2x".to_owned())); - - let s = r#"[null, 0, 2, "3"]"#; - let deserialized: Vec = serde_json::from_str(s).unwrap(); - assert_eq!(deserialized, vec![Id::Null, Id::Num(0), Id::Num(2), Id::Str("3".into())]); - } - - #[test] - fn id_serialization() { - let d = vec![Id::Null, Id::Num(0), Id::Num(2), Id::Num(3), Id::Str("3".to_owned()), Id::Str("test".to_owned())]; - let serialized = serde_json::to_string(&d).unwrap(); - assert_eq!(serialized, r#"[null,0,2,3,"3","test"]"#); - } -} diff --git a/types/src/jsonrpc/mod.rs b/types/src/jsonrpc/mod.rs deleted file mode 100644 index 1d3b3639f2..0000000000 --- a/types/src/jsonrpc/mod.rs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2019 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. - -//! Type definitions from the JSON-RPC specifications. -//! -//! All these common implement the `Serialize` and `Deserialize` traits of the `serde` library -//! and can be serialize/deserialized using the `to_string`/`to_vec`/`from_slice` methods. - -mod error; -mod id; -mod params; -mod request; -mod response; -mod version; - -/// Wrappers on-top of plain serialize/deserialize types with additional convience methods. -pub mod wrapped; - -pub use serde::{de::DeserializeOwned, ser::Serialize}; -pub use serde_json::error::Error as ParseError; -pub use serde_json::Map as JsonMap; -pub use serde_json::Number as JsonNumber; -pub use serde_json::Value as JsonValue; -pub use serde_json::{from_slice, from_value, to_string, to_value, to_vec}; - -pub use self::error::{Error, ErrorCode}; -pub use self::id::Id; -pub use self::params::Params; -pub use self::request::{Call, MethodCall, Notification, Request}; -pub use self::response::{ - Failure, Output, Response, SubscriptionId, SubscriptionNotif, SubscriptionNotifParams, Success, -}; -pub use self::version::Version; diff --git a/types/src/jsonrpc/params.rs b/types/src/jsonrpc/params.rs deleted file mode 100644 index 7db4695f7e..0000000000 --- a/types/src/jsonrpc/params.rs +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2019 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. - -use alloc::{format, string::String, vec::Vec}; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; -use serde_json::value::from_value; - -use super::{Error, JsonValue}; - -/// Request parameters -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -#[serde(untagged)] -pub enum Params { - /// No parameters - None, - /// Array of values - Array(Vec), - /// Map of values - Map(serde_json::Map), -} - -impl Params { - /// Parse incoming `Params` into expected common. - pub fn parse(self) -> Result - where - D: DeserializeOwned, - { - let value: JsonValue = self.into(); - from_value(value).map_err(|e| Error::invalid_params(format!("Invalid params: {}.", e))) - } - - /// Check for no params, returns Err if any params - pub fn expect_no_params(self) -> Result<(), Error> { - match self { - Params::None => Ok(()), - Params::Array(ref v) if v.is_empty() => Ok(()), - p => Err(Error::invalid_params_with_details("No parameters were expected", p)), - } - } -} - -impl From for JsonValue { - fn from(params: Params) -> JsonValue { - match params { - Params::Array(vec) => JsonValue::Array(vec), - Params::Map(map) => JsonValue::Object(map), - Params::None => JsonValue::Null, - } - } -} - -#[cfg(test)] -mod tests { - use super::Params; - use crate::jsonrpc::{Error, ErrorCode, JsonValue}; - - #[test] - fn params_deserialization() { - let s = r#"[null, true, -1, 4, 2.3, "hello", [0], {"key": "value"}, []]"#; - let deserialized: Params = serde_json::from_str(s).unwrap(); - - let mut map = serde_json::Map::new(); - map.insert("key".to_string(), JsonValue::String("value".to_string())); - - assert_eq!( - Params::Array(vec![ - JsonValue::Null, - JsonValue::Bool(true), - JsonValue::from(-1), - JsonValue::from(4), - JsonValue::from(2.3), - JsonValue::String("hello".to_string()), - JsonValue::Array(vec![JsonValue::from(0)]), - JsonValue::Object(map), - JsonValue::Array(vec![]), - ]), - deserialized - ); - } - - #[test] - fn should_return_meaningful_error_when_deserialization_fails() { - // given - let s = r#"[1, true]"#; - let params = || serde_json::from_str::(s).unwrap(); - - // when - let v1: Result<(Option, String), Error> = params().parse(); - let v2: Result<(u8, bool, String), Error> = params().parse(); - let err1 = v1.unwrap_err(); - let err2 = v2.unwrap_err(); - - // then - assert_eq!(err1.code, ErrorCode::InvalidParams); - assert_eq!(err1.message, "Invalid params: invalid type: boolean `true`, expected a string."); - assert_eq!(err1.data, None); - assert_eq!(err2.code, ErrorCode::InvalidParams); - assert_eq!(err2.message, "Invalid params: invalid length 2, expected a tuple of size 3."); - assert_eq!(err2.data, None); - } - - #[test] - fn single_param_parsed_as_tuple() { - let params: (u64,) = Params::Array(vec![JsonValue::from(1)]).parse().unwrap(); - assert_eq!(params, (1,)); - } -} diff --git a/types/src/jsonrpc/request.rs b/types/src/jsonrpc/request.rs deleted file mode 100644 index e6f7621dfe..0000000000 --- a/types/src/jsonrpc/request.rs +++ /dev/null @@ -1,307 +0,0 @@ -// Copyright 2019 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. - -use super::{Id, Params, Version}; - -use alloc::{fmt, string::String, vec::Vec}; -use serde::{Deserialize, Serialize}; - -/// Represents jsonrpc request which is a method call. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -pub struct MethodCall { - /// A String specifying the version of the JSON-RPC protocol. - pub jsonrpc: Version, - /// A String containing the name of the method to be invoked. - pub method: String, - /// A Structured value that holds the parameter values to be used - /// during the invocation of the method. This member MAY be omitted. - #[serde(default = "default_params")] - pub params: Params, - /// An identifier established by the Client that MUST contain a String, - /// Number, or NULL value if included. If it is not included it is assumed - /// to be a notification. - pub id: Id, -} - -/// Represents jsonrpc request which is a notification. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Notification { - /// A String specifying the version of the JSON-RPC protocol. - pub jsonrpc: Version, - /// A String containing the name of the method to be invoked. - pub method: String, - /// A Structured value that holds the parameter values to be used - /// during the invocation of the method. This member MAY be omitted. - #[serde(default = "default_params")] - pub params: Params, -} - -/// Represents single jsonrpc call. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -#[serde(untagged)] -pub enum Call { - /// Call method - MethodCall(MethodCall), - /// Fire notification - Notification(Notification), - /// Invalid call - Invalid { - /// Call id (if known) - #[serde(default = "default_id")] - id: Id, - }, -} - -fn default_params() -> Params { - Params::None -} - -fn default_id() -> Id { - Id::Null -} - -impl From for Call { - fn from(mc: MethodCall) -> Self { - Call::MethodCall(mc) - } -} - -impl From for Call { - fn from(n: Notification) -> Self { - Call::Notification(n) - } -} - -/// Represents jsonrpc request. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -#[serde(untagged)] -pub enum Request { - /// Single request (call) - Single(Call), - /// Batch of requests (calls) - Batch(Vec), -} - -impl fmt::Display for Request { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", serde_json::to_string(self).expect("Request valid JSON; qed")) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use serde_json::Value; - - #[test] - fn method_call_serialize() { - let m = MethodCall { - jsonrpc: Version::V2, - method: "update".to_owned(), - params: Params::Array(vec![Value::from(1), Value::from(2)]), - id: Id::Num(1), - }; - - let serialized = serde_json::to_string(&m).unwrap(); - assert_eq!(serialized, r#"{"jsonrpc":"2.0","method":"update","params":[1,2],"id":1}"#); - } - - #[test] - fn notification_serialize() { - let n = Notification { - jsonrpc: Version::V2, - method: "update".to_owned(), - params: Params::Array(vec![Value::from(1), Value::from(2)]), - }; - - let serialized = serde_json::to_string(&n).unwrap(); - assert_eq!(serialized, r#"{"jsonrpc":"2.0","method":"update","params":[1,2]}"#); - } - - #[test] - fn call_serialize() { - let n = Call::Notification(Notification { - jsonrpc: Version::V2, - method: "update".to_owned(), - params: Params::Array(vec![Value::from(1)]), - }); - - let serialized = serde_json::to_string(&n).unwrap(); - assert_eq!(serialized, r#"{"jsonrpc":"2.0","method":"update","params":[1]}"#); - } - - #[test] - fn request_serialize_batch() { - let batch = Request::Batch(vec![ - Call::MethodCall(MethodCall { - jsonrpc: Version::V2, - method: "update".to_owned(), - params: Params::Array(vec![Value::from(1), Value::from(2)]), - id: Id::Num(1), - }), - Call::Notification(Notification { - jsonrpc: Version::V2, - method: "update".to_owned(), - params: Params::Array(vec![Value::from(1)]), - }), - ]); - - let serialized = serde_json::to_string(&batch).unwrap(); - assert_eq!( - serialized, - r#"[{"jsonrpc":"2.0","method":"update","params":[1,2],"id":1},{"jsonrpc":"2.0","method":"update","params":[1]}]"# - ); - } - - #[test] - fn notification_deserialize() { - use serde_json; - use serde_json::Value; - - let s = r#"{"jsonrpc": "2.0", "method": "update", "params": [1,2]}"#; - let deserialized: Notification = serde_json::from_str(s).unwrap(); - - assert_eq!( - deserialized, - Notification { - jsonrpc: Version::V2, - method: "update".to_owned(), - params: Params::Array(vec![Value::from(1), Value::from(2)]) - } - ); - - let s = r#"{"jsonrpc": "2.0", "method": "foobar"}"#; - let deserialized: Notification = serde_json::from_str(s).unwrap(); - - assert_eq!( - deserialized, - Notification { jsonrpc: Version::V2, method: "foobar".to_owned(), params: Params::None } - ); - - let s = r#"{"jsonrpc": "2.0", "method": "update", "params": [1,2], "id": 1}"#; - let deserialized: Result = serde_json::from_str(s); - assert!(deserialized.is_err()); - } - - #[test] - fn call_deserialize() { - let s = r#"{"jsonrpc": "2.0", "method": "update", "params": [1]}"#; - let deserialized: Call = serde_json::from_str(s).unwrap(); - assert_eq!( - deserialized, - Call::Notification(Notification { - jsonrpc: Version::V2, - method: "update".to_owned(), - params: Params::Array(vec![Value::from(1)]) - }) - ); - - let s = r#"{"jsonrpc": "2.0", "method": "update", "params": [1], "id": 1}"#; - let deserialized: Call = serde_json::from_str(s).unwrap(); - assert_eq!( - deserialized, - Call::MethodCall(MethodCall { - jsonrpc: Version::V2, - method: "update".to_owned(), - params: Params::Array(vec![Value::from(1)]), - id: Id::Num(1) - }) - ); - - let s = r#"{"jsonrpc": "2.0", "method": "update", "params": [], "id": 1}"#; - let deserialized: Call = serde_json::from_str(s).unwrap(); - assert_eq!( - deserialized, - Call::MethodCall(MethodCall { - jsonrpc: Version::V2, - method: "update".to_owned(), - params: Params::Array(vec![]), - id: Id::Num(1) - }) - ); - - let s = r#"{"jsonrpc": "2.0", "method": "update", "params": null, "id": 1}"#; - let deserialized: Call = serde_json::from_str(s).unwrap(); - assert_eq!( - deserialized, - Call::MethodCall(MethodCall { - jsonrpc: Version::V2, - method: "update".to_owned(), - params: Params::None, - id: Id::Num(1) - }) - ); - - let s = r#"{"jsonrpc": "2.0", "method": "update", "id": 1}"#; - let deserialized: Call = serde_json::from_str(s).unwrap(); - assert_eq!( - deserialized, - Call::MethodCall(MethodCall { - jsonrpc: Version::V2, - method: "update".to_owned(), - params: Params::None, - id: Id::Num(1) - }) - ); - } - - #[test] - fn request_deserialize_batch() { - let s = r#"[{}, {"jsonrpc": "2.0", "method": "update", "params": [1,2], "id": 1},{"jsonrpc": "2.0", "method": "update", "params": [1]}]"#; - let deserialized: Request = serde_json::from_str(s).unwrap(); - assert_eq!( - deserialized, - Request::Batch(vec![ - Call::Invalid { id: Id::Null }, - Call::MethodCall(MethodCall { - jsonrpc: Version::V2, - method: "update".to_owned(), - params: Params::Array(vec![Value::from(1), Value::from(2)]), - id: Id::Num(1) - }), - Call::Notification(Notification { - jsonrpc: Version::V2, - method: "update".to_owned(), - params: Params::Array(vec![Value::from(1)]) - }) - ]) - ) - } - - #[test] - fn request_invalid_returns_id() { - let s = r#"{"id":120,"method":"my_method","params":["foo", "bar"],"extra_field":[]}"#; - let deserialized: Request = serde_json::from_str(s).unwrap(); - - match deserialized { - Request::Single(Call::Invalid { id: Id::Num(120) }) => {} - _ => panic!("Request wrongly deserialized: {:?}", deserialized), - } - } -} diff --git a/types/src/jsonrpc/response.rs b/types/src/jsonrpc/response.rs deleted file mode 100644 index 61ae5b4c44..0000000000 --- a/types/src/jsonrpc/response.rs +++ /dev/null @@ -1,297 +0,0 @@ -// Copyright 2019 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. - -use super::{Error, Id, JsonValue, Version}; - -use alloc::{ - fmt, - string::{String, ToString as _}, - vec, - vec::Vec, -}; -use core::convert::TryFrom; -use serde::{Deserialize, Serialize}; - -/// JSONRPC response. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -#[serde(untagged)] -pub enum Response { - /// Single response - Single(Output), - /// Response to batch request (batch of responses) - Batch(Vec), - /// Notification to an active subscription. - Notif(SubscriptionNotif), -} - -impl fmt::Display for Response { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", serde_json::to_string(self).expect("Response valid JSON; qed")) - } -} - -/// Successful response -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Success { - /// Protocol version - pub jsonrpc: Version, - /// Result - pub result: JsonValue, - /// Correlation id - pub id: Id, -} - -/// Unsuccessful response -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Failure { - /// Protocol version - pub jsonrpc: Version, - /// Error - pub error: Error, - /// Correlation id - pub id: Id, -} - -/// Represents output - failure or success -#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -#[serde(untagged)] -pub enum Output { - /// Success - Success(Success), - /// Failure - Failure(Failure), -} - -/// Server notification about something the client is subscribed to. -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct SubscriptionNotif { - /// Protocol version - pub jsonrpc: Version, - /// A String containing the name of the method that was used for the subscription. - pub method: String, - /// Parameters of the notification. - pub params: SubscriptionNotifParams, -} - -/// Field of a [`SubscriptionNotif`]. -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct SubscriptionNotifParams { - /// Subscription id, as communicated during the subscription. - pub subscription: SubscriptionId, - /// Actual data that the server wants to communicate to us. - pub result: JsonValue, -} - -/// Id of a subscription, communicated by the server. -#[derive(Debug, PartialEq, Clone, Hash, Eq, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -#[serde(untagged)] -pub enum SubscriptionId { - /// Numeric id - Num(u64), - /// String id - Str(String), -} - -impl Output { - /// Creates new output given `Result`, `Id` and `Version`. - pub fn from(result: Result, id: Id, jsonrpc: Version) -> Self { - match result { - Ok(result) => Output::Success(Success { jsonrpc, result, id }), - Err(error) => Output::Failure(Failure { jsonrpc, error, id }), - } - } - - /// Get the jsonrpc protocol version. - pub fn version(&self) -> Version { - match *self { - Output::Success(ref s) => s.jsonrpc, - Output::Failure(ref f) => f.jsonrpc, - } - } - - /// Get the correlation id. - pub fn id(&self) -> &Id { - match *self { - Output::Success(ref s) => &s.id, - Output::Failure(ref f) => &f.id, - } - } -} - -impl TryFrom for JsonValue { - type Error = Error; - - fn try_from(output: Output) -> Result { - match output { - Output::Success(s) => Ok(s.result), - Output::Failure(f) => Err(f.error), - } - } -} - -impl Response { - /// Creates new `Response` with given error and `Version` - pub fn from(error: impl Into, jsonrpc: Version) -> Self { - Failure { id: Id::Null, jsonrpc, error: error.into() }.into() - } - - /// Deserialize `Response` from given JSON string. - /// - /// This method will handle an empty string as empty batch response. - pub fn from_json(s: &str) -> Result { - if s.is_empty() { - Ok(Response::Batch(vec![])) - } else { - serde_json::from_str(s) - } - } -} - -impl From for Response { - fn from(failure: Failure) -> Self { - Response::Single(Output::Failure(failure)) - } -} - -impl From for Response { - fn from(success: Success) -> Self { - Response::Single(Output::Success(success)) - } -} - -impl SubscriptionId { - /// Turns the subscription ID into a string. - pub fn into_string(self) -> String { - match self { - SubscriptionId::Num(n) => n.to_string(), - SubscriptionId::Str(s) => s, - } - } -} - -#[cfg(test)] -mod tests { - use super::{Error, Failure, Id, Output, Response, Success, Version}; - use serde_json::Value; - - #[test] - fn success_output_serialize() { - let so = Output::Success(Success { jsonrpc: Version::V2, result: Value::from(1), id: Id::Num(1) }); - - let serialized = serde_json::to_string(&so).unwrap(); - assert_eq!(serialized, r#"{"jsonrpc":"2.0","result":1,"id":1}"#); - } - - #[test] - fn success_output_deserialize() { - let dso = r#"{"jsonrpc":"2.0","result":1,"id":1}"#; - - let deserialized: Output = serde_json::from_str(dso).unwrap(); - assert_eq!( - deserialized, - Output::Success(Success { jsonrpc: Version::V2, result: Value::from(1), id: Id::Num(1) }) - ); - } - - #[test] - fn failure_output_serialize() { - let fo = Output::Failure(Failure { jsonrpc: Version::V2, error: Error::parse_error(), id: Id::Num(1) }); - - let serialized = serde_json::to_string(&fo).unwrap(); - assert_eq!(serialized, r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":1}"#); - } - - #[test] - fn failure_output_deserialize() { - let dfo = r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":1}"#; - - let deserialized: Output = serde_json::from_str(dfo).unwrap(); - assert_eq!( - deserialized, - Output::Failure(Failure { jsonrpc: Version::V2, error: Error::parse_error(), id: Id::Num(1) }) - ); - } - - #[test] - fn single_response_deserialize() { - let dsr = r#"{"jsonrpc":"2.0","result":1,"id":1}"#; - - let deserialized: Response = serde_json::from_str(dsr).unwrap(); - assert_eq!( - deserialized, - Response::Single(Output::Success(Success { jsonrpc: Version::V2, result: Value::from(1), id: Id::Num(1) })) - ); - } - - #[test] - fn batch_response_deserialize() { - let dbr = r#"[{"jsonrpc":"2.0","result":1,"id":1},{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":1}]"#; - - let deserialized: Response = serde_json::from_str(dbr).unwrap(); - assert_eq!( - deserialized, - Response::Batch(vec![ - Output::Success(Success { jsonrpc: Version::V2, result: Value::from(1), id: Id::Num(1) }), - Output::Failure(Failure { jsonrpc: Version::V2, error: Error::parse_error(), id: Id::Num(1) }) - ]) - ); - } - - #[test] - fn handle_incorrect_responses() { - let dsr = r#" -{ - "id": 2, - "jsonrpc": "2.0", - "result": "0x62d3776be72cc7fa62cad6fe8ed873d9bc7ca2ee576e400d987419a3f21079d5", - "error": { - "message": "VM Exception while processing transaction: revert", - "code": -32000, - "data": {} - } -}"#; - - let deserialized: Result = serde_json::from_str(dsr); - assert!(deserialized.is_err(), "Expected error when deserializing invalid payload."); - } - - #[test] - fn should_parse_empty_response_as_batch() { - let dsr = r#""#; - - let deserialized1: Result = serde_json::from_str(dsr); - let deserialized2: Result = Response::from_json(dsr); - assert!(deserialized1.is_err(), "Empty string is not valid JSON, so we should get an error."); - assert_eq!(deserialized2.unwrap(), Response::Batch(vec![])); - } -} diff --git a/types/src/jsonrpc/version.rs b/types/src/jsonrpc/version.rs deleted file mode 100644 index 6a95e2f6d9..0000000000 --- a/types/src/jsonrpc/version.rs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2019 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. - -use core::fmt; -use serde::de::{self, Visitor}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; - -/// Protocol version. -#[derive(Debug, PartialEq, Clone, Copy, Hash, Eq)] -pub enum Version { - /// JSONRPC 2.0 - V2, -} - -impl Serialize for Version { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match *self { - Version::V2 => serializer.serialize_str("2.0"), - } - } -} - -impl<'a> Deserialize<'a> for Version { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'a>, - { - deserializer.deserialize_identifier(VersionVisitor) - } -} - -struct VersionVisitor; - -impl<'a> Visitor<'a> for VersionVisitor { - type Value = Version; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a string") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - match value { - "2.0" => Ok(Version::V2), - _ => Err(de::Error::custom("invalid version")), - } - } -} diff --git a/types/src/jsonrpc/wrapped/batch.rs b/types/src/jsonrpc/wrapped/batch.rs deleted file mode 100644 index 051311bda8..0000000000 --- a/types/src/jsonrpc/wrapped/batch.rs +++ /dev/null @@ -1,357 +0,0 @@ -// Copyright 2019 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. - -use crate::jsonrpc::{ - self, - wrapped::{Notification, Params}, -}; - -use alloc::vec::Vec; -use core::{fmt, iter}; -use smallvec::SmallVec; - -/// Batch corresponding to a request from a -/// [`TransportServer`](crate::transport::TransportServer). -/// -/// A [`BatchState`] combines three things: -/// -/// - An incoming batch waiting to be split into requests. -/// - A list of requests that have been extracted from the batch but are yet to be answered. -/// - A list of responses waiting to be sent out. -/// -/// Using the [`BatchState`] is done in the following steps: -/// -/// - Construct a [`BatchState`] from a raw request. -/// - Extract one by one the requests and notifications by calling [`next`](BatchState::next). This -/// moves requests from the batch to the list of requests that are yet to be answered. -/// - Answer these requests by calling [`set_response`](BatchRequest::set_response). -/// - Once all the requests have been answered, call -/// [`into_response`](BatchState::into_response) and send back the response. -/// - Once [`next`](BatchState::next) returns `None` and the response has been extracted, you can -/// destroy the [`BatchState`]. -/// -pub struct BatchState { - /// List of elements to present to the user. - to_yield: SmallVec<[ToYield; 1]>, - - /// List of requests to be answered. When a request is answered, we replace it with `None` so - /// that indices don't change. - requests: SmallVec<[Option; 1]>, - - /// List of pending responses. - responses: SmallVec<[jsonrpc::Output; 1]>, - - /// True if the original request was a batch. We need to keep track of this because we need to - /// respond differently depending on whether we have a single request or a batch with one - /// request. - is_batch: bool, -} - -/// Element remaining to be yielded to the user. -#[derive(Debug)] -enum ToYield { - Notification(jsonrpc::Notification), - Request(jsonrpc::MethodCall), -} - -/// Event generated by the [`next`](BatchState::next) function. -#[derive(Debug)] -pub enum BatchInc<'a> { - /// Request is a notification. - Notification(Notification), - /// Request is a method call. - Request(BatchRequest<'a>), -} - -/// References to a request within the batch that must be answered. -pub struct BatchRequest<'a> { - /// Index within the `BatchState::requests` list. - index: usize, - /// Reference to the actual element. Must always be `Some` for the lifetime of this object. - /// We hold a `&mut Option` rather than a `&mut jsonrpc::MethodCall` so - /// that we can put `None` in it. - elem: &'a mut Option, - /// Reference to the `BatchState::responses` list so that we can push a response. - responses: &'a mut SmallVec<[jsonrpc::Output; 1]>, -} - -impl BatchState { - /// Creates a `BatchState` that will manage the given request. - pub fn from_request(raw_request_body: jsonrpc::Request) -> BatchState { - match raw_request_body { - jsonrpc::Request::Single(rq) => BatchState::from_iter(iter::once(rq), false), - jsonrpc::Request::Batch(requests) => BatchState::from_iter(requests.into_iter(), true), - } - } - - /// Internal implementation of [`from_request`](BatchState::from_request). Generic over the - /// iterator. - fn from_iter(calls_list: impl ExactSizeIterator, is_batch: bool) -> BatchState { - debug_assert!(!(!is_batch && calls_list.len() >= 2)); - - let mut to_yield = SmallVec::with_capacity(calls_list.len()); - let mut responses = SmallVec::with_capacity(calls_list.len()); - let mut num_requests = 0; - - for call in calls_list { - match call { - jsonrpc::Call::MethodCall(call) => { - to_yield.push(ToYield::Request(call)); - num_requests += 1; - } - jsonrpc::Call::Notification(n) => { - to_yield.push(ToYield::Notification(n)); - } - jsonrpc::Call::Invalid { id } => { - let err = Err(jsonrpc::Error::invalid_request()); - let out = jsonrpc::Output::from(err, id, jsonrpc::Version::V2); - responses.push(out); - } - } - } - - BatchState { to_yield, requests: SmallVec::with_capacity(num_requests), responses, is_batch } - } - - /// Returns a request previously returned by [`next_event`](crate::raw::RawServer::next_event) - /// by its id. - /// - /// Note that previous notifications don't have an ID and can't be accessed with this method. - /// - /// Returns `None` if the request ID is invalid or if the request has already been answered in - /// the past. - pub fn request_by_id(&mut self, id: usize) -> Option { - if let Some(elem) = self.requests.get_mut(id) { - if elem.is_none() { - return None; - } - Some(BatchRequest { elem, index: id, responses: &mut self.responses }) - } else { - None - } - } - - /// Extracts the next request from the batch. Returns `None` if the batch is empty. - #[allow(clippy::should_implement_trait)] - pub fn next(&mut self) -> Option { - if self.to_yield.is_empty() { - return None; - } - - match self.to_yield.remove(0) { - ToYield::Notification(n) => Some(BatchInc::Notification(From::from(n))), - ToYield::Request(n) => { - let request_id = self.requests.len(); - self.requests.push(Some(n)); - Some(BatchInc::Request(BatchRequest { - index: request_id, - elem: &mut self.requests[request_id], - responses: &mut self.responses, - })) - } - } - } - - /// Returns true if this batch is ready to send out its response. - pub fn is_ready_to_respond(&self) -> bool { - self.to_yield.is_empty() && self.requests.iter().all(|r| r.is_none()) - } - - /// Turns this batch into a response to send out to the client. - /// - /// Returns `Ok(None)` if there is actually nothing to send to the client, such as when the - /// client has only sent notifications. - pub fn into_response(mut self) -> Result, Self> { - if !self.is_ready_to_respond() { - return Err(self); - } - - let raw_response = if self.is_batch { - let list: Vec<_> = self.responses.drain(..).collect(); - if list.is_empty() { - None - } else { - Some(jsonrpc::Response::Batch(list)) - } - } else { - debug_assert!(self.responses.len() <= 1); - if self.responses.is_empty() { - None - } else { - Some(jsonrpc::Response::Single(self.responses.remove(0))) - } - }; - - Ok(raw_response) - } -} - -impl fmt::Debug for BatchState { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_list() - .entries(self.to_yield.iter()) - .entries(self.requests.iter().filter(|r| r.is_some())) - .entries(self.responses.iter()) - .finish() - } -} - -impl<'a> BatchRequest<'a> { - /// Returns the id of the request within the [`BatchState`]. - /// - /// > **Note**: This is NOT the request id that the client passed. - pub fn id(&self) -> usize { - self.index - } - - /// Returns the id that the client sent out. - pub fn request_id(&self) -> &jsonrpc::Id { - let request = self.elem.as_ref().expect("elem must be Some for the lifetime of the object; qed"); - &request.id - } - - /// Returns the method of this request. - pub fn method(&self) -> &str { - let request = self.elem.as_ref().expect("elem must be Some for the lifetime of the object; qed"); - &request.method - } - - /// Returns the parameters of the request, as a `jsonrpc::Params`. - pub fn params(&self) -> Params { - let request = self.elem.as_ref().expect("elem must be Some for the lifetime of the object; qed"); - Params::from(&request.params) - } - - /// Responds to the request. This destroys the request object, meaning you can no longer - /// retrieve it with [`request_by_id`](BatchState::request_by_id) later anymore. - pub fn set_response(self, response: Result) { - let request = self.elem.take().expect("elem must be Some for the lifetime of the object; qed"); - let response = jsonrpc::Output::from(response, request.id, jsonrpc::Version::V2); - self.responses.push(response); - } -} - -impl<'a> fmt::Debug for BatchRequest<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("BatchRequest").field("method", &self.method()).field("params", &self.params()).finish() - } -} - -#[cfg(test)] -mod tests { - use super::{BatchInc, BatchState}; - use crate::jsonrpc::{self, wrapped::Notification}; - - #[test] - fn basic_notification() { - let notif = jsonrpc::Notification { - jsonrpc: jsonrpc::Version::V2, - method: "foo".to_string(), - params: jsonrpc::Params::None, - }; - - let mut state = { - let rq = jsonrpc::Request::Single(jsonrpc::Call::Notification(notif.clone())); - BatchState::from_request(rq) - }; - - assert!(!state.is_ready_to_respond()); - match state.next() { - Some(BatchInc::Notification(ref n)) if n == &Notification::from(notif) => {} - _ => panic!(), - } - assert!(state.is_ready_to_respond()); - assert!(state.next().is_none()); - match state.into_response() { - Ok(None) => {} - _ => panic!(), - } - } - - #[test] - fn basic_request() { - let call = jsonrpc::MethodCall { - jsonrpc: jsonrpc::Version::V2, - method: "foo".to_string(), - params: jsonrpc::Params::Map(serde_json::from_str("{\"test\":\"foo\"}").unwrap()), - id: jsonrpc::Id::Num(123), - }; - - let mut state = { - let rq = jsonrpc::Request::Single(jsonrpc::Call::MethodCall(call.clone())); - BatchState::from_request(rq) - }; - - assert!(!state.is_ready_to_respond()); - let rq_id = match state.next() { - Some(BatchInc::Request(rq)) => { - assert_eq!(rq.method(), "foo"); - assert_eq!( - { - let v: String = rq.params().get("test").unwrap(); - v - }, - "foo" - ); - assert_eq!(rq.request_id(), &jsonrpc::Id::Num(123)); - rq.id() - } - _ => panic!(), - }; - - assert!(state.next().is_none()); - assert!(!state.is_ready_to_respond()); - assert!(state.next().is_none()); - - assert_eq!(state.request_by_id(rq_id).unwrap().method(), "foo"); - state.request_by_id(rq_id).unwrap().set_response(Err(jsonrpc::Error::method_not_found())); - - assert!(state.is_ready_to_respond()); - assert!(state.next().is_none()); - - match state.into_response() { - Ok(Some(jsonrpc::Response::Single(jsonrpc::Output::Failure(f)))) => { - assert_eq!(f.id, jsonrpc::Id::Num(123)); - } - _ => panic!(), - } - } - - #[test] - fn empty_batch() { - let mut state = { - let rq = jsonrpc::Request::Batch(Vec::new()); - BatchState::from_request(rq) - }; - - assert!(state.is_ready_to_respond()); - assert!(state.next().is_none()); - match state.into_response() { - Ok(None) => {} - _ => panic!(), - } - } -} diff --git a/types/src/jsonrpc/wrapped/batches.rs b/types/src/jsonrpc/wrapped/batches.rs deleted file mode 100644 index 6a9ef9393c..0000000000 --- a/types/src/jsonrpc/wrapped/batches.rs +++ /dev/null @@ -1,400 +0,0 @@ -// Copyright 2019 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. - -use crate::jsonrpc::{ - self, - wrapped::{batch, Notification, Params}, -}; - -use alloc::vec::Vec; -use core::fmt; - -/// Collection of multiple batches. -/// -/// This struct manages the state of the requests that have been received by the server and that -/// are waiting for a response. Due to the batching mechanism in the JSON-RPC protocol, one single -/// message can contain multiple requests and notifications that must all be answered at once. -/// -/// # Usage -/// -/// - Create a new empty [`BatchesState`] with [`new`](BatchesState::new). -/// - Whenever the server receives a JSON message, call [`inject`](BatchesState::inject). -/// - Call [`next_event`](BatchesState::next_event) in a loop and process the events buffered -/// within the object. -/// -/// The [`BatchesState`] also acts as a collection of pending requests, which you can query using -/// [`request_by_id`](BatchesState::request_by_id). -/// -pub struct BatchesState { - /// For each batch, the individual batch's state and the user parameter. - batches: Vec>, - - /// Vacant re-usable indices into `batches`. All indices here must point to a `None`. - vacant: Vec, -} - -/// Event generated by [`next_event`](BatchesState::next_event). -#[derive(Debug)] -pub enum BatchesEvent<'a, T> { - /// A notification has been extracted from a batch. - Notification { - /// Notification in question. - notification: Notification, - /// User parameter passed when calling [`inject`](BatchesState::inject). - user_param: &'a mut T, - }, - - /// A request has been extracted from a batch. - Request(BatchesRequest<'a, T>), - - /// A batch has gotten all its requests answered and a response is ready to be sent out. - ReadyToSend { - /// Response to send out to the JSON-RPC client. - response: jsonrpc::Response, - /// User parameter passed when calling [`inject`](BatchesState::inject). - user_param: T, - }, -} - -/// Request within the batches. -pub struct BatchesRequest<'a, T> { - /// Id of the batch that contains this element. - batch_id: usize, - /// Inner reference to a request within a batch. - request: batch::BatchRequest<'a>, - /// User parameter passed when calling `inject`. - user_param: &'a mut T, -} - -/// Identifier of a request within a [`BatchesState`]. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct BatchesRequestId { - /// Id of the batch within `BatchesState::batches`. - batch_id: usize, - /// Id of the request within the batch. - request_id: usize, -} - -/// Minimal capacity for the `batches` container. -const BATCHES_MIN_CAPACITY: usize = 256; - -impl BatchesState { - /// Creates a new empty `BatchesState`. - pub fn new() -> BatchesState { - BatchesState { - batches: Vec::with_capacity(BATCHES_MIN_CAPACITY), - vacant: Vec::with_capacity(BATCHES_MIN_CAPACITY), - } - } - - /// Processes one step from a batch and returns an event. Returns `None` if there is nothing - /// to do. After you call `inject`, then this will return `Some` at least once. - pub fn next_event(&mut self) -> Option> { - // Note that this function has a complexity of `O(n)`, as we iterate over every single - // batch every single time. This is however the most straight-forward way to implement it, - // and while better strategies might yield better complexities, it might not actually yield - // better performances in real-world situations. More brainstorming and benchmarking could - // get helpful here. - - for (batch_id, entry) in self.batches.iter_mut().enumerate() { - enum WhatCanWeDo { - ReadyToRespond, - Notification(Notification), - Request(usize), - } - - let what_can_we_do = { - // Unwrap the entry, skipping `None`s - let (batch, _) = match entry { - Some(entry) => entry, - None => continue, - }; - - match batch.next() { - None => { - if batch.is_ready_to_respond() { - WhatCanWeDo::ReadyToRespond - } else { - continue; - } - } - Some(batch::BatchInc::Notification(n)) => WhatCanWeDo::Notification(n), - Some(batch::BatchInc::Request(request)) => WhatCanWeDo::Request(request.id()), - } - }; - - match what_can_we_do { - WhatCanWeDo::ReadyToRespond => { - // Here we take ownership of the entry, leaving `None` in its place, so - // we also must store the `batch_id` as being now vacant - self.vacant.push(batch_id); - let (batch, user_param) = entry.take().expect("entry is checked for `None`s above; qed"); - - let response = batch.into_response().expect("is_ready_to_respond returned true; qed"); - if let Some(response) = response { - return Some(BatchesEvent::ReadyToSend { response, user_param }); - } - } - WhatCanWeDo::Notification(notification) => { - let (_, user_param) = entry.as_mut().expect("entry is checked for `None`s above; qed"); - - return Some(BatchesEvent::Notification { notification, user_param }); - } - WhatCanWeDo::Request(id) => { - let (batch, user_param) = entry.as_mut().expect("entry is checked for `None`s above; qed"); - - return Some(BatchesEvent::Request(BatchesRequest { - batch_id, - request: batch.request_by_id(id).unwrap(), - user_param, - })); - } - } - } - - None - } - - /// Injects a newly received batch into the list. You must then call - /// [`next_event`] in order to process it. - pub fn inject(&mut self, request: jsonrpc::Request, user_param: T) { - let batch = batch::BatchState::from_request(request); - - match self.vacant.pop() { - Some(id) => self.batches[id] = Some((batch, user_param)), - None => self.batches.push(Some((batch, user_param))), - } - } - - /// Returns a list of all user data associated to active batches. - pub fn batches(&mut self) -> impl Iterator { - self.batches.iter_mut().filter_map(|entry| entry.as_mut().map(|(_, user_param)| user_param)) - } - - /// Returns a request previously returned by [`next_event`](crate::raw::RawServer::next_event) - /// by its id. - /// - /// Note that previous notifications don't have an ID and can't be accessed with this method. - /// - /// Returns `None` if the request ID is invalid or if the request has already been answered in - /// the past. - pub fn request_by_id(&mut self, id: BatchesRequestId) -> Option> { - if let Some(Some((batch, user_param))) = self.batches.get_mut(id.batch_id) { - Some(BatchesRequest { batch_id: id.batch_id, request: batch.request_by_id(id.request_id)?, user_param }) - } else { - None - } - } -} - -impl Default for BatchesState { - fn default() -> Self { - Self::new() - } -} - -impl fmt::Debug for BatchesState -where - T: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_fmt(format_args!("Batches: {:?} Vacant: {:?}", self.batches, self.vacant)) - } -} - -impl<'a, T> BatchesRequest<'a, T> { - /// Returns the id of the request within the [`BatchesState`]. - /// - /// > **Note**: This is NOT the request id that the client passed. - pub fn id(&self) -> BatchesRequestId { - BatchesRequestId { batch_id: self.batch_id, request_id: self.request.id() } - } - - /// Returns the user parameter passed when calling [`inject`](BatchesState::inject). - pub fn user_param(&mut self) -> &mut T { - &mut self.user_param - } - - /// Returns the id that the client sent out. - pub fn request_id(&self) -> &jsonrpc::Id { - self.request.request_id() - } - - /// Returns the method of this request. - pub fn method(&self) -> &str { - self.request.method() - } - - /// Returns the parameters of the request, as a `jsonrpc::Params`. - pub fn params(&self) -> Params { - self.request.params() - } - - /// Responds to the request. This destroys the request object, meaning you can no longer - /// retrieve it with [`request_by_id`](BatchesState::request_by_id) later anymore. - /// - /// A [`ReadyToSend`](BatchesEvent::ReadyToSend) event containing this response might be - /// generated the next time you call [`next_event`](BatchesState::next_event). - pub fn set_response(self, response: Result) { - self.request.set_response(response) - } -} - -impl<'a, T> fmt::Debug for BatchesRequest<'a, T> -where - T: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("BatchesRequest") - .field("id", &self.id()) - .field("user_param", &self.user_param) - .field("request_id", &self.request_id()) - .field("method", &self.method()) - .field("params", &self.params()) - .finish() - } -} - -#[cfg(test)] -mod tests { - use super::{BatchesEvent, BatchesState}; - use crate::jsonrpc::{self, wrapped::Notification}; - - #[test] - fn basic_notification() { - let notif = jsonrpc::Notification { - jsonrpc: jsonrpc::Version::V2, - method: "foo".to_string(), - params: jsonrpc::Params::None, - }; - - let mut state = BatchesState::new(); - assert!(state.next_event().is_none()); - state.inject(jsonrpc::Request::Single(jsonrpc::Call::Notification(notif.clone())), ()); - match state.next_event() { - Some(BatchesEvent::Notification { ref notification, .. }) if *notification == Notification::from(notif) => { - } - _ => panic!(), - } - assert!(state.next_event().is_none()); - } - - #[test] - fn basic_request() { - let call = jsonrpc::MethodCall { - jsonrpc: jsonrpc::Version::V2, - method: "foo".to_string(), - params: jsonrpc::Params::Map(serde_json::from_str("{\"test\":\"foo\"}").unwrap()), - id: jsonrpc::Id::Num(123), - }; - - let mut state = BatchesState::new(); - assert!(state.next_event().is_none()); - state.inject(jsonrpc::Request::Single(jsonrpc::Call::MethodCall(call)), 8889); - - let rq_id = match state.next_event() { - Some(BatchesEvent::Request(rq)) => { - assert_eq!(rq.method(), "foo"); - assert_eq!( - { - let v: String = rq.params().get("test").unwrap(); - v - }, - "foo" - ); - assert_eq!(rq.request_id(), &jsonrpc::Id::Num(123)); - rq.id() - } - _ => panic!(), - }; - - assert!(state.next_event().is_none()); - - assert_eq!(state.request_by_id(rq_id).unwrap().method(), "foo"); - state.request_by_id(rq_id).unwrap().set_response(Err(jsonrpc::Error::method_not_found())); - assert!(state.request_by_id(rq_id).is_none()); - - match state.next_event() { - Some(BatchesEvent::ReadyToSend { response, user_param }) => { - assert_eq!(user_param, 8889); - match response { - jsonrpc::Response::Single(jsonrpc::Output::Failure(f)) => { - assert_eq!(f.id, jsonrpc::Id::Num(123)); - } - _ => panic!(), - } - } - _ => panic!(), - }; - } - - #[test] - fn empty_batch() { - let mut state = BatchesState::new(); - assert!(state.next_event().is_none()); - state.inject(jsonrpc::Request::Batch(Vec::new()), ()); - assert!(state.next_event().is_none()); - } - - #[test] - fn batch_of_notifs() { - let notif1 = jsonrpc::Notification { - jsonrpc: jsonrpc::Version::V2, - method: "foo".to_string(), - params: jsonrpc::Params::None, - }; - - let notif2 = jsonrpc::Notification { - jsonrpc: jsonrpc::Version::V2, - method: "bar".to_string(), - params: jsonrpc::Params::None, - }; - - let mut state = BatchesState::new(); - assert!(state.next_event().is_none()); - state.inject( - jsonrpc::Request::Batch(vec![ - jsonrpc::Call::Notification(notif1.clone()), - jsonrpc::Call::Notification(notif2.clone()), - ]), - 2, - ); - - match state.next_event() { - Some(BatchesEvent::Notification { ref notification, ref user_param }) - if *notification == Notification::from(notif1) && **user_param == 2 => {} - _ => panic!(), - } - - match state.next_event() { - Some(BatchesEvent::Notification { ref notification, ref user_param }) - if *notification == Notification::from(notif2) && **user_param == 2 => {} - _ => panic!(), - } - - assert!(state.next_event().is_none()); - } -} diff --git a/types/src/jsonrpc/wrapped/mod.rs b/types/src/jsonrpc/wrapped/mod.rs deleted file mode 100644 index f402229f3d..0000000000 --- a/types/src/jsonrpc/wrapped/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -/// Batch. -pub mod batch; -/// Batches -pub mod batches; -mod notification; -mod params; - -pub use notification::Notification; -pub use params::Params; diff --git a/types/src/jsonrpc/wrapped/notification.rs b/types/src/jsonrpc/wrapped/notification.rs deleted file mode 100644 index 750c9e9290..0000000000 --- a/types/src/jsonrpc/wrapped/notification.rs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2019 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. - -use crate::jsonrpc::{self, wrapped::Params}; -use core::fmt; - -/// Notification received on a server. -/// -/// Wraps around a `jsonrpc::Notification`. -#[derive(PartialEq)] -pub struct Notification(jsonrpc::Notification); - -impl From for Notification { - fn from(notif: jsonrpc::Notification) -> Notification { - Notification(notif) - } -} - -impl From for jsonrpc::Notification { - fn from(notif: Notification) -> jsonrpc::Notification { - notif.0 - } -} - -impl Notification { - /// Returns the method of this notification. - pub fn method(&self) -> &str { - &self.0.method - } - - /// Returns the parameters of the notification. - pub fn params(&self) -> Params { - Params::from(&self.0.params) - } -} - -impl fmt::Debug for Notification { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Notification").field("method", &self.method()).field("params", &self.params()).finish() - } -} diff --git a/types/src/jsonrpc/wrapped/params.rs b/types/src/jsonrpc/wrapped/params.rs deleted file mode 100644 index b4a9e42cdf..0000000000 --- a/types/src/jsonrpc/wrapped/params.rs +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2019 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. - -use crate::error::Error; -use crate::jsonrpc; - -use alloc::string::String; -use core::fmt; - -/// Access to the parameters of a request. -#[derive(Copy, Debug, Clone)] -pub struct Params<'a> { - /// Raw parameters of the request. - params: &'a jsonrpc::Params, -} - -/// Key referring to a potential parameter of a request. -pub enum ParamKey<'a> { - /// String key. Only valid when the parameters list is a map. - String(&'a str), - /// Integer key. Only valid when the parameters list is an array. - Index(usize), -} - -impl<'a> Params<'a> { - /// Wraps around a `&jsonrpc::Params` and provides utility functions for the user. - pub(crate) fn from(params: &'a jsonrpc::Params) -> Params<'a> { - Params { params } - } - - /// Returns a parameter of the request by name and decodes it. - /// - /// Returns an error if the parameter doesn't exist or is of the wrong type. - pub fn get<'k, T>(self, param: impl Into>) -> Result - where - T: serde::de::DeserializeOwned, - { - let val = self.get_raw(param).ok_or_else(|| Error::Custom("No such param".into()))?; - serde_json::from_value(val.clone()).map_err(Error::ParseError) - } - - /// Returns a parameter of the request by name. - pub fn get_raw<'k>(self, param: impl Into>) -> Option<&'a jsonrpc::JsonValue> { - match (self.params, param.into()) { - (jsonrpc::Params::None, _) => None, - (jsonrpc::Params::Map(map), ParamKey::String(key)) => map.get(key), - (jsonrpc::Params::Map(_), ParamKey::Index(_)) => None, - (jsonrpc::Params::Array(_), ParamKey::String(_)) => None, - (jsonrpc::Params::Array(array), ParamKey::Index(index)) => { - if index < array.len() { - Some(&array[index]) - } else { - None - } - } - } - } -} - -impl<'a> IntoIterator for Params<'a> { - type Item = Entry<'a>; - type IntoIter = Iter<'a>; - - fn into_iter(self) -> Self::IntoIter { - Iter(match self.params { - jsonrpc::Params::None => IterInner::Empty, - jsonrpc::Params::Array(arr) => IterInner::Array(arr.iter()), - jsonrpc::Params::Map(map) => IterInner::Map(map.iter()), - }) - } -} - -impl<'a> AsRef for Params<'a> { - fn as_ref(&self) -> &jsonrpc::Params { - self.params - } -} - -impl<'a> From> for &'a jsonrpc::Params { - fn from(params: Params<'a>) -> &'a jsonrpc::Params { - params.params - } -} - -impl<'a> From<&'a str> for ParamKey<'a> { - fn from(s: &'a str) -> Self { - ParamKey::String(s) - } -} - -impl<'a> From<&'a String> for ParamKey<'a> { - fn from(s: &'a String) -> Self { - ParamKey::String(&s[..]) - } -} - -impl<'a> From for ParamKey<'a> { - fn from(i: usize) -> Self { - ParamKey::Index(i) - } -} - -impl<'a> fmt::Debug for ParamKey<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - ParamKey::String(s) => fmt::Debug::fmt(s, f), - ParamKey::Index(s) => fmt::Debug::fmt(s, f), - } - } -} - -/// Iterator to all the parameters of a request. -pub struct Iter<'a>(IterInner<'a>); - -enum IterInner<'a> { - Empty, - Map(serde_json::map::Iter<'a>), - Array(std::slice::Iter<'a, serde_json::Value>), -} - -#[derive(Debug)] -pub enum Entry<'a> { - Value(&'a jsonrpc::JsonValue), - KeyValue(ParamKey<'a>, &'a jsonrpc::JsonValue), -} - -impl<'a> Iterator for Iter<'a> { - type Item = Entry<'a>; - - fn next(&mut self) -> Option { - match &mut self.0 { - IterInner::Empty => None, - IterInner::Map(iter) => iter.next().map(|(k, v)| Entry::KeyValue(ParamKey::String(&k[..]), v)), - IterInner::Array(iter) => iter.next().map(|v| Entry::Value(v)), - } - } - - fn size_hint(&self) -> (usize, Option) { - match &self.0 { - IterInner::Empty => (0, Some(0)), - IterInner::Map(iter) => iter.size_hint(), - IterInner::Array(iter) => iter.size_hint(), - } - } -} - -impl<'a> ExactSizeIterator for Iter<'a> {} - -impl<'a> fmt::Debug for Iter<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("ParamsIter").finish() - } -} diff --git a/types/src/lib.rs b/types/src/lib.rs index d770dd070b..2ec38a60a3 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -5,9 +5,6 @@ extern crate alloc; -/// JSON-RPC 2.0 specification related types. -pub mod jsonrpc; - /// JSON-RPC 2.0 specification related types v2. pub mod v2; diff --git a/ws-client/src/lib.rs b/ws-client/src/lib.rs index cd0ce72907..5f66db10b9 100644 --- a/ws-client/src/lib.rs +++ b/ws-client/src/lib.rs @@ -22,8 +22,7 @@ pub use client::{WsClient, WsClientBuilder}; pub use jsonrpsee_types::{ client::Subscription as WsSubscription, error::Error, - jsonrpc, traits::{Client, SubscriptionClient}, v2::dummy::{JsonRpcCall, JsonRpcNotification, JsonRpcParams}, - v2::error, + v2::{error, JsonValue}, }; diff --git a/ws-client/src/tests.rs b/ws-client/src/tests.rs index 5e125be45a..678e76a26f 100644 --- a/ws-client/src/tests.rs +++ b/ws-client/src/tests.rs @@ -111,6 +111,8 @@ async fn is_connected_works() { let client = WsClientBuilder::default().build(&uri).await.unwrap(); assert!(client.is_connected()); client.request::("say_hello", JsonRpcParams::NoParams).await.unwrap_err(); + // give the background thread some time to terminate. + std::thread::sleep(std::time::Duration::from_millis(100)); assert!(!client.is_connected()) } From f2ee2a2f0b45a644469dcf3aed9d059d037ac91f Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 16 Apr 2021 17:27:50 +0200 Subject: [PATCH 22/41] reorg types_v2 mod --- benches/bench.rs | 10 +- examples/http.rs | 2 +- examples/ws.rs | 2 +- examples/ws_subscription.rs | 4 +- http-client/src/client.rs | 22 ++-- http-client/src/lib.rs | 8 +- http-client/src/tests.rs | 14 +-- http-client/src/transport.rs | 2 +- http-server/src/lib.rs | 2 +- http-server/src/module.rs | 3 +- http-server/src/server.rs | 4 +- jsonrpsee/tests/integration_tests.rs | 20 ++-- jsonrpsee/tests/proc_macros.rs | 4 +- proc-macros/src/lib.rs | 20 ++-- types/src/client.rs | 2 +- types/src/error.rs | 15 ++- types/src/lib.rs | 8 +- types/src/traits.rs | 9 +- types/src/v2/dummy.rs | 138 --------------------- types/src/v2/error.rs | 32 +---- types/src/v2/mod.rs | 173 +++++++++++++++++++++++++-- types/src/v2/traits.rs | 8 -- ws-client/src/client.rs | 34 +++--- ws-client/src/helpers.rs | 26 ++-- ws-client/src/lib.rs | 8 +- ws-client/src/manager.rs | 5 +- ws-client/src/tests.rs | 8 +- ws-server/src/server.rs | 14 +-- ws-server/src/server/module.rs | 2 +- 29 files changed, 287 insertions(+), 312 deletions(-) delete mode 100644 types/src/v2/dummy.rs delete mode 100644 types/src/v2/traits.rs diff --git a/benches/bench.rs b/benches/bench.rs index 3dc69ee598..d55d01d572 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -1,6 +1,10 @@ use criterion::*; use jsonrpsee::{ - http_client::{Client, HttpClientBuilder, JsonRpcCall, JsonRpcParams}, + http_client::{ + traits::Client, + v2::{JsonRpcCallSer, JsonRpcParams}, + HttpClientBuilder, + }, ws_client::WsClientBuilder, }; use std::sync::Arc; @@ -11,7 +15,7 @@ mod helpers; criterion_group!(benches, http_requests, websocket_requests, jsonrpsee_types_v2); criterion_main!(benches); -fn v2_serialize(req: JsonRpcCall) -> String { +fn v2_serialize(req: JsonRpcCallSer) -> String { serde_json::to_string(&req).unwrap() } @@ -19,7 +23,7 @@ pub fn jsonrpsee_types_v2(crit: &mut Criterion) { crit.bench_function("jsonrpsee_types_v2", |b| { b.iter(|| { let params = JsonRpcParams::Array(&[1_u64, 2]); - let request = JsonRpcCall::new(0, "say_hello", params); + let request = JsonRpcCallSer::new(0, "say_hello", params); v2_serialize(request); }) }); diff --git a/examples/http.rs b/examples/http.rs index d4880e8943..89aad696fa 100644 --- a/examples/http.rs +++ b/examples/http.rs @@ -25,7 +25,7 @@ // DEALINGS IN THE SOFTWARE. use jsonrpsee::{ - http_client::{Client, HttpClientBuilder, JsonRpcParams}, + http_client::{traits::Client, v2::JsonRpcParams, HttpClientBuilder}, http_server::HttpServerBuilder, }; use std::net::SocketAddr; diff --git a/examples/ws.rs b/examples/ws.rs index ef388958c9..3c6f845147 100644 --- a/examples/ws.rs +++ b/examples/ws.rs @@ -25,7 +25,7 @@ // DEALINGS IN THE SOFTWARE. use jsonrpsee::{ - ws_client::{Client, JsonRpcParams, WsClientBuilder}, + ws_client::{traits::Client, v2::JsonRpcParams, WsClientBuilder}, ws_server::WsServer, }; use std::net::SocketAddr; diff --git a/examples/ws_subscription.rs b/examples/ws_subscription.rs index 9ca7fedf88..9c4369c3eb 100644 --- a/examples/ws_subscription.rs +++ b/examples/ws_subscription.rs @@ -25,7 +25,7 @@ // DEALINGS IN THE SOFTWARE. use jsonrpsee::{ - ws_client::{JsonRpcParams, SubscriptionClient, WsClientBuilder, WsSubscription}, + ws_client::{traits::SubscriptionClient, v2::JsonRpcParams, Subscription, WsClientBuilder}, ws_server::WsServer, }; use std::net::SocketAddr; @@ -40,7 +40,7 @@ async fn main() -> anyhow::Result<()> { let client = WsClientBuilder::default().build(&url).await?; let params: JsonRpcParams = None.into(); - let mut subscribe_hello: WsSubscription = + let mut subscribe_hello: Subscription = client.subscribe("subscribe_hello", params, "unsubscribe_hello").await?; let mut i = 0; diff --git a/http-client/src/client.rs b/http-client/src/client.rs index e1c003a4c7..adb8122fa6 100644 --- a/http-client/src/client.rs +++ b/http-client/src/client.rs @@ -1,13 +1,9 @@ +use crate::traits::Client; use crate::transport::HttpTransportClient; +use crate::v2::{JsonRpcCallSer, JsonRpcErrorAlloc, JsonRpcNotificationSer, JsonRpcParams, JsonRpcResponse}; +use crate::{Error, JsonRawValue}; use async_trait::async_trait; use fnv::FnvHashMap; -use jsonrpsee_types::{ - error::Error, - traits::Client, - v2::dummy::{JsonRpcCall, JsonRpcNotification, JsonRpcParams}, - v2::error::JsonRpcError, - v2::{JsonRpcResponse, RawValue}, -}; use serde::{de::DeserializeOwned, Serialize}; use std::sync::atomic::{AtomicU64, Ordering}; @@ -53,7 +49,7 @@ impl Client for HttpClient { where T: Serialize + std::fmt::Debug + Send + Sync, { - let notif = JsonRpcNotification::new(method, params); + let notif = JsonRpcNotificationSer::new(method, params); self.transport .send(serde_json::to_string(¬if).map_err(Error::ParseError)?) .await @@ -68,7 +64,7 @@ impl Client for HttpClient { { // NOTE: `fetch_add` wraps on overflow which is intended. let id = self.request_id.fetch_add(1, Ordering::Relaxed); - let request = JsonRpcCall::new(id, method, params); + let request = JsonRpcCallSer::new(id, method, params); let body = self .transport @@ -79,7 +75,7 @@ impl Client for HttpClient { let response: JsonRpcResponse<_> = match serde_json::from_slice(&body) { Ok(response) => response, Err(_) => { - let err: JsonRpcError = serde_json::from_slice(&body).map_err(Error::ParseError)?; + let err: JsonRpcErrorAlloc = serde_json::from_slice(&body).map_err(Error::ParseError)?; return Err(Error::Request(err)); } }; @@ -105,7 +101,7 @@ impl Client for HttpClient { for (pos, (method, params)) in batch.into_iter().enumerate() { let id = self.request_id.fetch_add(1, Ordering::SeqCst); - batch_request.push(JsonRpcCall::new(id, method, params)); + batch_request.push(JsonRpcCallSer::new(id, method, params)); ordered_requests.push(id); request_set.insert(id, pos); } @@ -119,7 +115,7 @@ impl Client for HttpClient { let rps: Vec> = match serde_json::from_slice(&body) { Ok(response) => response, Err(_) => { - let err: JsonRpcError = serde_json::from_slice(&body).map_err(Error::ParseError)?; + let err: JsonRpcErrorAlloc = serde_json::from_slice(&body).map_err(Error::ParseError)?; return Err(Error::Request(err)); } }; @@ -138,7 +134,7 @@ impl Client for HttpClient { } } -fn parse_request_id(raw: Option<&RawValue>) -> Result { +fn parse_request_id(raw: Option<&JsonRawValue>) -> Result { match raw { None => Err(Error::InvalidRequestId), Some(id) => { diff --git a/http-client/src/lib.rs b/http-client/src/lib.rs index cb81a9a865..b9f34db6b5 100644 --- a/http-client/src/lib.rs +++ b/http-client/src/lib.rs @@ -43,10 +43,4 @@ mod transport; mod tests; pub use client::{HttpClient, HttpClientBuilder}; - -pub use jsonrpsee_types::{ - error::Error, - traits::Client, - v2::dummy::{JsonRpcCall, JsonRpcNotification, JsonRpcParams}, - v2::JsonValue, -}; +pub use jsonrpsee_types::*; diff --git a/http-client/src/tests.rs b/http-client/src/tests.rs index b7ed7ac653..4316aea79e 100644 --- a/http-client/src/tests.rs +++ b/http-client/src/tests.rs @@ -1,13 +1,11 @@ -use crate::client::HttpClientBuilder; +use crate::v2::error::{ + INTERNAL_ERROR_CODE, INTERNAL_ERROR_MSG, INVALID_PARAMS_CODE, INVALID_PARAMS_MSG, INVALID_REQUEST_CODE, + INVALID_REQUEST_MSG, METHOD_NOT_FOUND_CODE, METHOD_NOT_FOUND_MSG, PARSE_ERROR_CODE, PARSE_ERROR_MSG, +}; +use crate::v2::JsonRpcParams; +use crate::{traits::Client, Error, HttpClientBuilder, JsonValue, Serialize}; use jsonrpsee_test_utils::helpers::*; use jsonrpsee_test_utils::types::Id; -use jsonrpsee_types::{ - error::Error, - traits::Client, - v2::{dummy::JsonRpcParams, error::*}, -}; -use serde::Serialize; -use serde_json::Value as JsonValue; #[tokio::test] async fn method_call_works() { diff --git a/http-client/src/transport.rs b/http-client/src/transport.rs index 11749fa91e..88fb718597 100644 --- a/http-client/src/transport.rs +++ b/http-client/src/transport.rs @@ -6,9 +6,9 @@ // that we need to be guaranteed that hyper doesn't re-use an existing connection if we ever reset // the JSON-RPC request id to a value that might have already been used. +use crate::error::GenericTransportError; use hyper::client::{Client, HttpConnector}; use hyper_rustls::HttpsConnector; -use jsonrpsee_types::error::GenericTransportError; use jsonrpsee_utils::hyper_helpers; use thiserror::Error; diff --git a/http-server/src/lib.rs b/http-server/src/lib.rs index a1307155c8..47e7781cda 100644 --- a/http-server/src/lib.rs +++ b/http-server/src/lib.rs @@ -30,7 +30,7 @@ mod response; mod server; pub use access_control::{AccessControl, AccessControlBuilder, AllowHosts, Host}; -pub use jsonrpsee_types::error::Error; +pub use jsonrpsee_types::Error; pub use module::{RpcContextModule, RpcModule}; pub use server::{Builder as HttpServerBuilder, Server as HttpServer}; diff --git a/http-server/src/module.rs b/http-server/src/module.rs index 0f2df6c832..1427eebcd7 100644 --- a/http-server/src/module.rs +++ b/http-server/src/module.rs @@ -1,5 +1,4 @@ -use jsonrpsee_types::error::Error; -use jsonrpsee_types::v2::{traits::RpcMethod, RpcError, RpcParams}; +use jsonrpsee_types::{error::RpcError, traits::RpcMethod, v2::RpcParams, Error}; use jsonrpsee_utils::server::{send_response, Methods}; use serde::Serialize; use std::sync::Arc; diff --git a/http-server/src/server.rs b/http-server/src/server.rs index b956ed08c3..89eb5c03a1 100644 --- a/http-server/src/server.rs +++ b/http-server/src/server.rs @@ -34,12 +34,12 @@ use hyper::{ service::{make_service_fn, service_fn}, Error as HyperError, }; -use jsonrpsee_types::error::{Error, GenericTransportError}; +use jsonrpsee_types::error::{Error, GenericTransportError, RpcError}; use jsonrpsee_types::v2::error::{ INVALID_REQUEST_CODE, INVALID_REQUEST_MSG, METHOD_NOT_FOUND_CODE, METHOD_NOT_FOUND_MSG, PARSE_ERROR_CODE, PARSE_ERROR_MSG, }; -use jsonrpsee_types::v2::{JsonRpcInvalidRequest, JsonRpcRequest, RpcError, RpcParams}; +use jsonrpsee_types::v2::{JsonRpcInvalidRequest, JsonRpcRequest, RpcParams}; use jsonrpsee_utils::{hyper_helpers::read_response_to_body, server::send_error}; use serde::Serialize; use socket2::{Domain, Socket, Type}; diff --git a/jsonrpsee/tests/integration_tests.rs b/jsonrpsee/tests/integration_tests.rs index f929707516..cdb01eb989 100644 --- a/jsonrpsee/tests/integration_tests.rs +++ b/jsonrpsee/tests/integration_tests.rs @@ -30,8 +30,8 @@ mod helpers; use helpers::{http_server, websocket_server, websocket_server_with_subscription}; use jsonrpsee::{ - http_client::{Client, Error, HttpClientBuilder}, - ws_client::{JsonRpcParams, JsonValue, SubscriptionClient, WsClientBuilder, WsSubscription}, + http_client::{traits::Client, Error, HttpClientBuilder}, + ws_client::{traits::SubscriptionClient, v2::JsonRpcParams, JsonValue, Subscription, WsClientBuilder}, }; use std::sync::Arc; use std::time::Duration; @@ -41,9 +41,9 @@ async fn ws_subscription_works() { let server_addr = websocket_server_with_subscription().await; let server_url = format!("ws://{}", server_addr); let client = WsClientBuilder::default().build(&server_url).await.unwrap(); - let mut hello_sub: WsSubscription = + let mut hello_sub: Subscription = client.subscribe("subscribe_hello", JsonRpcParams::NoParams::, "unsubscribe_hello").await.unwrap(); - let mut foo_sub: WsSubscription = + let mut foo_sub: Subscription = client.subscribe("subscribe_foo", JsonRpcParams::NoParams::, "unsubscribe_foo").await.unwrap(); for _ in 0..10 { @@ -80,9 +80,9 @@ async fn ws_subscription_several_clients() { let mut clients = Vec::with_capacity(10); for _ in 0..10 { let client = WsClientBuilder::default().build(&server_url).await.unwrap(); - let hello_sub: WsSubscription = + let hello_sub: Subscription = client.subscribe("subscribe_hello", JsonRpcParams::NoParams::, "unsubscribe_hello").await.unwrap(); - let foo_sub: WsSubscription = + let foo_sub: Subscription = client.subscribe("subscribe_foo", JsonRpcParams::NoParams::, "unsubscribe_foo").await.unwrap(); clients.push((client, hello_sub, foo_sub)) } @@ -97,9 +97,9 @@ async fn ws_subscription_several_clients_with_drop() { for _ in 0..10 { let client = WsClientBuilder::default().max_notifs_per_subscription(u32::MAX as usize).build(&server_url).await.unwrap(); - let hello_sub: WsSubscription = + let hello_sub: Subscription = client.subscribe("subscribe_hello", JsonRpcParams::NoParams::, "unsubscribe_hello").await.unwrap(); - let foo_sub: WsSubscription = + let foo_sub: Subscription = client.subscribe("subscribe_foo", JsonRpcParams::NoParams::, "unsubscribe_foo").await.unwrap(); clients.push((client, hello_sub, foo_sub)) } @@ -142,7 +142,7 @@ async fn ws_subscription_without_polling_doesnt_make_client_unuseable() { let server_url = format!("ws://{}", server_addr); let client = WsClientBuilder::default().max_notifs_per_subscription(4).build(&server_url).await.unwrap(); - let mut hello_sub: WsSubscription = + let mut hello_sub: Subscription = client.subscribe("subscribe_hello", JsonRpcParams::NoParams::, "unsubscribe_hello").await.unwrap(); // don't poll the subscription stream for 2 seconds, should be full now. @@ -160,7 +160,7 @@ async fn ws_subscription_without_polling_doesnt_make_client_unuseable() { let _hello_req: JsonValue = client.request("say_hello", JsonRpcParams::NoParams::).await.unwrap(); // The same subscription should be possible to register again. - let mut other_sub: WsSubscription = + let mut other_sub: Subscription = client.subscribe("subscribe_hello", JsonRpcParams::NoParams::, "unsubscribe_hello").await.unwrap(); other_sub.next().await.unwrap(); diff --git a/jsonrpsee/tests/proc_macros.rs b/jsonrpsee/tests/proc_macros.rs index 0df7e4a3f6..e8d7971663 100644 --- a/jsonrpsee/tests/proc_macros.rs +++ b/jsonrpsee/tests/proc_macros.rs @@ -1,7 +1,7 @@ // Not all proc macros are used let's suppress it for now. #![allow(dead_code)] -/*mod helpers; +mod helpers; use jsonrpsee::{http_client::*, proc_macros, ws_client::*}; @@ -81,4 +81,4 @@ async fn proc_macros_generic_http_client_api() { assert_eq!(Test2::::foo(&client, 99_u16).await.unwrap(), "hello".to_string()); // TODO: https://github.com/paritytech/jsonrpsee/issues/212 //assert!(Registrar::register_para(&client, 99, "para").await.is_ok()); -}*/ +} diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs index 8d24620e2f..299ce23a7c 100644 --- a/proc-macros/src/lib.rs +++ b/proc-macros/src/lib.rs @@ -234,28 +234,28 @@ fn build_client_functions(api: &api_def::ApiDefinition) -> Result #generated_param_name: impl Into<#ty>)); params_to_json.push(quote_spanned!(pat_span=> map.insert( - #rpc_param_name.to_string(), - #_crate::jsonrpc::to_value(#generated_param_name.into()).map_err(|e| #_crate::Error::Custom(format!("{:?}", e)))? + #rpc_param_name, + #_crate::to_json_value(#generated_param_name.into()).map_err(|e| #_crate::Error::Custom(format!("{:?}", e)))? ); )); params_to_array.push(quote_spanned!(pat_span => - #_crate::jsonrpc::to_value(#generated_param_name.into()).map_err(|e| #_crate::Error::Custom(format!("{:?}", e)))? + #_crate::to_json_value(#generated_param_name.into()).map_err(|e| #_crate::Error::Custom(format!("{:?}", e)))? )); } let params_building = if params_list.is_empty() { - quote! {#_crate::jsonrpc::Params::None} + quote! {None.into()} } else if function.attributes.positional_params { quote_spanned!(function.signature.span()=> - #_crate::jsonrpc::Params::Array(vec![ + #_crate::v2::JsonRpcParams::Array(&[ #(#params_to_array),* ]) ) } else { let params_list_len = params_list.len(); quote_spanned!(function.signature.span()=> - #_crate::jsonrpc::Params::Map({ - let mut map = #_crate::jsonrpc::JsonMap::with_capacity(#params_list_len); + #_crate::v2::JsonRpcParams::Map({ + let mut map = alloc:collections::BTreeMap::with_capacity(#params_list_len); #(#params_to_json)* map }) @@ -274,10 +274,10 @@ fn build_client_functions(api: &api_def::ApiDefinition) -> Result - #visibility async fn #f_name (client: &impl #_crate::Client #(, #params_list)*) -> core::result::Result<#ret_ty, #_crate::Error> + #visibility async fn #f_name (client: &impl #_crate::traits::Client #(, #params_list)*) -> core::result::Result<#ret_ty, #_crate::Error> where - #ret_ty: #_crate::jsonrpc::DeserializeOwned - #(, #params_tys: #_crate::jsonrpc::Serialize)* + #ret_ty: #_crate::DeserializeOwned + #(, #params_tys: #_crate::Serialize)* { #function_body } diff --git a/types/src/client.rs b/types/src/client.rs index 58c00ea083..5b4f299296 100644 --- a/types/src/client.rs +++ b/types/src/client.rs @@ -1,4 +1,4 @@ -use crate::{error::Error, v2::dummy::SubscriptionId}; +use crate::{v2::SubscriptionId, Error}; use core::marker::PhantomData; use futures::channel::{mpsc, oneshot}; use futures::prelude::*; diff --git a/types/src/error.rs b/types/src/error.rs index 45131cdc2f..521a86fd9c 100644 --- a/types/src/error.rs +++ b/types/src/error.rs @@ -1,6 +1,17 @@ -use crate::v2::error::JsonRpcError; +use crate::v2::JsonRpcErrorAlloc; use std::fmt; +/// Error. +#[derive(thiserror::Error, Debug)] +pub enum RpcError { + /// Unknown error. + #[error("unknown rpc error")] + Unknown, + /// Invalid params in the RPC call. + #[error("invalid params")] + InvalidParams, +} + /// Convenience type for displaying errors. #[derive(Clone, Debug, PartialEq)] pub struct Mismatch { @@ -24,7 +35,7 @@ pub enum Error { TransportError(#[source] Box), /// JSON-RPC request error. #[error("JSON-RPC request error: {0:?}")] - Request(#[source] JsonRpcError), + Request(#[source] JsonRpcErrorAlloc), /// Frontend/backend channel error. #[error("Frontend/backend channel error: {0}")] Internal(#[source] futures::channel::mpsc::SendError), diff --git a/types/src/lib.rs b/types/src/lib.rs index 2ec38a60a3..72a9fa3745 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -12,7 +12,13 @@ pub mod v2; pub mod error; /// Shared client types. -pub mod client; +mod client; /// Traits pub mod traits; + +pub use beef::Cow; +pub use client::*; +pub use error::Error; +pub use serde::{de::DeserializeOwned, Serialize}; +pub use serde_json::{to_value as to_json_value, value::RawValue as JsonRawValue, Value as JsonValue}; diff --git a/types/src/traits.rs b/types/src/traits.rs index b662a2b931..28c8b67aee 100644 --- a/types/src/traits.rs +++ b/types/src/traits.rs @@ -1,5 +1,5 @@ -use crate::error::Error; -use crate::{client::Subscription, v2::dummy::JsonRpcParams}; +use crate::v2::{JsonRpcParams, RpcParams}; +use crate::{error::RpcError, Error, Subscription}; use async_trait::async_trait; use serde::{de::DeserializeOwned, Serialize}; @@ -50,3 +50,8 @@ pub trait SubscriptionClient: Client { Notif: DeserializeOwned, T: Serialize + std::fmt::Debug + Send + Sync; } + +/// JSON-RPC server interface for managing method calls. +pub trait RpcMethod: Fn(RpcParams) -> Result + Send + Sync + 'static {} + +impl RpcMethod for T where T: Fn(RpcParams) -> Result + Send + Sync + 'static {} diff --git a/types/src/v2/dummy.rs b/types/src/v2/dummy.rs deleted file mode 100644 index 34d324ce9c..0000000000 --- a/types/src/v2/dummy.rs +++ /dev/null @@ -1,138 +0,0 @@ -//! Client side JSON-RPC types. - -use crate::v2::TwoPointZero; -use alloc::collections::BTreeMap; -use serde::{Deserialize, Serialize}; - -/// [JSON-RPC parameters](https://www.jsonrpc.org/specification#parameter_structures) -#[derive(Serialize, Debug)] -#[serde(untagged)] -pub enum JsonRpcParams<'a, T> -where - T: Serialize + std::fmt::Debug, -{ - /// No params. - NoParams, - /// Positional params. - Array(&'a [T]), - /// Params by name. - // - // TODO(niklasad1): maybe take a reference here but BTreeMap needs allocation anyway. - Map(BTreeMap<&'a str, &'a T>), -} - -// TODO(niklasad1): this is a little weird but nice if `None.into()` works. -impl<'a, T> From> for JsonRpcParams<'a, T> -where - T: Serialize + std::fmt::Debug, -{ - fn from(_raw: Option<&'a T>) -> Self { - Self::NoParams - } -} - -impl<'a, T> From> for JsonRpcParams<'a, T> -where - T: Serialize + std::fmt::Debug, -{ - fn from(map: BTreeMap<&'a str, &'a T>) -> Self { - Self::Map(map) - } -} - -impl<'a, T> From<&'a [T]> for JsonRpcParams<'a, T> -where - T: Serialize + std::fmt::Debug, -{ - fn from(arr: &'a [T]) -> Self { - Self::Array(arr) - } -} - -/// Serializable [JSON-RPC object](https://www.jsonrpc.org/specification#request-object) -#[derive(Serialize, Debug)] -pub struct JsonRpcCall<'a, T> -where - T: Serialize + std::fmt::Debug, -{ - /// JSON-RPC version. - pub jsonrpc: TwoPointZero, - /// Name of the method to be invoked. - pub method: &'a str, - /// Request ID - pub id: u64, - /// Parameter values of the request. - pub params: JsonRpcParams<'a, T>, -} - -impl<'a, T> JsonRpcCall<'a, T> -where - T: Serialize + std::fmt::Debug, -{ - /// Create a new serializable JSON-RPC request. - pub fn new(id: u64, method: &'a str, params: JsonRpcParams<'a, T>) -> Self { - Self { jsonrpc: TwoPointZero, id, method, params } - } -} - -/// Serializable [JSON-RPC notification object](https://www.jsonrpc.org/specification#request-object) -#[derive(Serialize, Debug)] -pub struct JsonRpcNotification<'a, T> -where - T: Serialize + std::fmt::Debug, -{ - /// JSON-RPC version. - pub jsonrpc: TwoPointZero, - /// Name of the method to be invoked. - pub method: &'a str, - /// Parameter values of the request. - pub params: JsonRpcParams<'a, T>, -} - -impl<'a, T> JsonRpcNotification<'a, T> -where - T: Serialize + std::fmt::Debug, -{ - /// Create a new serializable JSON-RPC request. - pub fn new(method: &'a str, params: JsonRpcParams<'a, T>) -> Self { - Self { jsonrpc: TwoPointZero, method, params } - } -} - -/// JSON-RPC parameter values for subscriptions. -#[derive(Deserialize, Debug)] -pub struct JsonRpcNotificationParams { - /// Subscription ID - pub subscription: SubscriptionId, - /// Result. - pub result: T, -} - -/// JSON-RPC notification response. -// NOTE(niklasad1): basically the same as Maciej version but I wanted to support Strings too. -// Maybe make subscription ID generic?! -#[derive(Deserialize, Debug)] -pub struct JsonRpcResponseNotif { - /// JSON-RPC version. - pub jsonrpc: TwoPointZero, - /// Params. - pub params: JsonRpcNotificationParams, -} - -/// Id of a subscription, communicated by the server. -#[derive(Debug, PartialEq, Clone, Hash, Eq, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -#[serde(untagged)] -pub enum SubscriptionId { - /// Numeric id - Num(u64), - /// String id - Str(String), -} - -#[cfg(test)] -mod tests { - - #[test] - fn deser_error() {} -} diff --git a/types/src/v2/error.rs b/types/src/v2/error.rs index 1b85f82818..3511944dfc 100644 --- a/types/src/v2/error.rs +++ b/types/src/v2/error.rs @@ -1,38 +1,8 @@ -use super::TwoPointZero; +use crate::JsonValue; use serde::{de::Deserializer, ser::Serializer, Deserialize}; -use serde_json::Value as JsonValue; use std::fmt; use thiserror::Error; -/// Error. -#[derive(Error, Debug)] -pub enum RpcError { - /// Unknown error. - #[error("unknown rpc error")] - Unknown, - /// Invalid params in the RPC call. - #[error("invalid params")] - InvalidParams, -} - -/// [Failed JSON-RPC response object](https://www.jsonrpc.org/specification#response_object). -#[derive(Error, Debug, Deserialize, PartialEq)] -pub struct JsonRpcError { - /// JSON-RPC version. - pub jsonrpc: TwoPointZero, - #[serde(rename = "error")] - /// Error object. - pub inner: JsonRpcErrorObject, - /// Request ID. - pub id: u64, -} - -impl fmt::Display for JsonRpcError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.inner) - } -} - /// [JSON-RPC Error object](https://www.jsonrpc.org/specification#error_object) #[derive(Error, Debug, PartialEq, Deserialize)] #[serde(deny_unknown_fields)] diff --git a/types/src/v2/mod.rs b/types/src/v2/mod.rs index 5369ba679e..d30f6aaa29 100644 --- a/types/src/v2/mod.rs +++ b/types/src/v2/mod.rs @@ -1,20 +1,14 @@ -use serde::de::{self, Deserializer, Unexpected, Visitor}; +use crate::{error::RpcError, Cow, Error}; +use alloc::collections::BTreeMap; +use serde::de::{self, DeserializeOwned, Deserializer, Unexpected, Visitor}; use serde::ser::Serializer; use serde::{Deserialize, Serialize}; +use serde_json::value::RawValue; use std::fmt; +use thiserror::Error; -/// Niklas dummy types -pub mod dummy; -/// Error type. +/// JSON-RPC related error types. pub mod error; -/// Traits. -pub mod traits; - -// TODO: revisit re-exports. -pub use beef::lean::Cow; -pub use error::RpcError; -pub use serde_json::value::{to_raw_value, RawValue}; -pub use serde_json::Value as JsonValue; /// [JSON-RPC request object](https://www.jsonrpc.org/specification#request-object) #[derive(Deserialize, Debug)] @@ -85,6 +79,24 @@ pub struct JsonRpcError<'a> { pub id: Option<&'a RawValue>, } +/// [Failed JSON-RPC response object](https://www.jsonrpc.org/specification#response_object). +#[derive(Error, Debug, Deserialize, PartialEq)] +pub struct JsonRpcErrorAlloc { + /// JSON-RPC version. + pub jsonrpc: TwoPointZero, + #[serde(rename = "error")] + /// Error object. + pub inner: error::JsonRpcErrorObject, + /// Request ID. + pub id: u64, +} + +impl fmt::Display for JsonRpcErrorAlloc { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.inner) + } +} + /// [JSON-RPC error object](https://www.jsonrpc.org/specification#error-object) #[derive(Serialize, Debug)] pub struct JsonRpcErrorParams<'a> { @@ -165,3 +177,140 @@ impl<'a> RpcParams<'a> { self.parse::<[T; 1]>().map(|[res]| res) } } + +/// [JSON-RPC parameters](https://www.jsonrpc.org/specification#parameter_structures) +#[derive(Serialize, Debug)] +#[serde(untagged)] +pub enum JsonRpcParams<'a, T> +where + T: Serialize + std::fmt::Debug, +{ + /// No params. + NoParams, + /// Positional params. + Array(&'a [T]), + /// Params by name. + // + // TODO(niklasad1): maybe take a reference here but BTreeMap needs allocation anyway. + Map(BTreeMap<&'a str, &'a T>), +} + +// TODO(niklasad1): this is a little weird but nice if `None.into()` works. +impl<'a, T> From> for JsonRpcParams<'a, T> +where + T: Serialize + std::fmt::Debug, +{ + fn from(_raw: Option<&'a T>) -> Self { + Self::NoParams + } +} + +impl<'a, T> From> for JsonRpcParams<'a, T> +where + T: Serialize + std::fmt::Debug, +{ + fn from(map: BTreeMap<&'a str, &'a T>) -> Self { + Self::Map(map) + } +} + +impl<'a, T> From<&'a [T]> for JsonRpcParams<'a, T> +where + T: Serialize + std::fmt::Debug, +{ + fn from(arr: &'a [T]) -> Self { + Self::Array(arr) + } +} + +/// Serializable [JSON-RPC object](https://www.jsonrpc.org/specification#request-object) +#[derive(Serialize, Debug)] +pub struct JsonRpcCallSer<'a, T> +where + T: Serialize + std::fmt::Debug, +{ + /// JSON-RPC version. + pub jsonrpc: TwoPointZero, + /// Name of the method to be invoked. + pub method: &'a str, + /// Request ID + pub id: u64, + /// Parameter values of the request. + pub params: JsonRpcParams<'a, T>, +} + +impl<'a, T> JsonRpcCallSer<'a, T> +where + T: Serialize + std::fmt::Debug, +{ + /// Create a new serializable JSON-RPC request. + pub fn new(id: u64, method: &'a str, params: JsonRpcParams<'a, T>) -> Self { + Self { jsonrpc: TwoPointZero, id, method, params } + } +} + +/// Serializable [JSON-RPC notification object](https://www.jsonrpc.org/specification#request-object) +#[derive(Serialize, Debug)] +pub struct JsonRpcNotificationSer<'a, T> +where + T: Serialize + std::fmt::Debug, +{ + /// JSON-RPC version. + pub jsonrpc: TwoPointZero, + /// Name of the method to be invoked. + pub method: &'a str, + /// Parameter values of the request. + pub params: JsonRpcParams<'a, T>, +} + +impl<'a, T> JsonRpcNotificationSer<'a, T> +where + T: Serialize + std::fmt::Debug, +{ + /// Create a new serializable JSON-RPC request. + pub fn new(method: &'a str, params: JsonRpcParams<'a, T>) -> Self { + Self { jsonrpc: TwoPointZero, method, params } + } +} + +/// JSON-RPC parameter values for subscriptions. +#[derive(Deserialize, Debug)] +pub struct JsonRpcNotificationParamsAlloc { + /// Subscription ID + pub subscription: SubscriptionId, + /// Result. + pub result: T, +} + +/// JSON-RPC notification response. +// NOTE(niklasad1): basically the same as Maciej version but I wanted to support Strings too. +// Maybe make subscription ID generic?! +#[derive(Deserialize, Debug)] +pub struct JsonRpcNotifAlloc { + /// JSON-RPC version. + pub jsonrpc: TwoPointZero, + /// Params. + pub params: JsonRpcNotificationParamsAlloc, +} + +/// Id of a subscription, communicated by the server. +#[derive(Debug, PartialEq, Clone, Hash, Eq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +#[serde(untagged)] +pub enum SubscriptionId { + /// Numeric id + Num(u64), + /// String id + Str(String), +} + +/// Parse request ID from RawValue. +pub fn parse_request_id(raw: Option<&RawValue>) -> Result { + match raw { + None => Err(Error::InvalidRequestId), + Some(v) => { + let val = serde_json::from_str(v.get()).map_err(Error::ParseError)?; + Ok(val) + } + } +} diff --git a/types/src/v2/traits.rs b/types/src/v2/traits.rs deleted file mode 100644 index f007f6fa72..0000000000 --- a/types/src/v2/traits.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! Module for shared traits in JSON-RPC related types. - -use super::{error::RpcError, RpcParams}; - -/// RPC Call. -pub trait RpcMethod: Fn(RpcParams) -> Result + Send + Sync + 'static {} - -impl RpcMethod for T where T: Fn(RpcParams) -> Result + Send + Sync + 'static {} diff --git a/ws-client/src/client.rs b/ws-client/src/client.rs index 274f35d0a3..88bb53d2d6 100644 --- a/ws-client/src/client.rs +++ b/ws-client/src/client.rs @@ -28,8 +28,14 @@ use crate::helpers::{ build_unsubscribe_message, process_batch_response, process_error_response, process_single_response, process_subscription_response, stop_subscription, }; -use crate::manager::RequestManager; +use crate::traits::{Client, SubscriptionClient}; use crate::transport::{parse_url, Receiver as WsReceiver, Sender as WsSender, WsTransportClientBuilder}; +use crate::v2::{ + JsonRpcCallSer, JsonRpcErrorAlloc, JsonRpcNotifAlloc, JsonRpcNotificationSer, JsonRpcParams, JsonRpcResponse, +}; +use crate::{ + manager::RequestManager, BatchMessage, Error, FrontToBack, RequestMessage, Subscription, SubscriptionMessage, +}; use async_std::sync::Mutex; use async_trait::async_trait; use futures::{ @@ -38,14 +44,6 @@ use futures::{ prelude::*, sink::SinkExt, }; -use jsonrpsee_types::{ - client::{BatchMessage, FrontToBack, RequestMessage, Subscription, SubscriptionMessage}, - error::Error, - traits::{Client, SubscriptionClient}, - v2::dummy::{JsonRpcCall, JsonRpcNotification, JsonRpcParams, JsonRpcResponseNotif}, - v2::error::JsonRpcError, - v2::JsonRpcResponse, -}; use serde::{de::DeserializeOwned, Serialize}; use std::{ borrow::Cow, @@ -295,9 +293,13 @@ impl Client for WsClient { T: Serialize + std::fmt::Debug + Send + Sync, { log::trace!("[frontend]: send notification: method={:?}, params={:?}", method, params); - let notif = JsonRpcNotification::new(method, params); + // NOTE: we use this to guard against max number of concurrent requests. + let _req_id = self.id_guard.next_request_id()?; + let notif = JsonRpcNotificationSer::new(method, params); let raw = serde_json::to_string(¬if).map_err(Error::ParseError)?; - match self.to_back.clone().send(FrontToBack::Notification(raw)).await { + let res = self.to_back.clone().send(FrontToBack::Notification(raw)).await; + self.id_guard.reclaim_request_id(); + match res { Ok(()) => Ok(()), Err(_) => Err(self.read_error_from_backend().await), } @@ -311,7 +313,7 @@ impl Client for WsClient { log::trace!("[frontend]: send request: method={:?}, params={:?}", method, params); let (send_back_tx, send_back_rx) = oneshot::channel(); let req_id = self.id_guard.next_request_id()?; - let raw = serde_json::to_string(&JsonRpcCall::new(req_id, method, params)).map_err(Error::ParseError)?; + let raw = serde_json::to_string(&JsonRpcCallSer::new(req_id, method, params)).map_err(Error::ParseError)?; if self .to_back @@ -353,7 +355,7 @@ impl Client for WsClient { let mut batches = Vec::with_capacity(batch.len()); for (idx, (method, params)) in batch.into_iter().enumerate() { - batches.push(JsonRpcCall::new(batch_ids[idx], method, params)); + batches.push(JsonRpcCallSer::new(batch_ids[idx], method, params)); } let (send_back_tx, send_back_rx) = oneshot::channel(); @@ -410,7 +412,7 @@ impl SubscriptionClient for WsClient { let ids = self.id_guard.next_request_ids(2)?; let raw = - serde_json::to_string(&JsonRpcCall::new(ids[0], subscribe_method, params)).map_err(Error::ParseError)?; + serde_json::to_string(&JsonRpcCallSer::new(ids[0], subscribe_method, params)).map_err(Error::ParseError)?; let (send_back_tx, send_back_rx) = oneshot::channel(); if self @@ -547,7 +549,7 @@ async fn background_task( } } // Subscription response. - else if let Ok(notif) = serde_json::from_slice::>(&raw) { + else if let Ok(notif) = serde_json::from_slice::>(&raw) { log::debug!("[backend]: recv subscription {:?}", notif); if let Err(Some(unsub)) = process_subscription_response(&mut manager, notif) { let _ = stop_subscription(&mut sender, &mut manager, unsub).await; @@ -562,7 +564,7 @@ async fn background_task( } } // Error response - else if let Ok(err) = serde_json::from_slice::(&raw) { + else if let Ok(err) = serde_json::from_slice::(&raw) { log::debug!("[backend]: recv error response {:?}", err); if let Err(e) = process_error_response(&mut manager, err) { let _ = front_error.send(e); diff --git a/ws-client/src/helpers.rs b/ws-client/src/helpers.rs index 448f3e1f8a..8c71084460 100644 --- a/ws-client/src/helpers.rs +++ b/ws-client/src/helpers.rs @@ -2,11 +2,11 @@ use crate::manager::{RequestManager, RequestStatus}; use crate::transport::Sender as WsSender; use futures::channel::mpsc; use jsonrpsee_types::{ - client::RequestMessage, - error::Error, - v2::dummy::{JsonRpcCall, JsonRpcParams, JsonRpcResponseNotif, SubscriptionId}, - v2::error::JsonRpcError, - v2::{JsonRpcResponse, RawValue}, + v2::{ + parse_request_id, JsonRpcCallSer, JsonRpcErrorAlloc, JsonRpcNotifAlloc, JsonRpcParams, JsonRpcResponse, + SubscriptionId, + }, + Error, RequestMessage, }; use serde_json::Value as JsonValue; @@ -52,7 +52,7 @@ pub fn process_batch_response<'a>( /// Returns `Err(Some(msg))` if the subscription was full. pub fn process_subscription_response( manager: &mut RequestManager, - notif: JsonRpcResponseNotif, + notif: JsonRpcNotifAlloc, ) -> Result<(), Option> { let sub_id = notif.params.subscription; let request_id = match manager.get_request_id_by_subscription_id(&sub_id) { @@ -153,25 +153,15 @@ pub fn build_unsubscribe_message( } // TODO(niklasad): better type for params or maybe a macro?!. let params = JsonRpcParams::Array(sub_id_slice); - let raw = serde_json::to_string(&JsonRpcCall::new(unsub_req_id, &unsub, params)).unwrap(); + let raw = serde_json::to_string(&JsonRpcCallSer::new(unsub_req_id, &unsub, params)).unwrap(); Some(RequestMessage { raw, id: unsub_req_id, send_back: None }) } -fn parse_request_id(raw: Option<&RawValue>) -> Result { - match raw { - None => Err(Error::InvalidRequestId), - Some(id) => { - let id = serde_json::from_str(id.get()).map_err(Error::ParseError)?; - Ok(id) - } - } -} - /// Attempts to process an error response. /// /// Returns `Ok` if the response was successfully sent. /// Returns `Err(_)` if the response ID was not found. -pub fn process_error_response(manager: &mut RequestManager, err: JsonRpcError) -> Result<(), Error> { +pub fn process_error_response(manager: &mut RequestManager, err: JsonRpcErrorAlloc) -> Result<(), Error> { match manager.request_status(&err.id) { RequestStatus::PendingMethodCall => { let send_back = manager.complete_pending_call(err.id).expect("State checked above; qed"); diff --git a/ws-client/src/lib.rs b/ws-client/src/lib.rs index 5f66db10b9..116ea99cb7 100644 --- a/ws-client/src/lib.rs +++ b/ws-client/src/lib.rs @@ -19,10 +19,4 @@ pub mod transport; mod tests; pub use client::{WsClient, WsClientBuilder}; -pub use jsonrpsee_types::{ - client::Subscription as WsSubscription, - error::Error, - traits::{Client, SubscriptionClient}, - v2::dummy::{JsonRpcCall, JsonRpcNotification, JsonRpcParams}, - v2::{error, JsonValue}, -}; +pub use jsonrpsee_types::*; diff --git a/ws-client/src/manager.rs b/ws-client/src/manager.rs index 041758a604..2a577d3d52 100644 --- a/ws-client/src/manager.rs +++ b/ws-client/src/manager.rs @@ -8,8 +8,7 @@ use fnv::FnvHashMap; use futures::channel::{mpsc, oneshot}; -use jsonrpsee_types::{error::Error, v2::dummy::SubscriptionId}; -use serde_json::Value as JsonValue; +use jsonrpsee_types::{v2::SubscriptionId, Error, JsonValue}; use std::collections::hash_map::{Entry, HashMap}; #[derive(Debug)] @@ -248,7 +247,7 @@ impl RequestManager { mod tests { use super::{Error, RequestManager}; use futures::channel::{mpsc, oneshot}; - use jsonrpsee_types::v2::dummy::SubscriptionId; + use jsonrpsee_types::v2::SubscriptionId; use serde_json::Value as JsonValue; #[test] diff --git a/ws-client/src/tests.rs b/ws-client/src/tests.rs index 678e76a26f..bba4e5d615 100644 --- a/ws-client/src/tests.rs +++ b/ws-client/src/tests.rs @@ -1,6 +1,10 @@ #![cfg(test)] -use crate::{error::*, Client, Error, JsonRpcParams, SubscriptionClient, WsClientBuilder, WsSubscription}; +use crate::v2::{error::*, JsonRpcParams}; +use crate::{ + traits::{Client, SubscriptionClient}, + Error, Subscription, WsClientBuilder, +}; use jsonrpsee_test_utils::helpers::*; use jsonrpsee_test_utils::types::{Id, WebSocketTestServer}; use serde::Serialize; @@ -68,7 +72,7 @@ async fn subscription_works() { let uri = to_ws_uri_string(server.local_addr()); let client = WsClientBuilder::default().build(&uri).await.unwrap(); { - let mut sub: WsSubscription = + let mut sub: Subscription = client.subscribe("subscribe_hello", JsonRpcParams::NoParams::, "unsubscribe_hello").await.unwrap(); let response: String = sub.next().await.unwrap().into(); assert_eq!("hello my friend".to_owned(), response); diff --git a/ws-server/src/server.rs b/ws-server/src/server.rs index d00cc012f5..47404383fa 100644 --- a/ws-server/src/server.rs +++ b/ws-server/src/server.rs @@ -28,10 +28,6 @@ use futures::{ channel::mpsc, io::{BufReader, BufWriter}, }; -use jsonrpsee_types::{ - error::Error, - v2::error::{INVALID_REQUEST_CODE, INVALID_REQUEST_MSG, PARSE_ERROR_CODE, PARSE_ERROR_MSG}, -}; use parking_lot::Mutex; use rustc_hash::FxHashMap; use serde::Serialize; @@ -43,9 +39,13 @@ use tokio::net::{TcpListener, ToSocketAddrs}; use tokio_stream::{wrappers::TcpListenerStream, StreamExt}; use tokio_util::compat::TokioAsyncReadCompatExt; -use jsonrpsee_types::v2::error::{METHOD_NOT_FOUND_CODE, METHOD_NOT_FOUND_MSG}; -use jsonrpsee_types::v2::{JsonRpcInvalidRequest, JsonRpcRequest, RpcError, RpcParams, TwoPointZero}; -use jsonrpsee_types::v2::{JsonRpcNotification, JsonRpcNotificationParams}; +use jsonrpsee_types::error::{Error, RpcError}; +use jsonrpsee_types::v2::error::{ + INVALID_REQUEST_CODE, INVALID_REQUEST_MSG, METHOD_NOT_FOUND_CODE, METHOD_NOT_FOUND_MSG, PARSE_ERROR_CODE, + PARSE_ERROR_MSG, +}; +use jsonrpsee_types::v2::{JsonRpcInvalidRequest, JsonRpcRequest, TwoPointZero}; +use jsonrpsee_types::v2::{JsonRpcNotification, JsonRpcNotificationParams, RpcParams}; use jsonrpsee_utils::server::{send_error, ConnectionId, Methods}; mod module; diff --git a/ws-server/src/server/module.rs b/ws-server/src/server/module.rs index 6a33bb116e..890f271ba5 100644 --- a/ws-server/src/server/module.rs +++ b/ws-server/src/server/module.rs @@ -1,6 +1,6 @@ use crate::server::{RpcError, RpcParams, SubscriptionId, SubscriptionSink}; use jsonrpsee_types::error::Error; -use jsonrpsee_types::v2::traits::RpcMethod; +use jsonrpsee_types::traits::RpcMethod; use jsonrpsee_utils::server::{send_response, Methods}; use parking_lot::Mutex; use rustc_hash::FxHashMap; From 26e2425b11771d729329ab8deac0dd9bb7406882 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 16 Apr 2021 19:07:16 +0200 Subject: [PATCH 23/41] port macros to new types --- examples/Cargo.toml | 5 +++ examples/http.rs | 2 +- examples/proc_macro.rs | 75 ++++++++++++++++++++++++++++++++++ http-client/src/client.rs | 6 +-- jsonrpsee/tests/proc_macros.rs | 8 ++-- proc-macros/src/lib.rs | 14 +++---- types/src/traits.rs | 8 ++-- types/src/v2/mod.rs | 42 ++++--------------- ws-client/src/client.rs | 12 +++--- 9 files changed, 114 insertions(+), 58 deletions(-) create mode 100644 examples/proc_macro.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 4a98b4831d..21896ba754 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -11,6 +11,7 @@ anyhow = "1" env_logger = "0.8" futures = "0.3" jsonrpsee = { path = "../jsonrpsee", features = ["full"] } +jsonrpsee-ws-client = { path = "../ws-client" } log = "0.4" tokio = { version = "1", features = ["full"] } @@ -25,3 +26,7 @@ path = "ws.rs" [[example]] name = "ws_subscription" path = "ws_subscription.rs" + +[[example]] +name = "proc_macro" +path = "proc_macro.rs" diff --git a/examples/http.rs b/examples/http.rs index 89aad696fa..2fbef28483 100644 --- a/examples/http.rs +++ b/examples/http.rs @@ -40,7 +40,7 @@ async fn main() -> anyhow::Result<()> { let params = JsonRpcParams::Array(&[1_u64, 2, 3]); let client = HttpClientBuilder::default().build(url)?; - let response: Result = client.request("say_hello", params).await; + let response: Result = client.request("say_hello", params).await; println!("r: {:?}", response); Ok(()) diff --git a/examples/proc_macro.rs b/examples/proc_macro.rs new file mode 100644 index 0000000000..3cc6311818 --- /dev/null +++ b/examples/proc_macro.rs @@ -0,0 +1,75 @@ +// Copyright 2019 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. + +use jsonrpsee::{ + http_client::{traits::Client, HttpClientBuilder}, + http_server::HttpServerBuilder, +}; +use std::net::SocketAddr; + +jsonrpsee::proc_macros::rpc_client_api! { + RpcApi { + #[rpc(method = "state_getPairs")] + fn storage_pairs() -> Vec; + } +} + +jsonrpsee::proc_macros::rpc_client_api! { + Registrar { + #[rpc(method = "say_hello")] + fn register_para(foo: i32, bar: String); + } +} + +jsonrpsee::proc_macros::rpc_client_api! { + ManyReturnTypes { + #[rpc(method = "say_hello")] + fn a() -> A; + fn b() -> B; + } +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + env_logger::init(); + + let server_addr = run_server().await?; + let url = format!("http://{}", server_addr); + + let client = HttpClientBuilder::default().build(url)?; + let response: Vec = RpcApi::storage_pairs(&client).await.unwrap(); + println!("r: {:?}", response); + + Ok(()) +} + +async fn run_server() -> anyhow::Result { + let mut server = HttpServerBuilder::default().build("127.0.0.1:0".parse()?)?; + server.register_method("state_getPairs", |_| Ok(vec![1, 2, 3]))?; + let addr = server.local_addr(); + tokio::spawn(async move { server.start().await }); + addr +} diff --git a/http-client/src/client.rs b/http-client/src/client.rs index adb8122fa6..61e6cac6d1 100644 --- a/http-client/src/client.rs +++ b/http-client/src/client.rs @@ -47,7 +47,7 @@ pub struct HttpClient { impl Client for HttpClient { async fn notification<'a, T>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result<(), Error> where - T: Serialize + std::fmt::Debug + Send + Sync, + T: Serialize + Send + Sync, { let notif = JsonRpcNotificationSer::new(method, params); self.transport @@ -59,7 +59,7 @@ impl Client for HttpClient { /// Perform a request towards the server. async fn request<'a, T, R>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result where - T: Serialize + std::fmt::Debug + Send + Sync, + T: Serialize + Send + Sync, R: DeserializeOwned, { // NOTE: `fetch_add` wraps on overflow which is intended. @@ -91,7 +91,7 @@ impl Client for HttpClient { async fn batch_request<'a, T, R>(&self, batch: Vec<(&'a str, JsonRpcParams<'a, T>)>) -> Result, Error> where - T: Serialize + std::fmt::Debug + Send + Sync, + T: Serialize + Send + Sync, R: DeserializeOwned + Default + Clone, { let mut batch_request = Vec::with_capacity(batch.len()); diff --git a/jsonrpsee/tests/proc_macros.rs b/jsonrpsee/tests/proc_macros.rs index e8d7971663..7b9e3037ed 100644 --- a/jsonrpsee/tests/proc_macros.rs +++ b/jsonrpsee/tests/proc_macros.rs @@ -6,13 +6,13 @@ mod helpers; use jsonrpsee::{http_client::*, proc_macros, ws_client::*}; proc_macros::rpc_client_api! { - Test { + Test { fn say_hello() -> T; } } proc_macros::rpc_client_api! { - pub(crate) Test2 { + pub(crate) Test2 { #[rpc(method = "say_hello")] fn foo(b: B) -> T; } @@ -50,7 +50,7 @@ proc_macros::rpc_client_api! { } proc_macros::rpc_client_api! { - ManyReturnTypes { + ManyReturnTypes { #[rpc(method = "say_hello")] fn a() -> A; fn b() -> B; @@ -68,7 +68,7 @@ async fn proc_macros_generic_ws_client_api() { assert_eq!(Test::::say_hello(&client).await.unwrap(), "hello".to_string()); assert_eq!(Test2::::foo(&client, 99_u16).await.unwrap(), "hello".to_string()); - assert!(Registrar::register_para(&client, 99, "para").await.is_ok()); + assert!(Registrar::register_para(&client, 99, "para".into()).await.is_ok()); } #[tokio::test] diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs index 299ce23a7c..fe53926264 100644 --- a/proc-macros/src/lib.rs +++ b/proc-macros/src/lib.rs @@ -231,20 +231,21 @@ fn build_client_functions(api: &api_def::ApiDefinition) -> Result #generated_param_name: impl Into<#ty>)); + params_list.push(quote_spanned!(pat_span=> #generated_param_name: #ty)); params_to_json.push(quote_spanned!(pat_span=> map.insert( #rpc_param_name, - #_crate::to_json_value(#generated_param_name.into()).map_err(|e| #_crate::Error::Custom(format!("{:?}", e)))? + &#generated_param_name ); )); - params_to_array.push(quote_spanned!(pat_span => - #_crate::to_json_value(#generated_param_name.into()).map_err(|e| #_crate::Error::Custom(format!("{:?}", e)))? + params_to_array.push(quote_spanned!(pat_span=> + #generated_param_name )); } let params_building = if params_list.is_empty() { - quote! {None.into()} + // NOTE(niklasad1): type annotation is required. + quote_spanned!(function.signature.span()=> #_crate::v2::JsonRpcParams::NoParams::<()>) } else if function.attributes.positional_params { quote_spanned!(function.signature.span()=> #_crate::v2::JsonRpcParams::Array(&[ @@ -252,10 +253,9 @@ fn build_client_functions(api: &api_def::ApiDefinition) -> Result #_crate::v2::JsonRpcParams::Map({ - let mut map = alloc:collections::BTreeMap::with_capacity(#params_list_len); + let mut map = std::collections::BTreeMap::new(); #(#params_to_json)* map }) diff --git a/types/src/traits.rs b/types/src/traits.rs index 28c8b67aee..77eee589c5 100644 --- a/types/src/traits.rs +++ b/types/src/traits.rs @@ -9,12 +9,12 @@ pub trait Client { /// Send a [notification request](https://www.jsonrpc.org/specification#notification) async fn notification<'a, T>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result<(), Error> where - T: Serialize + std::fmt::Debug + Send + Sync; + T: Serialize + Send + Sync; /// Send a [method call request](https://www.jsonrpc.org/specification#request_object). async fn request<'a, T, R>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result where - T: Serialize + std::fmt::Debug + Send + Sync, + T: Serialize + Send + Sync, R: DeserializeOwned; /// Send a [batch request](https://www.jsonrpc.org/specification#batch). @@ -25,7 +25,7 @@ pub trait Client { /// Returns `Error` if any of the requests in batch fails. async fn batch_request<'a, T, R>(&self, batch: Vec<(&'a str, JsonRpcParams<'a, T>)>) -> Result, Error> where - T: Serialize + std::fmt::Debug + Send + Sync, + T: Serialize + Send + Sync, R: DeserializeOwned + Default + Clone; } @@ -48,7 +48,7 @@ pub trait SubscriptionClient: Client { ) -> Result, Error> where Notif: DeserializeOwned, - T: Serialize + std::fmt::Debug + Send + Sync; + T: Serialize + Send + Sync; } /// JSON-RPC server interface for managing method calls. diff --git a/types/src/v2/mod.rs b/types/src/v2/mod.rs index d30f6aaa29..06fba4f0de 100644 --- a/types/src/v2/mod.rs +++ b/types/src/v2/mod.rs @@ -149,7 +149,7 @@ impl Serialize for TwoPointZero { } /// Parameters sent with the RPC request -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub struct RpcParams<'a>(Option<&'a str>); impl<'a> RpcParams<'a> { @@ -181,10 +181,7 @@ impl<'a> RpcParams<'a> { /// [JSON-RPC parameters](https://www.jsonrpc.org/specification#parameter_structures) #[derive(Serialize, Debug)] #[serde(untagged)] -pub enum JsonRpcParams<'a, T> -where - T: Serialize + std::fmt::Debug, -{ +pub enum JsonRpcParams<'a, T> { /// No params. NoParams, /// Positional params. @@ -196,28 +193,19 @@ where } // TODO(niklasad1): this is a little weird but nice if `None.into()` works. -impl<'a, T> From> for JsonRpcParams<'a, T> -where - T: Serialize + std::fmt::Debug, -{ +impl<'a, T> From> for JsonRpcParams<'a, T> { fn from(_raw: Option<&'a T>) -> Self { Self::NoParams } } -impl<'a, T> From> for JsonRpcParams<'a, T> -where - T: Serialize + std::fmt::Debug, -{ +impl<'a, T> From> for JsonRpcParams<'a, T> { fn from(map: BTreeMap<&'a str, &'a T>) -> Self { Self::Map(map) } } -impl<'a, T> From<&'a [T]> for JsonRpcParams<'a, T> -where - T: Serialize + std::fmt::Debug, -{ +impl<'a, T> From<&'a [T]> for JsonRpcParams<'a, T> { fn from(arr: &'a [T]) -> Self { Self::Array(arr) } @@ -225,10 +213,7 @@ where /// Serializable [JSON-RPC object](https://www.jsonrpc.org/specification#request-object) #[derive(Serialize, Debug)] -pub struct JsonRpcCallSer<'a, T> -where - T: Serialize + std::fmt::Debug, -{ +pub struct JsonRpcCallSer<'a, T> { /// JSON-RPC version. pub jsonrpc: TwoPointZero, /// Name of the method to be invoked. @@ -239,10 +224,7 @@ where pub params: JsonRpcParams<'a, T>, } -impl<'a, T> JsonRpcCallSer<'a, T> -where - T: Serialize + std::fmt::Debug, -{ +impl<'a, T> JsonRpcCallSer<'a, T> { /// Create a new serializable JSON-RPC request. pub fn new(id: u64, method: &'a str, params: JsonRpcParams<'a, T>) -> Self { Self { jsonrpc: TwoPointZero, id, method, params } @@ -251,10 +233,7 @@ where /// Serializable [JSON-RPC notification object](https://www.jsonrpc.org/specification#request-object) #[derive(Serialize, Debug)] -pub struct JsonRpcNotificationSer<'a, T> -where - T: Serialize + std::fmt::Debug, -{ +pub struct JsonRpcNotificationSer<'a, T> { /// JSON-RPC version. pub jsonrpc: TwoPointZero, /// Name of the method to be invoked. @@ -263,10 +242,7 @@ where pub params: JsonRpcParams<'a, T>, } -impl<'a, T> JsonRpcNotificationSer<'a, T> -where - T: Serialize + std::fmt::Debug, -{ +impl<'a, T> JsonRpcNotificationSer<'a, T> { /// Create a new serializable JSON-RPC request. pub fn new(method: &'a str, params: JsonRpcParams<'a, T>) -> Self { Self { jsonrpc: TwoPointZero, method, params } diff --git a/ws-client/src/client.rs b/ws-client/src/client.rs index 88bb53d2d6..fcdd8b8d85 100644 --- a/ws-client/src/client.rs +++ b/ws-client/src/client.rs @@ -290,13 +290,13 @@ impl WsClient { impl Client for WsClient { async fn notification<'a, T>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result<(), Error> where - T: Serialize + std::fmt::Debug + Send + Sync, + T: Serialize + Send + Sync, { - log::trace!("[frontend]: send notification: method={:?}, params={:?}", method, params); // NOTE: we use this to guard against max number of concurrent requests. let _req_id = self.id_guard.next_request_id()?; let notif = JsonRpcNotificationSer::new(method, params); let raw = serde_json::to_string(¬if).map_err(Error::ParseError)?; + log::trace!("[frontend]: send notification: {:?}", raw); let res = self.to_back.clone().send(FrontToBack::Notification(raw)).await; self.id_guard.reclaim_request_id(); match res { @@ -307,13 +307,13 @@ impl Client for WsClient { async fn request<'a, T, R>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result where - T: Serialize + std::fmt::Debug + Send + Sync, + T: Serialize + Send + Sync, R: DeserializeOwned, { - log::trace!("[frontend]: send request: method={:?}, params={:?}", method, params); let (send_back_tx, send_back_rx) = oneshot::channel(); let req_id = self.id_guard.next_request_id()?; let raw = serde_json::to_string(&JsonRpcCallSer::new(req_id, method, params)).map_err(Error::ParseError)?; + log::trace!("[frontend]: send request: {:?}", raw); if self .to_back @@ -348,7 +348,7 @@ impl Client for WsClient { async fn batch_request<'a, T, R>(&self, batch: Vec<(&'a str, JsonRpcParams<'a, T>)>) -> Result, Error> where - T: Serialize + std::fmt::Debug + Send + Sync, + T: Serialize + Send + Sync, R: DeserializeOwned + Default + Clone, { let batch_ids = self.id_guard.next_request_ids(batch.len())?; @@ -401,7 +401,7 @@ impl SubscriptionClient for WsClient { ) -> Result, Error> where N: DeserializeOwned, - T: Serialize + std::fmt::Debug + Send + Sync, + T: Serialize + Send + Sync, { log::trace!("[frontend]: subscribe: {:?}, unsubscribe: {:?}", subscribe_method, unsubscribe_method); From 1cbfba7cd94cbcc573e922ef60a714ea013b912a Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sat, 17 Apr 2021 00:06:10 +0200 Subject: [PATCH 24/41] fix tests again; more jsonvalue --- benches/bench.rs | 11 ++++----- examples/http.rs | 2 +- examples/proc_macro.rs | 26 ++++---------------- examples/ws.rs | 2 +- examples/ws_subscription.rs | 5 ++-- http-client/src/client.rs | 13 ++++------ http-client/src/tests.rs | 17 ++++++------- jsonrpsee/tests/integration_tests.rs | 31 ++++++++++++------------ proc-macros/src/lib.rs | 9 ++++--- types/src/lib.rs | 2 +- types/src/traits.rs | 19 ++++++--------- types/src/v2/mod.rs | 36 ++++++++++++++-------------- ws-client/src/client.rs | 18 +++++--------- ws-client/src/helpers.rs | 3 +-- ws-client/src/tests.rs | 17 +++++++------ 15 files changed, 84 insertions(+), 127 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index d55d01d572..76ca894131 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -15,14 +15,14 @@ mod helpers; criterion_group!(benches, http_requests, websocket_requests, jsonrpsee_types_v2); criterion_main!(benches); -fn v2_serialize(req: JsonRpcCallSer) -> String { +fn v2_serialize<'a>(req: JsonRpcCallSer<'a>) -> String { serde_json::to_string(&req).unwrap() } pub fn jsonrpsee_types_v2(crit: &mut Criterion) { crit.bench_function("jsonrpsee_types_v2", |b| { b.iter(|| { - let params = JsonRpcParams::Array(&[1_u64, 2]); + let params = JsonRpcParams::Array(vec![1_u64.into(), 2.into()]); let request = JsonRpcCallSer::new(0, "say_hello", params); v2_serialize(request); }) @@ -50,7 +50,7 @@ fn run_round_trip(rt: &TokioRuntime, crit: &mut Criterion, client: Arc("say_hello", JsonRpcParams::NoParams).await.unwrap()); + black_box(client.request::("say_hello", JsonRpcParams::NoParams).await.unwrap()); }) }) }); @@ -70,9 +70,8 @@ fn run_concurrent_round_trip( for _ in 0..num_concurrent_tasks { let client_rc = client.clone(); let task = rt.spawn(async move { - let _ = black_box( - client_rc.request::("say_hello", JsonRpcParams::NoParams).await.unwrap(), - ); + let _ = + black_box(client_rc.request::("say_hello", JsonRpcParams::NoParams).await.unwrap()); }); tasks.push(task); } diff --git a/examples/http.rs b/examples/http.rs index 2fbef28483..151fd8f621 100644 --- a/examples/http.rs +++ b/examples/http.rs @@ -37,7 +37,7 @@ async fn main() -> anyhow::Result<()> { let server_addr = run_server().await?; let url = format!("http://{}", server_addr); - let params = JsonRpcParams::Array(&[1_u64, 2, 3]); + let params = JsonRpcParams::Array(vec![1_u64.into(), 2.into(), 3.into()]); let client = HttpClientBuilder::default().build(url)?; let response: Result = client.request("say_hello", params).await; diff --git a/examples/proc_macro.rs b/examples/proc_macro.rs index 3cc6311818..72f360eefc 100644 --- a/examples/proc_macro.rs +++ b/examples/proc_macro.rs @@ -24,31 +24,13 @@ // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use jsonrpsee::{ - http_client::{traits::Client, HttpClientBuilder}, - http_server::HttpServerBuilder, -}; +use jsonrpsee::{http_client::HttpClientBuilder, http_server::HttpServerBuilder}; use std::net::SocketAddr; jsonrpsee::proc_macros::rpc_client_api! { RpcApi { - #[rpc(method = "state_getPairs")] - fn storage_pairs() -> Vec; - } -} - -jsonrpsee::proc_macros::rpc_client_api! { - Registrar { - #[rpc(method = "say_hello")] - fn register_para(foo: i32, bar: String); - } -} - -jsonrpsee::proc_macros::rpc_client_api! { - ManyReturnTypes { - #[rpc(method = "say_hello")] - fn a() -> A; - fn b() -> B; + #[rpc(method = "state_getPairs", positional_params)] + fn storage_pairs(prefix: usize, hash: Option) -> Vec; } } @@ -60,7 +42,7 @@ async fn main() -> anyhow::Result<()> { let url = format!("http://{}", server_addr); let client = HttpClientBuilder::default().build(url)?; - let response: Vec = RpcApi::storage_pairs(&client).await.unwrap(); + let response = RpcApi::storage_pairs(&client, 0_usize, Some("aaa".to_string())).await?; println!("r: {:?}", response); Ok(()) diff --git a/examples/ws.rs b/examples/ws.rs index 3c6f845147..209361e819 100644 --- a/examples/ws.rs +++ b/examples/ws.rs @@ -37,7 +37,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", JsonRpcParams::NoParams::).await?; + let response: String = client.request("say_hello", JsonRpcParams::NoParams).await?; println!("r: {:?}", response); Ok(()) diff --git a/examples/ws_subscription.rs b/examples/ws_subscription.rs index 9c4369c3eb..cbd1f0c34d 100644 --- a/examples/ws_subscription.rs +++ b/examples/ws_subscription.rs @@ -25,7 +25,7 @@ // DEALINGS IN THE SOFTWARE. use jsonrpsee::{ - ws_client::{traits::SubscriptionClient, v2::JsonRpcParams, Subscription, WsClientBuilder}, + ws_client::{traits::SubscriptionClient, Subscription, WsClientBuilder}, ws_server::WsServer, }; use std::net::SocketAddr; @@ -39,9 +39,8 @@ async fn main() -> anyhow::Result<()> { let url = format!("ws://{}", addr); let client = WsClientBuilder::default().build(&url).await?; - let params: JsonRpcParams = None.into(); let mut subscribe_hello: Subscription = - client.subscribe("subscribe_hello", params, "unsubscribe_hello").await?; + client.subscribe("subscribe_hello", None.into(), "unsubscribe_hello").await?; let mut i = 0; while i <= NUM_SUBSCRIPTION_RESPONSES { diff --git a/http-client/src/client.rs b/http-client/src/client.rs index 61e6cac6d1..726ac2f83c 100644 --- a/http-client/src/client.rs +++ b/http-client/src/client.rs @@ -4,7 +4,7 @@ use crate::v2::{JsonRpcCallSer, JsonRpcErrorAlloc, JsonRpcNotificationSer, JsonR use crate::{Error, JsonRawValue}; use async_trait::async_trait; use fnv::FnvHashMap; -use serde::{de::DeserializeOwned, Serialize}; +use serde::de::DeserializeOwned; use std::sync::atomic::{AtomicU64, Ordering}; /// Http Client Builder. @@ -45,10 +45,7 @@ pub struct HttpClient { #[async_trait] impl Client for HttpClient { - async fn notification<'a, T>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result<(), Error> - where - T: Serialize + Send + Sync, - { + async fn notification<'a>(&self, method: &'a str, params: JsonRpcParams<'a>) -> Result<(), Error> { let notif = JsonRpcNotificationSer::new(method, params); self.transport .send(serde_json::to_string(¬if).map_err(Error::ParseError)?) @@ -57,9 +54,8 @@ impl Client for HttpClient { } /// Perform a request towards the server. - async fn request<'a, T, R>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result + async fn request<'a, R>(&self, method: &'a str, params: JsonRpcParams<'a>) -> Result where - T: Serialize + Send + Sync, R: DeserializeOwned, { // NOTE: `fetch_add` wraps on overflow which is intended. @@ -89,9 +85,8 @@ impl Client for HttpClient { } } - async fn batch_request<'a, T, R>(&self, batch: Vec<(&'a str, JsonRpcParams<'a, T>)>) -> Result, Error> + async fn batch_request<'a, R>(&self, batch: Vec<(&'a str, JsonRpcParams<'a>)>) -> Result, Error> where - T: Serialize + Send + Sync, R: DeserializeOwned + Default + Clone, { let mut batch_request = Vec::with_capacity(batch.len()); diff --git a/http-client/src/tests.rs b/http-client/src/tests.rs index 4316aea79e..5201cc51d4 100644 --- a/http-client/src/tests.rs +++ b/http-client/src/tests.rs @@ -3,7 +3,7 @@ use crate::v2::error::{ INVALID_REQUEST_MSG, METHOD_NOT_FOUND_CODE, METHOD_NOT_FOUND_MSG, PARSE_ERROR_CODE, PARSE_ERROR_MSG, }; use crate::v2::JsonRpcParams; -use crate::{traits::Client, Error, HttpClientBuilder, JsonValue, Serialize}; +use crate::{traits::Client, Error, HttpClientBuilder, JsonValue}; use jsonrpsee_test_utils::helpers::*; use jsonrpsee_test_utils::types::Id; @@ -19,10 +19,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", - JsonRpcParams::NoParams::, - ) + .notification("i_dont_care_about_the_response_because_the_server_should_not_respond", JsonRpcParams::NoParams) .await .unwrap(); } @@ -74,7 +71,7 @@ async fn subscription_response_to_request() { async fn batch_request_works() { let batch_request = vec![ ("say_hello", JsonRpcParams::NoParams), - ("say_goodbye", JsonRpcParams::Array(&[0_u64, 1, 2])), + ("say_goodbye", JsonRpcParams::Array(vec![0_u64.into(), 1.into(), 2.into()])), ("get_swag", JsonRpcParams::NoParams), ]; 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(); @@ -86,7 +83,7 @@ async fn batch_request_works() { async fn batch_request_out_of_order_response() { let batch_request = vec![ ("say_hello", JsonRpcParams::NoParams), - ("say_goodbye", JsonRpcParams::Array(&[0_u64, 1, 2])), + ("say_goodbye", JsonRpcParams::Array(vec![0_u64.into(), 1.into(), 2.into()])), ("get_swag", JsonRpcParams::NoParams), ]; 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(); @@ -94,8 +91,8 @@ async fn batch_request_out_of_order_response() { assert_eq!(response, vec!["hello".to_string(), "goodbye".to_string(), "here's your swag".to_string()]); } -async fn run_batch_request_with_response<'a, T: Serialize + std::fmt::Debug + Send + Sync>( - batch: Vec<(&'a str, JsonRpcParams<'a, T>)>, +async fn run_batch_request_with_response<'a>( + batch: Vec<(&'a str, JsonRpcParams<'a>)>, response: String, ) -> Result, Error> { let server_addr = http_server_with_hardcoded_response(response).await; @@ -108,7 +105,7 @@ async fn run_request_with_response(response: String) -> Result let server_addr = http_server_with_hardcoded_response(response).await; let uri = format!("http://{}", server_addr); let client = HttpClientBuilder::default().build(&uri).unwrap(); - client.request::("say_hello", JsonRpcParams::NoParams).await + client.request("say_hello", JsonRpcParams::NoParams).await } fn assert_jsonrpc_error_response(error: Error, code: i32, message: &str) { diff --git a/jsonrpsee/tests/integration_tests.rs b/jsonrpsee/tests/integration_tests.rs index cdb01eb989..aaa586710f 100644 --- a/jsonrpsee/tests/integration_tests.rs +++ b/jsonrpsee/tests/integration_tests.rs @@ -42,9 +42,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", JsonRpcParams::NoParams::, "unsubscribe_hello").await.unwrap(); + client.subscribe("subscribe_hello", JsonRpcParams::NoParams, "unsubscribe_hello").await.unwrap(); let mut foo_sub: Subscription = - client.subscribe("subscribe_foo", JsonRpcParams::NoParams::, "unsubscribe_foo").await.unwrap(); + client.subscribe("subscribe_foo", JsonRpcParams::NoParams, "unsubscribe_foo").await.unwrap(); for _ in 0..10 { let hello = hello_sub.next().await.unwrap(); @@ -59,7 +59,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", JsonRpcParams::NoParams::).await.unwrap(); + let response: String = client.request("say_hello", JsonRpcParams::NoParams).await.unwrap(); assert_eq!(&response, "hello"); } @@ -68,7 +68,7 @@ async fn http_method_call_works() { let server_addr = http_server().await; let uri = format!("http://{}", server_addr); let client = HttpClientBuilder::default().build(&uri).unwrap(); - let response: String = client.request("say_hello", JsonRpcParams::NoParams::).await.unwrap(); + let response: String = client.request("say_hello", JsonRpcParams::NoParams).await.unwrap(); assert_eq!(&response, "hello"); } @@ -81,9 +81,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", JsonRpcParams::NoParams::, "unsubscribe_hello").await.unwrap(); + client.subscribe("subscribe_hello", JsonRpcParams::NoParams, "unsubscribe_hello").await.unwrap(); let foo_sub: Subscription = - client.subscribe("subscribe_foo", JsonRpcParams::NoParams::, "unsubscribe_foo").await.unwrap(); + client.subscribe("subscribe_foo", JsonRpcParams::NoParams, "unsubscribe_foo").await.unwrap(); clients.push((client, hello_sub, foo_sub)) } } @@ -98,9 +98,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", JsonRpcParams::NoParams::, "unsubscribe_hello").await.unwrap(); + client.subscribe("subscribe_hello", JsonRpcParams::NoParams, "unsubscribe_hello").await.unwrap(); let foo_sub: Subscription = - client.subscribe("subscribe_foo", JsonRpcParams::NoParams::, "unsubscribe_foo").await.unwrap(); + client.subscribe("subscribe_foo", JsonRpcParams::NoParams, "unsubscribe_foo").await.unwrap(); clients.push((client, hello_sub, foo_sub)) } @@ -143,7 +143,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", JsonRpcParams::NoParams::, "unsubscribe_hello").await.unwrap(); + client.subscribe("subscribe_hello", JsonRpcParams::NoParams, "unsubscribe_hello").await.unwrap(); // don't poll the subscription stream for 2 seconds, should be full now. std::thread::sleep(Duration::from_secs(2)); @@ -157,11 +157,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", JsonRpcParams::NoParams::).await.unwrap(); + let _hello_req: JsonValue = client.request("say_hello", JsonRpcParams::NoParams).await.unwrap(); // The same subscription should be possible to register again. let mut other_sub: Subscription = - client.subscribe("subscribe_hello", JsonRpcParams::NoParams::, "unsubscribe_hello").await.unwrap(); + client.subscribe("subscribe_hello", JsonRpcParams::NoParams, "unsubscribe_hello").await.unwrap(); other_sub.next().await.unwrap(); } @@ -176,8 +176,7 @@ async fn ws_more_request_than_buffer_should_not_deadlock() { for _ in 0..6 { let c = client.clone(); - requests - .push(tokio::spawn(async move { c.request::("say_hello", JsonRpcParams::NoParams).await })); + requests.push(tokio::spawn(async move { c.request::("say_hello", JsonRpcParams::NoParams).await })); } for req in requests { @@ -188,14 +187,14 @@ async fn ws_more_request_than_buffer_should_not_deadlock() { #[tokio::test] async fn https_works() { let client = HttpClientBuilder::default().build("https://kusama-rpc.polkadot.io").unwrap(); - let response: String = client.request("system_chain", JsonRpcParams::NoParams::).await.unwrap(); + let response: String = client.request("system_chain", JsonRpcParams::NoParams).await.unwrap(); assert_eq!(&response, "Kusama"); } #[tokio::test] async fn wss_works() { let client = WsClientBuilder::default().build("wss://kusama-rpc.polkadot.io").await.unwrap(); - let response: String = client.request("system_chain", JsonRpcParams::NoParams::).await.unwrap(); + let response: String = client.request("system_chain", JsonRpcParams::NoParams).await.unwrap(); assert_eq!(&response, "Kusama"); } @@ -208,6 +207,6 @@ async fn ws_with_non_ascii_url_doesnt_hang_or_panic() { #[tokio::test] async fn http_with_non_ascii_url_doesnt_hang_or_panic() { let client = HttpClientBuilder::default().build("http://♥♥♥♥♥♥∀∂").unwrap(); - let err: Result<(), Error> = client.request("system_chain", JsonRpcParams::NoParams::).await; + let err: Result<(), Error> = client.request("system_chain", JsonRpcParams::NoParams).await; assert!(matches!(err, Err(Error::TransportError(_)))); } diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs index fe53926264..e8d37ee009 100644 --- a/proc-macros/src/lib.rs +++ b/proc-macros/src/lib.rs @@ -235,20 +235,19 @@ fn build_client_functions(api: &api_def::ApiDefinition) -> Result map.insert( #rpc_param_name, - &#generated_param_name + #_crate::to_json_value(#generated_param_name).map_err(#_crate::Error::ParseError)? ); )); params_to_array.push(quote_spanned!(pat_span=> - #generated_param_name + #_crate::to_json_value(#generated_param_name).map_err(#_crate::Error::ParseError)? )); } let params_building = if params_list.is_empty() { - // NOTE(niklasad1): type annotation is required. - quote_spanned!(function.signature.span()=> #_crate::v2::JsonRpcParams::NoParams::<()>) + quote_spanned!(function.signature.span()=> #_crate::v2::JsonRpcParams::NoParams) } else if function.attributes.positional_params { quote_spanned!(function.signature.span()=> - #_crate::v2::JsonRpcParams::Array(&[ + #_crate::v2::JsonRpcParams::Array(vec![ #(#params_to_array),* ]) ) diff --git a/types/src/lib.rs b/types/src/lib.rs index 72a9fa3745..73fe7e4994 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -21,4 +21,4 @@ pub use beef::Cow; pub use client::*; pub use error::Error; pub use serde::{de::DeserializeOwned, Serialize}; -pub use serde_json::{to_value as to_json_value, value::RawValue as JsonRawValue, Value as JsonValue}; +pub use serde_json::{to_value as to_json_value, value::RawValue as JsonRawValue, Map as JsonMap, Value as JsonValue}; diff --git a/types/src/traits.rs b/types/src/traits.rs index 77eee589c5..cab98a7344 100644 --- a/types/src/traits.rs +++ b/types/src/traits.rs @@ -1,20 +1,17 @@ use crate::v2::{JsonRpcParams, RpcParams}; use crate::{error::RpcError, Error, Subscription}; use async_trait::async_trait; -use serde::{de::DeserializeOwned, Serialize}; +use serde::de::DeserializeOwned; /// [JSON-RPC](https://www.jsonrpc.org/specification) client interface that can make requests and notifications. #[async_trait] pub trait Client { /// Send a [notification request](https://www.jsonrpc.org/specification#notification) - async fn notification<'a, T>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result<(), Error> - where - T: Serialize + Send + Sync; + async fn notification<'a>(&self, method: &'a str, params: JsonRpcParams<'a>) -> Result<(), Error>; /// Send a [method call request](https://www.jsonrpc.org/specification#request_object). - async fn request<'a, T, R>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result + async fn request<'a, R>(&self, method: &'a str, params: JsonRpcParams<'a>) -> Result where - T: Serialize + Send + Sync, R: DeserializeOwned; /// Send a [batch request](https://www.jsonrpc.org/specification#batch). @@ -23,9 +20,8 @@ pub trait Client { /// /// 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, T, R>(&self, batch: Vec<(&'a str, JsonRpcParams<'a, T>)>) -> Result, Error> + async fn batch_request<'a, R>(&self, batch: Vec<(&'a str, JsonRpcParams<'a>)>) -> Result, Error> where - T: Serialize + Send + Sync, R: DeserializeOwned + Default + Clone; } @@ -40,15 +36,14 @@ pub trait SubscriptionClient: Client { /// The `unsubscribe_method` is used to close the subscription. /// /// The `Notif` param is a generic type to receive generic subscriptions, see [`Subscription`](crate::client::Subscription) for further documentation. - async fn subscribe<'a, T, Notif>( + async fn subscribe<'a, Notif>( &self, subscribe_method: &'a str, - params: JsonRpcParams<'a, T>, + params: JsonRpcParams<'a>, unsubscribe_method: &'a str, ) -> Result, Error> where - Notif: DeserializeOwned, - T: Serialize + Send + Sync; + Notif: DeserializeOwned; } /// JSON-RPC server interface for managing method calls. diff --git a/types/src/v2/mod.rs b/types/src/v2/mod.rs index 06fba4f0de..4ae342f353 100644 --- a/types/src/v2/mod.rs +++ b/types/src/v2/mod.rs @@ -3,7 +3,7 @@ use alloc::collections::BTreeMap; use serde::de::{self, DeserializeOwned, Deserializer, Unexpected, Visitor}; use serde::ser::Serializer; use serde::{Deserialize, Serialize}; -use serde_json::value::RawValue; +use serde_json::{value::RawValue, Value as JsonValue}; use std::fmt; use thiserror::Error; @@ -181,39 +181,39 @@ impl<'a> RpcParams<'a> { /// [JSON-RPC parameters](https://www.jsonrpc.org/specification#parameter_structures) #[derive(Serialize, Debug)] #[serde(untagged)] -pub enum JsonRpcParams<'a, T> { +pub enum JsonRpcParams<'a> { /// No params. NoParams, /// Positional params. - Array(&'a [T]), + Array(Vec), /// Params by name. // // TODO(niklasad1): maybe take a reference here but BTreeMap needs allocation anyway. - Map(BTreeMap<&'a str, &'a T>), + Map(BTreeMap<&'a str, JsonValue>), } // TODO(niklasad1): this is a little weird but nice if `None.into()` works. -impl<'a, T> From> for JsonRpcParams<'a, T> { - fn from(_raw: Option<&'a T>) -> Self { +impl<'a> From> for JsonRpcParams<'a> { + fn from(_raw: Option<&'a JsonValue>) -> Self { Self::NoParams } } -impl<'a, T> From> for JsonRpcParams<'a, T> { - fn from(map: BTreeMap<&'a str, &'a T>) -> Self { +impl<'a> From> for JsonRpcParams<'a> { + fn from(map: BTreeMap<&'a str, JsonValue>) -> Self { Self::Map(map) } } -impl<'a, T> From<&'a [T]> for JsonRpcParams<'a, T> { - fn from(arr: &'a [T]) -> Self { +impl<'a> From> for JsonRpcParams<'a> { + fn from(arr: Vec) -> Self { Self::Array(arr) } } /// Serializable [JSON-RPC object](https://www.jsonrpc.org/specification#request-object) #[derive(Serialize, Debug)] -pub struct JsonRpcCallSer<'a, T> { +pub struct JsonRpcCallSer<'a> { /// JSON-RPC version. pub jsonrpc: TwoPointZero, /// Name of the method to be invoked. @@ -221,30 +221,30 @@ pub struct JsonRpcCallSer<'a, T> { /// Request ID pub id: u64, /// Parameter values of the request. - pub params: JsonRpcParams<'a, T>, + pub params: JsonRpcParams<'a>, } -impl<'a, T> JsonRpcCallSer<'a, T> { +impl<'a> JsonRpcCallSer<'a> { /// Create a new serializable JSON-RPC request. - pub fn new(id: u64, method: &'a str, params: JsonRpcParams<'a, T>) -> Self { + pub fn new(id: u64, method: &'a str, params: JsonRpcParams<'a>) -> Self { Self { jsonrpc: TwoPointZero, id, method, params } } } /// Serializable [JSON-RPC notification object](https://www.jsonrpc.org/specification#request-object) #[derive(Serialize, Debug)] -pub struct JsonRpcNotificationSer<'a, T> { +pub struct JsonRpcNotificationSer<'a> { /// JSON-RPC version. pub jsonrpc: TwoPointZero, /// Name of the method to be invoked. pub method: &'a str, /// Parameter values of the request. - pub params: JsonRpcParams<'a, T>, + pub params: JsonRpcParams<'a>, } -impl<'a, T> JsonRpcNotificationSer<'a, T> { +impl<'a> JsonRpcNotificationSer<'a> { /// Create a new serializable JSON-RPC request. - pub fn new(method: &'a str, params: JsonRpcParams<'a, T>) -> Self { + pub fn new(method: &'a str, params: JsonRpcParams<'a>) -> Self { Self { jsonrpc: TwoPointZero, method, params } } } diff --git a/ws-client/src/client.rs b/ws-client/src/client.rs index fcdd8b8d85..0f20a6d248 100644 --- a/ws-client/src/client.rs +++ b/ws-client/src/client.rs @@ -44,7 +44,7 @@ use futures::{ prelude::*, sink::SinkExt, }; -use serde::{de::DeserializeOwned, Serialize}; +use serde::de::DeserializeOwned; use std::{ borrow::Cow, marker::PhantomData, @@ -288,10 +288,7 @@ impl WsClient { #[async_trait] impl Client for WsClient { - async fn notification<'a, T>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result<(), Error> - where - T: Serialize + Send + Sync, - { + async fn notification<'a>(&self, method: &'a str, params: JsonRpcParams<'a>) -> Result<(), Error> { // NOTE: we use this to guard against max number of concurrent requests. let _req_id = self.id_guard.next_request_id()?; let notif = JsonRpcNotificationSer::new(method, params); @@ -305,9 +302,8 @@ impl Client for WsClient { } } - async fn request<'a, T, R>(&self, method: &'a str, params: JsonRpcParams<'a, T>) -> Result + async fn request<'a, R>(&self, method: &'a str, params: JsonRpcParams<'a>) -> Result where - T: Serialize + Send + Sync, R: DeserializeOwned, { let (send_back_tx, send_back_rx) = oneshot::channel(); @@ -346,9 +342,8 @@ impl Client for WsClient { serde_json::from_value(json_value).map_err(Error::ParseError) } - async fn batch_request<'a, T, R>(&self, batch: Vec<(&'a str, JsonRpcParams<'a, T>)>) -> Result, Error> + async fn batch_request<'a, R>(&self, batch: Vec<(&'a str, JsonRpcParams<'a>)>) -> Result, Error> where - T: Serialize + Send + Sync, R: DeserializeOwned + Default + Clone, { let batch_ids = self.id_guard.next_request_ids(batch.len())?; @@ -393,15 +388,14 @@ impl SubscriptionClient for WsClient { /// /// 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, T, N>( + async fn subscribe<'a, N>( &self, subscribe_method: &'a str, - params: JsonRpcParams<'a, T>, + params: JsonRpcParams<'a>, unsubscribe_method: &'a str, ) -> Result, Error> where N: DeserializeOwned, - T: Serialize + Send + Sync, { log::trace!("[frontend]: subscribe: {:?}, unsubscribe: {:?}", subscribe_method, unsubscribe_method); diff --git a/ws-client/src/helpers.rs b/ws-client/src/helpers.rs index 8c71084460..a59fb709b8 100644 --- a/ws-client/src/helpers.rs +++ b/ws-client/src/helpers.rs @@ -146,13 +146,12 @@ pub fn build_unsubscribe_message( sub_id: SubscriptionId, ) -> Option { let (unsub_req_id, _, unsub, sub_id) = manager.remove_subscription(sub_req_id, sub_id)?; - let sub_id_slice = &[sub_id]; if manager.insert_pending_call(unsub_req_id, None).is_err() { log::warn!("Unsubscribe message failed to get slot in the RequestManager"); return None; } // TODO(niklasad): better type for params or maybe a macro?!. - let params = JsonRpcParams::Array(sub_id_slice); + let params = JsonRpcParams::Array(vec![serde_json::to_value(sub_id).unwrap()]); let raw = serde_json::to_string(&JsonRpcCallSer::new(unsub_req_id, &unsub, params)).unwrap(); Some(RequestMessage { raw, id: unsub_req_id, send_back: None }) } diff --git a/ws-client/src/tests.rs b/ws-client/src/tests.rs index bba4e5d615..ac710fa0c4 100644 --- a/ws-client/src/tests.rs +++ b/ws-client/src/tests.rs @@ -7,7 +7,6 @@ use crate::{ }; use jsonrpsee_test_utils::helpers::*; use jsonrpsee_test_utils::types::{Id, WebSocketTestServer}; -use serde::Serialize; use serde_json::Value as JsonValue; #[tokio::test] @@ -22,7 +21,7 @@ async fn notif_works() { let server = WebSocketTestServer::with_hardcoded_response("127.0.0.1:0".parse().unwrap(), String::new()).await; let uri = to_ws_uri_string(server.local_addr()); let client = WsClientBuilder::default().build(&uri).await.unwrap(); - assert!(client.notification("notif", JsonRpcParams::NoParams::).await.is_ok()); + assert!(client.notification("notif", JsonRpcParams::NoParams).await.is_ok()); } #[tokio::test] @@ -73,7 +72,7 @@ async fn subscription_works() { let client = WsClientBuilder::default().build(&uri).await.unwrap(); { let mut sub: Subscription = - client.subscribe("subscribe_hello", JsonRpcParams::NoParams::, "unsubscribe_hello").await.unwrap(); + client.subscribe("subscribe_hello", JsonRpcParams::NoParams, "unsubscribe_hello").await.unwrap(); let response: String = sub.next().await.unwrap().into(); assert_eq!("hello my friend".to_owned(), response); } @@ -84,7 +83,7 @@ async fn batch_request_works() { let _ = env_logger::try_init(); let batch_request = vec![ ("say_hello", JsonRpcParams::NoParams), - ("say_goodbye", JsonRpcParams::Array(&[0_u64, 1, 2])), + ("say_goodbye", JsonRpcParams::Array(vec![0_u64.into(), 1.into(), 2.into()])), ("get_swag", JsonRpcParams::NoParams), ]; 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(); @@ -96,7 +95,7 @@ async fn batch_request_works() { async fn batch_request_out_of_order_response() { let batch_request = vec![ ("say_hello", JsonRpcParams::NoParams), - ("say_goodbye", JsonRpcParams::Array(&[0_u64, 1, 2])), + ("say_goodbye", JsonRpcParams::Array(vec![0_u64.into(), 1.into(), 2.into()])), ("get_swag", JsonRpcParams::NoParams), ]; 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(); @@ -114,14 +113,14 @@ async fn is_connected_works() { let uri = to_ws_uri_string(server.local_addr()); let client = WsClientBuilder::default().build(&uri).await.unwrap(); assert!(client.is_connected()); - client.request::("say_hello", JsonRpcParams::NoParams).await.unwrap_err(); + client.request::("say_hello", JsonRpcParams::NoParams).await.unwrap_err(); // give the background thread some time to terminate. std::thread::sleep(std::time::Duration::from_millis(100)); assert!(!client.is_connected()) } -async fn run_batch_request_with_response<'a, T: Serialize + std::fmt::Debug + Send + Sync>( - batch: Vec<(&'a str, JsonRpcParams<'a, T>)>, +async fn run_batch_request_with_response<'a>( + batch: Vec<(&'a str, JsonRpcParams<'a>)>, response: String, ) -> Result, Error> { let server = WebSocketTestServer::with_hardcoded_response("127.0.0.1:0".parse().unwrap(), response).await; @@ -134,7 +133,7 @@ async fn run_request_with_response(response: String) -> Result let server = WebSocketTestServer::with_hardcoded_response("127.0.0.1:0".parse().unwrap(), response).await; let uri = format!("ws://{}", server.local_addr()); let client = WsClientBuilder::default().build(&uri).await.unwrap(); - client.request::("say_hello", JsonRpcParams::NoParams).await + client.request("say_hello", JsonRpcParams::NoParams).await } fn assert_error_response(error: Error, code: i32, message: &str) { From a47a0c92847480c004ae39ab3ecf5ac4aeb98d5d Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sat, 17 Apr 2021 00:30:16 +0200 Subject: [PATCH 25/41] [proc macros]: bring back impl Into for params. --- proc-macros/src/lib.rs | 6 +++--- types/src/lib.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs index e8d37ee009..5e573b4503 100644 --- a/proc-macros/src/lib.rs +++ b/proc-macros/src/lib.rs @@ -231,15 +231,15 @@ fn build_client_functions(api: &api_def::ApiDefinition) -> Result #generated_param_name: #ty)); + params_list.push(quote_spanned!(pat_span=> #generated_param_name: impl Into<#ty>)); params_to_json.push(quote_spanned!(pat_span=> map.insert( #rpc_param_name, - #_crate::to_json_value(#generated_param_name).map_err(#_crate::Error::ParseError)? + #_crate::to_json_value(#generated_param_name.into()).map_err(#_crate::Error::ParseError)? ); )); params_to_array.push(quote_spanned!(pat_span=> - #_crate::to_json_value(#generated_param_name).map_err(#_crate::Error::ParseError)? + #_crate::to_json_value(#generated_param_name.into()).map_err(#_crate::Error::ParseError)? )); } diff --git a/types/src/lib.rs b/types/src/lib.rs index 73fe7e4994..72a9fa3745 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -21,4 +21,4 @@ pub use beef::Cow; pub use client::*; pub use error::Error; pub use serde::{de::DeserializeOwned, Serialize}; -pub use serde_json::{to_value as to_json_value, value::RawValue as JsonRawValue, Map as JsonMap, Value as JsonValue}; +pub use serde_json::{to_value as to_json_value, value::RawValue as JsonRawValue, Value as JsonValue}; From 6bdc7732e453c5d020dea819cc6f9ff00ef60b6c Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sat, 17 Apr 2021 12:00:15 +0200 Subject: [PATCH 26/41] fix build --- jsonrpsee/tests/proc_macros.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsonrpsee/tests/proc_macros.rs b/jsonrpsee/tests/proc_macros.rs index 7b9e3037ed..d05427aa78 100644 --- a/jsonrpsee/tests/proc_macros.rs +++ b/jsonrpsee/tests/proc_macros.rs @@ -68,7 +68,7 @@ async fn proc_macros_generic_ws_client_api() { assert_eq!(Test::::say_hello(&client).await.unwrap(), "hello".to_string()); assert_eq!(Test2::::foo(&client, 99_u16).await.unwrap(), "hello".to_string()); - assert!(Registrar::register_para(&client, 99, "para".into()).await.is_ok()); + assert!(Registrar::register_para(&client, 99, "para").await.is_ok()); } #[tokio::test] From ce70d2e742d5364a58fe884ee2dc40ac65c99dc0 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sun, 18 Apr 2021 11:48:21 +0200 Subject: [PATCH 27/41] [proc macros]: make it work for external crates. --- Cargo.toml | 1 + examples/Cargo.toml | 2 +- http-client/Cargo.toml | 1 + jsonrpsee/Cargo.toml | 3 +- jsonrpsee/src/lib.rs | 3 ++ proc-macros/Cargo.toml | 2 +- proc-macros/src/lib.rs | 43 ++++++++++++------- tests/Cargo.toml | 14 ++++++ {jsonrpsee => tests}/tests/helpers.rs | 0 .../tests/integration_tests.rs | 0 {jsonrpsee => tests}/tests/proc_macros.rs | 0 ws-client/Cargo.toml | 2 +- 12 files changed, 51 insertions(+), 20 deletions(-) create mode 100644 tests/Cargo.toml rename {jsonrpsee => tests}/tests/helpers.rs (100%) rename {jsonrpsee => tests}/tests/integration_tests.rs (100%) rename {jsonrpsee => tests}/tests/proc_macros.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index a63159d4b2..662d4e225a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "http-server", "test-utils", "jsonrpsee", + "tests", "types", "utils", "ws-client", diff --git a/examples/Cargo.toml b/examples/Cargo.toml index a6fd985844..ee4a3f886f 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -8,9 +8,9 @@ publish = false [dev-dependencies] anyhow = "1" +async-std = "1" env_logger = "0.8" jsonrpsee = { path = "../jsonrpsee", features = ["full"] } -jsonrpsee-ws-client = { path = "../ws-client" } log = "0.4" tokio = { version = "1", features = ["full"] } diff --git a/http-client/Cargo.toml b/http-client/Cargo.toml index 501841d29f..aff630168b 100644 --- a/http-client/Cargo.toml +++ b/http-client/Cargo.toml @@ -29,4 +29,5 @@ tokio02 = ["hyper13", "hyper13-rustls", "jsonrpsee-utils/hyper_13"] [dev-dependencies] jsonrpsee-test-utils = { path = "../test-utils" } +jsonrpsee-proc-macros = { path = "../proc-macros" } tokio = { version = "1.0", features = ["net", "rt-multi-thread", "macros"] } diff --git a/jsonrpsee/Cargo.toml b/jsonrpsee/Cargo.toml index ca70169a66..c472cdff06 100644 --- a/jsonrpsee/Cargo.toml +++ b/jsonrpsee/Cargo.toml @@ -12,6 +12,7 @@ http-server = { path = "../http-server", version = "0.2.0-alpha.4", package = "j ws-client = { path = "../ws-client", version = "0.2.0-alpha.4", package = "jsonrpsee-ws-client", optional = true } ws-server = { path = "../ws-server", version = "0.2.0-alpha.4", package = "jsonrpsee-ws-server", optional = true } proc-macros = { path = "../proc-macros", version = "0.2.0-alpha.4", package = "jsonrpsee-proc-macros", optional = true } +types = { path = "../types", version = "0.2.0-alpha.4", package = "jsonrpsee-types", optional = true } [dev-dependencies] env_logger = "0.8" @@ -23,5 +24,5 @@ tokio = { version = "1", features = ["full"] } [features] client = ["http-client", "ws-client"] server = ["http-server", "ws-server"] -macros = ["proc-macros"] +macros = ["proc-macros", "types"] full = ["client", "server", "macros"] diff --git a/jsonrpsee/src/lib.rs b/jsonrpsee/src/lib.rs index abced68bec..b45471f24c 100644 --- a/jsonrpsee/src/lib.rs +++ b/jsonrpsee/src/lib.rs @@ -14,3 +14,6 @@ pub use ws_server; #[cfg(feature = "macros")] pub use proc_macros; + +#[cfg(feature = "macros")] +pub use types; diff --git a/proc-macros/Cargo.toml b/proc-macros/Cargo.toml index 09965cedd3..44ab41fe15 100644 --- a/proc-macros/Cargo.toml +++ b/proc-macros/Cargo.toml @@ -13,5 +13,5 @@ proc-macro = true Inflector = "0.11.4" proc-macro2 = "1.0" quote = "1.0" -syn = { version = "1.0", default-features = false, features = ["extra-traits"] } +syn = { version = "1.0", default-features = false, features = ["extra-traits", "full"] } proc-macro-crate = "1" diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs index 5e573b4503..2d6e2072d1 100644 --- a/proc-macros/src/lib.rs +++ b/proc-macros/src/lib.rs @@ -191,13 +191,7 @@ fn build_client_impl(api: &api_def::ApiDefinition) -> Result Result, syn::Error> { let visibility = &api.visibility; - let _crate = match (crate_name("jsonrpsee-http-client"), crate_name("jsonrpsee-ws-client")) { - (Ok(FoundCrate::Name(name)), _) => syn::Ident::new(&name, Span::call_site()), - (_, Ok(FoundCrate::Name(name))) => syn::Ident::new(&name, Span::call_site()), - (_, Err(e)) => return Err(syn::Error::new(Span::call_site(), &e)), - (Err(e), _) => return Err(syn::Error::new(Span::call_site(), &e)), - (_, _) => panic!("Deriving RPC methods in the `types` crate is not supported"), - }; + let _crate = find_jsonrpsee_crate()?; let mut client_functions = Vec::new(); for function in &api.definitions { @@ -244,20 +238,16 @@ fn build_client_functions(api: &api_def::ApiDefinition) -> Result #_crate::v2::JsonRpcParams::NoParams) + quote_spanned!(function.signature.span()=> None.into()) } else if function.attributes.positional_params { - quote_spanned!(function.signature.span()=> - #_crate::v2::JsonRpcParams::Array(vec![ - #(#params_to_array),* - ]) - ) + quote_spanned!(function.signature.span()=> vec![#(#params_to_array),*].into()) } else { quote_spanned!(function.signature.span()=> - #_crate::v2::JsonRpcParams::Map({ + { let mut map = std::collections::BTreeMap::new(); #(#params_to_json)* - map - }) + map.into() + } ) }; @@ -309,3 +299,24 @@ fn rpc_param_name(pat: &syn::Pat, _attrs: &[syn::Attribute]) -> syn::parse::Resu _ => unimplemented!(), } } + +/// Search for `jsonrpsee` in `Cargo.toml`. +fn find_jsonrpsee_crate() -> Result { + match crate_name("jsonrpsee") { + Ok(FoundCrate::Name(name)) => { + let ident = syn::Ident::new(&name, Span::call_site()); + Ok(quote!(#ident::types)) + } + Ok(FoundCrate::Itself) => panic!("Deriving RPC methods in any of the `jsonrpsee crates` is not supported"), + Err(_) => match (crate_name("jsonrpsee-http-client"), crate_name("jsonrpsee-ws-client")) { + (Ok(FoundCrate::Name(name)), _) | (_, Ok(FoundCrate::Name(name))) => { + let ident = syn::Ident::new(&name, Span::call_site()); + Ok(quote!(#ident)) + } + (Ok(FoundCrate::Itself), _) | (_, Ok(FoundCrate::Itself)) => { + panic!("Deriving RPC methods in any of the `jsonrpsee crates` is not supported") + } + (_, Err(e)) => Err(syn::Error::new(Span::call_site(), &e)), + }, + } +} diff --git a/tests/Cargo.toml b/tests/Cargo.toml new file mode 100644 index 0000000000..02e87c9c20 --- /dev/null +++ b/tests/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "jsonrpsee-integration-tests" +version = "0.1.0" +authors = ["Parity Technologies "] +description = "Integration tests for jsonrpsee" +edition = "2018" +license = "MIT" +publish = false + +[dev-dependencies] +env_logger = "0.8" +futures-channel = { version = "0.3", default-features = false } +jsonrpsee = { path = "../jsonrpsee", features = ["full"] } +tokio = { version = "1", features = ["full"] } diff --git a/jsonrpsee/tests/helpers.rs b/tests/tests/helpers.rs similarity index 100% rename from jsonrpsee/tests/helpers.rs rename to tests/tests/helpers.rs diff --git a/jsonrpsee/tests/integration_tests.rs b/tests/tests/integration_tests.rs similarity index 100% rename from jsonrpsee/tests/integration_tests.rs rename to tests/tests/integration_tests.rs diff --git a/jsonrpsee/tests/proc_macros.rs b/tests/tests/proc_macros.rs similarity index 100% rename from jsonrpsee/tests/proc_macros.rs rename to tests/tests/proc_macros.rs diff --git a/ws-client/Cargo.toml b/ws-client/Cargo.toml index 687030e874..a07e2dbcde 100644 --- a/ws-client/Cargo.toml +++ b/ws-client/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT" [dependencies] async-trait = "0.1" -async-std = { version = "1.9", default-features = false, features = ["std"] } +async-std = "1.9" async-tls = "0.11" fnv = "1" futures = { version = "0.3", default-features = false, features = ["std"] } From 6d0c7849a41fe6533eb910d214b6760bcf4376c1 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sun, 18 Apr 2021 14:20:14 +0200 Subject: [PATCH 28/41] [types]: remove weird From> to impl. --- examples/ws_subscription.rs | 4 ++-- proc-macros/src/lib.rs | 2 +- types/src/v2/mod.rs | 7 ------- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/examples/ws_subscription.rs b/examples/ws_subscription.rs index cbd1f0c34d..0505ffb223 100644 --- a/examples/ws_subscription.rs +++ b/examples/ws_subscription.rs @@ -25,7 +25,7 @@ // DEALINGS IN THE SOFTWARE. use jsonrpsee::{ - ws_client::{traits::SubscriptionClient, Subscription, WsClientBuilder}, + ws_client::{traits::SubscriptionClient, v2::JsonRpcParams, Subscription, WsClientBuilder}, ws_server::WsServer, }; use std::net::SocketAddr; @@ -40,7 +40,7 @@ async fn main() -> anyhow::Result<()> { let client = WsClientBuilder::default().build(&url).await?; let mut subscribe_hello: Subscription = - client.subscribe("subscribe_hello", None.into(), "unsubscribe_hello").await?; + client.subscribe("subscribe_hello", JsonRpcParams::NoParams, "unsubscribe_hello").await?; let mut i = 0; while i <= NUM_SUBSCRIPTION_RESPONSES { diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs index 2d6e2072d1..764639f666 100644 --- a/proc-macros/src/lib.rs +++ b/proc-macros/src/lib.rs @@ -238,7 +238,7 @@ fn build_client_functions(api: &api_def::ApiDefinition) -> Result None.into()) + quote_spanned!(function.signature.span()=> #_crate::v2::JsonRpcParams::NoParams) } else if function.attributes.positional_params { quote_spanned!(function.signature.span()=> vec![#(#params_to_array),*].into()) } else { diff --git a/types/src/v2/mod.rs b/types/src/v2/mod.rs index 4ae342f353..f99a025579 100644 --- a/types/src/v2/mod.rs +++ b/types/src/v2/mod.rs @@ -192,13 +192,6 @@ pub enum JsonRpcParams<'a> { Map(BTreeMap<&'a str, JsonValue>), } -// TODO(niklasad1): this is a little weird but nice if `None.into()` works. -impl<'a> From> for JsonRpcParams<'a> { - fn from(_raw: Option<&'a JsonValue>) -> Self { - Self::NoParams - } -} - impl<'a> From> for JsonRpcParams<'a> { fn from(map: BTreeMap<&'a str, JsonValue>) -> Self { Self::Map(map) From 151b06683aca066bb69b20fdc5d7fa47d5ab84ff Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sun, 18 Apr 2021 22:31:47 +0200 Subject: [PATCH 29/41] cleanup again --- benches/bench.rs | 13 +++++++++++-- examples/http.rs | 6 +++--- examples/ws_subscription.rs | 5 ----- http-client/Cargo.toml | 1 + http-client/src/tests.rs | 6 +++--- types/src/v2/error.rs | 20 ++++++++++++-------- types/src/v2/mod.rs | 32 ++++++++++++++++++++++++-------- utils/src/hyper_helpers.rs | 11 ----------- ws-client/src/helpers.rs | 22 +++++++++------------- ws-client/src/manager.rs | 6 +++--- ws-client/src/tests.rs | 6 +++--- 11 files changed, 69 insertions(+), 59 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index 76ca894131..a14924692a 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -20,9 +20,18 @@ fn v2_serialize<'a>(req: JsonRpcCallSer<'a>) -> String { } pub fn jsonrpsee_types_v2(crit: &mut Criterion) { - crit.bench_function("jsonrpsee_types_v2", |b| { + crit.bench_function("jsonrpsee_types_v2_array_ref", |b| { b.iter(|| { - let params = JsonRpcParams::Array(vec![1_u64.into(), 2.into()]); + let params = &[1_u64.into(), 2_u32.into()]; + let params = JsonRpcParams::ArrayRef(params); + let request = JsonRpcCallSer::new(0, "say_hello", params); + v2_serialize(request); + }) + }); + + crit.bench_function("jsonrpsee_types_v2_vec", |b| { + b.iter(|| { + let params = JsonRpcParams::Array(vec![1_u64.into(), 2_u32.into()]); let request = JsonRpcCallSer::new(0, "say_hello", params); v2_serialize(request); }) diff --git a/examples/http.rs b/examples/http.rs index 151fd8f621..824b6fa6ab 100644 --- a/examples/http.rs +++ b/examples/http.rs @@ -25,7 +25,7 @@ // DEALINGS IN THE SOFTWARE. use jsonrpsee::{ - http_client::{traits::Client, v2::JsonRpcParams, HttpClientBuilder}, + http_client::{traits::Client, HttpClientBuilder, JsonValue}, http_server::HttpServerBuilder, }; use std::net::SocketAddr; @@ -37,10 +37,10 @@ async fn main() -> anyhow::Result<()> { let server_addr = run_server().await?; let url = format!("http://{}", server_addr); - let params = JsonRpcParams::Array(vec![1_u64.into(), 2.into(), 3.into()]); + let params: &[JsonValue] = &[1_u64.into(), 2.into(), 3.into()]; let client = HttpClientBuilder::default().build(url)?; - let response: Result = client.request("say_hello", params).await; + let response: Result = client.request("say_hello", params.into()).await; println!("r: {:?}", response); Ok(()) diff --git a/examples/ws_subscription.rs b/examples/ws_subscription.rs index 0505ffb223..23c6d6f5df 100644 --- a/examples/ws_subscription.rs +++ b/examples/ws_subscription.rs @@ -49,11 +49,6 @@ async fn main() -> anyhow::Result<()> { i += 1; } - drop(subscribe_hello); - drop(client); - - std::thread::sleep(std::time::Duration::from_secs(1)); - Ok(()) } diff --git a/http-client/Cargo.toml b/http-client/Cargo.toml index aff630168b..18f0a1ef1c 100644 --- a/http-client/Cargo.toml +++ b/http-client/Cargo.toml @@ -8,6 +8,7 @@ license = "MIT" [dependencies] async-trait = "0.1" +env_logger = "0.8" hyper13-rustls = { package = "hyper-rustls", version = "0.21", optional = true } hyper14-rustls = { package = "hyper-rustls", version = "0.22", optional = true } hyper14 = { package = "hyper", version = "0.14", features = ["client", "http1", "http2", "tcp"], optional = true } diff --git a/http-client/src/tests.rs b/http-client/src/tests.rs index 5201cc51d4..1012e8391d 100644 --- a/http-client/src/tests.rs +++ b/http-client/src/tests.rs @@ -110,9 +110,9 @@ async fn run_request_with_response(response: String) -> Result fn assert_jsonrpc_error_response(error: Error, code: i32, message: &str) { match &error { - Error::Request(err) => { - assert_eq!(err.inner.code.code(), code); - assert_eq!(&err.inner.message, message); + Error::Request(e) => { + assert_eq!(e.error.code.code(), code); + assert_eq!(e.error.message, message); } e @ _ => panic!("Expected error: \"{}\", got: {:?}", error, e), }; diff --git a/types/src/v2/error.rs b/types/src/v2/error.rs index 3511944dfc..31acfa895f 100644 --- a/types/src/v2/error.rs +++ b/types/src/v2/error.rs @@ -17,7 +17,7 @@ pub struct JsonRpcErrorObject { impl fmt::Display for JsonRpcErrorObject { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}: {}", self.code.to_string(), self.message) + write!(f, "{}: {}: {:?}", self.code.code(), self.message, self.data) } } @@ -73,7 +73,7 @@ pub enum ErrorCode { impl ErrorCode { /// Returns integer code value - pub fn code(&self) -> i32 { + pub const fn code(&self) -> i32 { match *self { ErrorCode::ParseError => PARSE_ERROR_CODE, ErrorCode::InvalidRequest => INVALID_REQUEST_CODE, @@ -84,11 +84,10 @@ impl ErrorCode { ErrorCode::ApplicationError(code) => code, } } -} -impl fmt::Display for ErrorCode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let err = match *self { + /// Returns the message for the given error code. + pub const fn message(&self) -> &str { + match self { ErrorCode::ParseError => PARSE_ERROR_MSG, ErrorCode::InvalidRequest => INVALID_REQUEST_MSG, ErrorCode::MethodNotFound => METHOD_NOT_FOUND_MSG, @@ -96,8 +95,13 @@ impl fmt::Display for ErrorCode { ErrorCode::InternalError => INTERNAL_ERROR_MSG, ErrorCode::ServerError(_) => SERVER_ERROR_MSG, ErrorCode::ApplicationError(_) => APPLICATION_ERROR_MSG, - }; - f.write_str(err) + } + } +} + +impl fmt::Display for ErrorCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.code()) } } diff --git a/types/src/v2/mod.rs b/types/src/v2/mod.rs index f99a025579..f76a1de4de 100644 --- a/types/src/v2/mod.rs +++ b/types/src/v2/mod.rs @@ -84,16 +84,15 @@ pub struct JsonRpcError<'a> { pub struct JsonRpcErrorAlloc { /// JSON-RPC version. pub jsonrpc: TwoPointZero, - #[serde(rename = "error")] /// Error object. - pub inner: error::JsonRpcErrorObject, + pub error: error::JsonRpcErrorObject, /// Request ID. pub id: u64, } impl fmt::Display for JsonRpcErrorAlloc { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.inner) + write!(f, "{:?}: {}: {}", self.jsonrpc, self.error, self.id) } } @@ -179,16 +178,20 @@ impl<'a> RpcParams<'a> { } /// [JSON-RPC parameters](https://www.jsonrpc.org/specification#parameter_structures) +/// +/// If your type implement `Into` call that 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)] #[serde(untagged)] pub enum JsonRpcParams<'a> { /// No params. NoParams, - /// Positional params. + /// Positional params (heap allocated) Array(Vec), + /// Positional params (slices) + ArrayRef(&'a [JsonValue]), /// Params by name. - // - // TODO(niklasad1): maybe take a reference here but BTreeMap needs allocation anyway. Map(BTreeMap<&'a str, JsonValue>), } @@ -204,6 +207,12 @@ impl<'a> From> for JsonRpcParams<'a> { } } +impl<'a> From<&'a [JsonValue]> for JsonRpcParams<'a> { + fn from(slice: &'a [JsonValue]) -> Self { + Self::ArrayRef(slice) + } +} + /// Serializable [JSON-RPC object](https://www.jsonrpc.org/specification#request-object) #[derive(Serialize, Debug)] pub struct JsonRpcCallSer<'a> { @@ -252,8 +261,6 @@ pub struct JsonRpcNotificationParamsAlloc { } /// JSON-RPC notification response. -// NOTE(niklasad1): basically the same as Maciej version but I wanted to support Strings too. -// Maybe make subscription ID generic?! #[derive(Deserialize, Debug)] pub struct JsonRpcNotifAlloc { /// JSON-RPC version. @@ -273,6 +280,15 @@ pub enum SubscriptionId { Str(String), } +impl From for JsonValue { + fn from(sub_id: SubscriptionId) -> Self { + match sub_id { + SubscriptionId::Num(n) => n.into(), + SubscriptionId::Str(s) => s.into(), + } + } +} + /// Parse request ID from RawValue. pub fn parse_request_id(raw: Option<&RawValue>) -> Result { match raw { diff --git a/utils/src/hyper_helpers.rs b/utils/src/hyper_helpers.rs index 3c56d65805..62445a53ce 100644 --- a/utils/src/hyper_helpers.rs +++ b/utils/src/hyper_helpers.rs @@ -92,17 +92,6 @@ pub fn read_header_values<'a>( mod tests { use super::{read_header_content_length, read_response_to_body}; - #[tokio::test] - async fn body_to_request_works() { - /*let s = r#"[{"a":"hello"}]"#; - let expected: JsonRpcRequest = serde_json::from_str(s).unwrap(); - let body = hyper::Body::from(s.to_owned()); - let headers = hyper::header::HeaderMap::new(); - let bytes = read_response_to_body(&headers, body, 10 * 1024 * 1024).await.unwrap(); - let req: JsonRpcRequest = serde_json::from_slice(&bytes).unwrap(); - assert_eq!(req, expected);*/ - } - #[tokio::test] async fn body_to_bytes_size_limit_works() { let headers = hyper::header::HeaderMap::new(); diff --git a/ws-client/src/helpers.rs b/ws-client/src/helpers.rs index a59fb709b8..244ff1935a 100644 --- a/ws-client/src/helpers.rs +++ b/ws-client/src/helpers.rs @@ -1,22 +1,17 @@ use crate::manager::{RequestManager, RequestStatus}; use crate::transport::Sender as WsSender; use futures::channel::mpsc; -use jsonrpsee_types::{ - v2::{ - parse_request_id, JsonRpcCallSer, JsonRpcErrorAlloc, JsonRpcNotifAlloc, JsonRpcParams, JsonRpcResponse, - SubscriptionId, - }, - Error, RequestMessage, +use jsonrpsee_types::v2::{ + parse_request_id, JsonRpcCallSer, JsonRpcErrorAlloc, JsonRpcNotifAlloc, JsonRpcParams, JsonRpcResponse, + SubscriptionId, }; +use jsonrpsee_types::{Error, RequestMessage}; use serde_json::Value as JsonValue; /// Attempts to process a batch response. /// /// On success the result is sent to the frontend. -pub fn process_batch_response<'a>( - manager: &mut RequestManager, - rps: Vec>, -) -> Result<(), Error> { +pub fn process_batch_response(manager: &mut RequestManager, rps: Vec>) -> Result<(), Error> { let mut digest = Vec::with_capacity(rps.len()); let mut ordered_responses = vec![JsonValue::Null; rps.len()]; let mut rps_unordered: Vec<_> = Vec::with_capacity(rps.len()); @@ -139,20 +134,21 @@ pub async fn stop_subscription(sender: &mut WsSender, manager: &mut RequestManag } } -/// Builds an unsubscription message, semantically the same as an ordinary request. +/// Builds an unsubscription message. pub fn build_unsubscribe_message( manager: &mut RequestManager, sub_req_id: u64, sub_id: SubscriptionId, ) -> Option { let (unsub_req_id, _, unsub, sub_id) = manager.remove_subscription(sub_req_id, sub_id)?; + let sub_id_slice: &[JsonValue] = &[sub_id.into()]; if manager.insert_pending_call(unsub_req_id, None).is_err() { log::warn!("Unsubscribe message failed to get slot in the RequestManager"); return None; } // TODO(niklasad): better type for params or maybe a macro?!. - let params = JsonRpcParams::Array(vec![serde_json::to_value(sub_id).unwrap()]); - let raw = serde_json::to_string(&JsonRpcCallSer::new(unsub_req_id, &unsub, params)).unwrap(); + let params = JsonRpcParams::ArrayRef(sub_id_slice); + let raw = serde_json::to_string(&JsonRpcCallSer::new(unsub_req_id, &unsub, params)).ok()?; Some(RequestMessage { raw, id: unsub_req_id, send_back: None }) } diff --git a/ws-client/src/manager.rs b/ws-client/src/manager.rs index 2a577d3d52..dfe4dbdacf 100644 --- a/ws-client/src/manager.rs +++ b/ws-client/src/manager.rs @@ -47,7 +47,7 @@ pub struct BatchState { pub send_back: PendingBatchOneshot, } -#[derive(Debug)] +#[derive(Debug, Default)] /// Manages and monitors JSONRPC v2 method calls and subscriptions. pub struct RequestManager { /// List of requests that are waiting for a response from the server. @@ -60,9 +60,9 @@ pub struct RequestManager { } impl RequestManager { - /// Create a new `RequestManager` with specified capacity. + /// Create a new `RequestManager`. pub fn new() -> Self { - Self { requests: FnvHashMap::default(), subscriptions: HashMap::new(), batches: HashMap::default() } + Self::default() } /// Tries to insert a new pending call. diff --git a/ws-client/src/tests.rs b/ws-client/src/tests.rs index ac710fa0c4..7821cc5815 100644 --- a/ws-client/src/tests.rs +++ b/ws-client/src/tests.rs @@ -138,9 +138,9 @@ async fn run_request_with_response(response: String) -> Result fn assert_error_response(error: Error, code: i32, message: &str) { match &error { - Error::Request(err) => { - assert_eq!(err.inner.code.code(), code); - assert_eq!(&err.inner.message, message); + Error::Request(e) => { + assert_eq!(e.error.code.code(), code); + assert_eq!(e.error.message, message); } e @ _ => panic!("Expected error: \"{}\", got: {:?}", error, e), }; From 78153c93919bb9c62b1ee2473b0649e3973f3e15 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sun, 18 Apr 2021 22:36:39 +0200 Subject: [PATCH 30/41] [examples]: remove unused async-std dep --- examples/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/Cargo.toml b/examples/Cargo.toml index ee4a3f886f..251259e9ee 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -8,7 +8,6 @@ publish = false [dev-dependencies] anyhow = "1" -async-std = "1" env_logger = "0.8" jsonrpsee = { path = "../jsonrpsee", features = ["full"] } log = "0.4" From bee0f8b70613f0457c07bd3424658c0a147d7944 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sun, 18 Apr 2021 22:45:36 +0200 Subject: [PATCH 31/41] Update types/src/v2/mod.rs --- types/src/v2/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/src/v2/mod.rs b/types/src/v2/mod.rs index f76a1de4de..f3e2f6c63d 100644 --- a/types/src/v2/mod.rs +++ b/types/src/v2/mod.rs @@ -189,7 +189,7 @@ pub enum JsonRpcParams<'a> { NoParams, /// Positional params (heap allocated) Array(Vec), - /// Positional params (slices) + /// Positional params (slice) ArrayRef(&'a [JsonValue]), /// Params by name. Map(BTreeMap<&'a str, JsonValue>), From 76d2c40adf8a4deaf89557273c37fd0a01591404 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 19 Apr 2021 11:13:38 +0200 Subject: [PATCH 32/41] [types]: remove unsed dep smallvec --- types/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/types/Cargo.toml b/types/Cargo.toml index 34a195c6ed..81e9056348 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -14,5 +14,4 @@ futures-util = { version = "0.3", default-features = false, features = ["std", " log = { version = "0.4", default-features = false } serde = { version = "1", default-features = false, features = ["derive"] } serde_json = { version = "1", default-features = false, features = ["alloc", "raw_value", "std"] } -smallvec = "1.0" thiserror = "1.0" From b055e6d30e429e04fa1ae9e268b7792e7601df25 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 19 Apr 2021 12:03:42 +0200 Subject: [PATCH 33/41] rewrite me --- http-client/src/tests.rs | 4 ++-- types/src/v2/error.rs | 42 +++++++++++++++++++++------------------- types/src/v2/mod.rs | 7 ++++++- ws-client/src/tests.rs | 4 ++-- 4 files changed, 32 insertions(+), 25 deletions(-) diff --git a/http-client/src/tests.rs b/http-client/src/tests.rs index 1012e8391d..6b5f11bf2a 100644 --- a/http-client/src/tests.rs +++ b/http-client/src/tests.rs @@ -111,8 +111,8 @@ async fn run_request_with_response(response: String) -> Result fn assert_jsonrpc_error_response(error: Error, code: i32, message: &str) { match &error { Error::Request(e) => { - assert_eq!(e.error.code.code(), code); - assert_eq!(e.error.message, message); + assert_eq!(e.error.code(), code); + assert_eq!(e.error.message(), message); } e @ _ => panic!("Expected error: \"{}\", got: {:?}", error, e), }; diff --git a/types/src/v2/error.rs b/types/src/v2/error.rs index 31acfa895f..a4744e43f9 100644 --- a/types/src/v2/error.rs +++ b/types/src/v2/error.rs @@ -1,26 +1,12 @@ use crate::JsonValue; -use serde::{de::Deserializer, ser::Serializer, Deserialize}; +use serde::{ + de::Deserializer, + ser::{SerializeSeq, Serializer}, + Deserialize, Serialize, +}; use std::fmt; use thiserror::Error; -/// [JSON-RPC Error object](https://www.jsonrpc.org/specification#error_object) -#[derive(Error, Debug, PartialEq, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct JsonRpcErrorObject { - /// Error code - pub code: ErrorCode, - /// Message - pub message: String, - /// Optional data - pub data: Option, -} - -impl fmt::Display for JsonRpcErrorObject { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}: {}: {:?}", self.code.code(), self.message, self.data) - } -} - /// Parse error code. pub const PARSE_ERROR_CODE: i32 = -32700; /// Internal error code. @@ -134,6 +120,22 @@ impl serde::Serialize for ErrorCode { where S: Serializer, { - serializer.serialize_i32(self.code()) + let mut seq = serializer.serialize_seq(Some(2))?; + seq.serialize_element(&self.code())?; + seq.serialize_element(self.message())?; + seq.end() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let code = ErrorCode::InternalError; + + let ser = serde_json::to_string(&code).unwrap(); + panic!("{}", ser); } } diff --git a/types/src/v2/mod.rs b/types/src/v2/mod.rs index f3e2f6c63d..3355d308db 100644 --- a/types/src/v2/mod.rs +++ b/types/src/v2/mod.rs @@ -10,6 +10,11 @@ use thiserror::Error; /// JSON-RPC related error types. pub mod error; +//// JSON-RPC request types. +//pub mod request; + +/// JSON-RPC response types. + /// [JSON-RPC request object](https://www.jsonrpc.org/specification#request-object) #[derive(Deserialize, Debug)] #[serde(deny_unknown_fields)] @@ -85,7 +90,7 @@ pub struct JsonRpcErrorAlloc { /// JSON-RPC version. pub jsonrpc: TwoPointZero, /// Error object. - pub error: error::JsonRpcErrorObject, + pub error: error::ErrorCode, /// Request ID. pub id: u64, } diff --git a/ws-client/src/tests.rs b/ws-client/src/tests.rs index 7821cc5815..d959db21a5 100644 --- a/ws-client/src/tests.rs +++ b/ws-client/src/tests.rs @@ -139,8 +139,8 @@ async fn run_request_with_response(response: String) -> Result fn assert_error_response(error: Error, code: i32, message: &str) { match &error { Error::Request(e) => { - assert_eq!(e.error.code.code(), code); - assert_eq!(e.error.message, message); + assert_eq!(e.error.code(), code); + assert_eq!(e.error.message(), message); } e @ _ => panic!("Expected error: \"{}\", got: {:?}", error, e), }; From 3a7a09b1729d1d2690b17f7bd05d630fc20e9f2a Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 19 Apr 2021 23:04:28 +0200 Subject: [PATCH 34/41] [types]: error code impl ser/deser Manual implementation of serialize/deserialize to get rid of duplicated message string --- types/src/error.rs | 2 +- types/src/v2/error.rs | 99 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 82 insertions(+), 19 deletions(-) diff --git a/types/src/error.rs b/types/src/error.rs index 153eae5def..35d44c65ba 100644 --- a/types/src/error.rs +++ b/types/src/error.rs @@ -1,4 +1,4 @@ -use crate::v2::JsonRpcErrorAlloc; +use crate::v2::error::JsonRpcErrorAlloc; use std::fmt; /// Error. diff --git a/types/src/v2/error.rs b/types/src/v2/error.rs index a4744e43f9..a79d3ba704 100644 --- a/types/src/v2/error.rs +++ b/types/src/v2/error.rs @@ -1,12 +1,38 @@ -use crate::JsonValue; -use serde::{ - de::Deserializer, - ser::{SerializeSeq, Serializer}, - Deserialize, Serialize, -}; +use crate::v2::params::{Id, TwoPointZero}; +use serde::de::{Deserializer, MapAccess, Visitor}; +use serde::ser::{SerializeMap, Serializer}; +use serde::{Deserialize, Serialize}; +use serde_json::value::RawValue; use std::fmt; use thiserror::Error; +/// [Failed JSON-RPC response object](https://www.jsonrpc.org/specification#response_object). +#[derive(Serialize, Debug)] +pub struct JsonRpcError<'a> { + /// JSON-RPC version. + pub jsonrpc: TwoPointZero, + /// Error. + pub error: ErrorCode, + /// Request ID + pub id: Option<&'a RawValue>, +} +/// [Failed JSON-RPC response object with allocations](https://www.jsonrpc.org/specification#response_object). +#[derive(Error, Debug, Deserialize, PartialEq)] +pub struct JsonRpcErrorAlloc { + /// JSON-RPC version. + pub jsonrpc: TwoPointZero, + /// Error object. + pub error: ErrorCode, + /// Request ID. + pub id: Id, +} + +impl fmt::Display for JsonRpcErrorAlloc { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}: {}: {:?}", self.jsonrpc, self.error, self.id) + } +} + /// Parse error code. pub const PARSE_ERROR_CODE: i32 = -32700; /// Internal error code. @@ -87,7 +113,7 @@ impl ErrorCode { impl fmt::Display for ErrorCode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.code()) + write!(f, "{}: {}", self.code(), self.message()) } } @@ -110,8 +136,8 @@ impl<'a> serde::Deserialize<'a> for ErrorCode { where D: Deserializer<'a>, { - let code: i32 = serde::Deserialize::deserialize(deserializer)?; - Ok(ErrorCode::from(code)) + let code = deserializer.deserialize_map(ErrorCodeVisitor)?; + Ok(code) } } @@ -120,22 +146,59 @@ impl serde::Serialize for ErrorCode { where S: Serializer, { - let mut seq = serializer.serialize_seq(Some(2))?; - seq.serialize_element(&self.code())?; - seq.serialize_element(self.message())?; - seq.end() + let mut map = serializer.serialize_map(Some(2))?; + map.serialize_entry("code", &self.code())?; + map.serialize_entry("message", self.message())?; + map.end() + } +} + +struct ErrorCodeVisitor; + +impl<'de> Visitor<'de> for ErrorCodeVisitor { + type Value = ErrorCode; + + // Format a message stating what data this Visitor expects to receive. + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("code") + } + + fn visit_map(self, mut access: M) -> Result + where + M: MapAccess<'de>, + { + let mut maybe_code = None; + + while let Ok(Some((key, val))) = access.next_entry::<&str, i32>() { + if key == "code" && maybe_code.is_none() { + maybe_code = Some(val.into()) + } + } + + let code = maybe_code.ok_or_else(|| serde::de::Error::missing_field("code"))?; + Ok(code) } } #[cfg(test)] mod tests { - use super::*; + use super::{ErrorCode, Id, JsonRpcError, JsonRpcErrorAlloc, TwoPointZero}; #[test] - fn it_works() { - let code = ErrorCode::InternalError; + fn deserialize_works() { + let ser = r#"{"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null}"#; + let err: JsonRpcErrorAlloc = serde_json::from_str(ser).unwrap(); + assert_eq!(err.jsonrpc, TwoPointZero); + assert_eq!(err.error, ErrorCode::ParseError); + assert_eq!(err.id, Id::Null); + } - let ser = serde_json::to_string(&code).unwrap(); - panic!("{}", ser); + #[test] + fn serialize_works() { + let exp = r#"{"jsonrpc": "2.0", "error": {"code": -32608, "message": "Internal error"}, "id": 1337}"#; + let raw_id = serde_json::value::to_raw_value(&1337).unwrap(); + let err = JsonRpcError { jsonrpc: TwoPointZero, error: ErrorCode::InternalError, id: Some(&*raw_id) }; + let ser = serde_json::to_string(&err).unwrap(); + assert_eq!(exp, ser); } } From bbdafb7872e910ab31ad52d269e1fb3a719be749 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 19 Apr 2021 23:08:14 +0200 Subject: [PATCH 35/41] [types v2]: re-org with explicit mods --- benches/bench.rs | 7 +- examples/ws.rs | 2 +- examples/ws_subscription.rs | 2 +- http-client/src/client.rs | 11 +- http-client/src/tests.rs | 4 +- http-server/src/module.rs | 2 +- http-server/src/server.rs | 17 +- proc-macros/src/lib.rs | 2 +- tests/tests/integration_tests.rs | 2 +- types/src/client.rs | 2 +- types/src/traits.rs | 2 +- types/src/v2/mod.rs | 303 +------------------------------ utils/src/server.rs | 15 +- ws-client/src/client.rs | 18 +- ws-client/src/helpers.rs | 21 ++- ws-client/src/manager.rs | 4 +- ws-client/src/tests.rs | 6 +- ws-server/src/server.rs | 19 +- 18 files changed, 78 insertions(+), 361 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index a14924692a..774cfba2e7 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -2,7 +2,8 @@ use criterion::*; use jsonrpsee::{ http_client::{ traits::Client, - v2::{JsonRpcCallSer, JsonRpcParams}, + v2::params::{Id, JsonRpcParams}, + v2::request::JsonRpcCallSer, HttpClientBuilder, }, ws_client::WsClientBuilder, @@ -24,7 +25,7 @@ pub fn jsonrpsee_types_v2(crit: &mut Criterion) { b.iter(|| { let params = &[1_u64.into(), 2_u32.into()]; let params = JsonRpcParams::ArrayRef(params); - let request = JsonRpcCallSer::new(0, "say_hello", params); + let request = JsonRpcCallSer::new(Id::Number(0), "say_hello", params); v2_serialize(request); }) }); @@ -32,7 +33,7 @@ pub fn jsonrpsee_types_v2(crit: &mut Criterion) { crit.bench_function("jsonrpsee_types_v2_vec", |b| { b.iter(|| { let params = JsonRpcParams::Array(vec![1_u64.into(), 2_u32.into()]); - let request = JsonRpcCallSer::new(0, "say_hello", params); + let request = JsonRpcCallSer::new(Id::Number(0), "say_hello", params); v2_serialize(request); }) }); diff --git a/examples/ws.rs b/examples/ws.rs index 209361e819..ec1f071b0f 100644 --- a/examples/ws.rs +++ b/examples/ws.rs @@ -25,7 +25,7 @@ // DEALINGS IN THE SOFTWARE. use jsonrpsee::{ - ws_client::{traits::Client, v2::JsonRpcParams, WsClientBuilder}, + ws_client::{traits::Client, v2::params::JsonRpcParams, WsClientBuilder}, ws_server::WsServer, }; use std::net::SocketAddr; diff --git a/examples/ws_subscription.rs b/examples/ws_subscription.rs index 23c6d6f5df..cba815c3a7 100644 --- a/examples/ws_subscription.rs +++ b/examples/ws_subscription.rs @@ -25,7 +25,7 @@ // DEALINGS IN THE SOFTWARE. use jsonrpsee::{ - ws_client::{traits::SubscriptionClient, v2::JsonRpcParams, Subscription, WsClientBuilder}, + ws_client::{traits::SubscriptionClient, v2::params::JsonRpcParams, Subscription, WsClientBuilder}, ws_server::WsServer, }; use std::net::SocketAddr; diff --git a/http-client/src/client.rs b/http-client/src/client.rs index 726ac2f83c..33bc996f41 100644 --- a/http-client/src/client.rs +++ b/http-client/src/client.rs @@ -1,6 +1,11 @@ use crate::traits::Client; use crate::transport::HttpTransportClient; -use crate::v2::{JsonRpcCallSer, JsonRpcErrorAlloc, JsonRpcNotificationSer, JsonRpcParams, JsonRpcResponse}; +use crate::v2::request::{JsonRpcCallSer, JsonRpcNotificationSer}; +use crate::v2::{ + error::JsonRpcErrorAlloc, + params::{Id, JsonRpcParams}, + response::JsonRpcResponse, +}; use crate::{Error, JsonRawValue}; use async_trait::async_trait; use fnv::FnvHashMap; @@ -60,7 +65,7 @@ impl Client for HttpClient { { // NOTE: `fetch_add` wraps on overflow which is intended. let id = self.request_id.fetch_add(1, Ordering::Relaxed); - let request = JsonRpcCallSer::new(id, method, params); + let request = JsonRpcCallSer::new(Id::Number(id), method, params); let body = self .transport @@ -96,7 +101,7 @@ impl Client for HttpClient { for (pos, (method, params)) in batch.into_iter().enumerate() { let id = self.request_id.fetch_add(1, Ordering::SeqCst); - batch_request.push(JsonRpcCallSer::new(id, method, params)); + batch_request.push(JsonRpcCallSer::new(Id::Number(id), method, params)); ordered_requests.push(id); request_set.insert(id, pos); } diff --git a/http-client/src/tests.rs b/http-client/src/tests.rs index 6b5f11bf2a..eb5484da34 100644 --- a/http-client/src/tests.rs +++ b/http-client/src/tests.rs @@ -2,7 +2,7 @@ use crate::v2::error::{ INTERNAL_ERROR_CODE, INTERNAL_ERROR_MSG, INVALID_PARAMS_CODE, INVALID_PARAMS_MSG, INVALID_REQUEST_CODE, INVALID_REQUEST_MSG, METHOD_NOT_FOUND_CODE, METHOD_NOT_FOUND_MSG, PARSE_ERROR_CODE, PARSE_ERROR_MSG, }; -use crate::v2::JsonRpcParams; +use crate::v2::params::JsonRpcParams; use crate::{traits::Client, Error, HttpClientBuilder, JsonValue}; use jsonrpsee_test_utils::helpers::*; use jsonrpsee_test_utils::types::Id; @@ -114,6 +114,6 @@ fn assert_jsonrpc_error_response(error: Error, code: i32, message: &str) { assert_eq!(e.error.code(), code); assert_eq!(e.error.message(), message); } - e @ _ => panic!("Expected error: \"{}\", got: {:?}", error, e), + e => panic!("Expected error: \"{}\", got: {:?}", error, e), }; } diff --git a/http-server/src/module.rs b/http-server/src/module.rs index 1427eebcd7..5852e73d25 100644 --- a/http-server/src/module.rs +++ b/http-server/src/module.rs @@ -1,4 +1,4 @@ -use jsonrpsee_types::{error::RpcError, traits::RpcMethod, v2::RpcParams, Error}; +use jsonrpsee_types::{error::RpcError, traits::RpcMethod, v2::params::RpcParams, Error}; use jsonrpsee_utils::server::{send_response, Methods}; use serde::Serialize; use std::sync::Arc; diff --git a/http-server/src/server.rs b/http-server/src/server.rs index 89eb5c03a1..c2f25b1f2c 100644 --- a/http-server/src/server.rs +++ b/http-server/src/server.rs @@ -35,11 +35,8 @@ use hyper::{ Error as HyperError, }; use jsonrpsee_types::error::{Error, GenericTransportError, RpcError}; -use jsonrpsee_types::v2::error::{ - INVALID_REQUEST_CODE, INVALID_REQUEST_MSG, METHOD_NOT_FOUND_CODE, METHOD_NOT_FOUND_MSG, PARSE_ERROR_CODE, - PARSE_ERROR_MSG, -}; -use jsonrpsee_types::v2::{JsonRpcInvalidRequest, JsonRpcRequest, RpcParams}; +use jsonrpsee_types::v2::request::{JsonRpcInvalidRequest, JsonRpcRequest}; +use jsonrpsee_types::v2::{error::ErrorCode, params::RpcParams}; use jsonrpsee_utils::{hyper_helpers::read_response_to_body, server::send_error}; use serde::Serialize; use socket2::{Domain, Socket, Type}; @@ -187,15 +184,15 @@ impl Server { log::error!("method_call: {} failed: {:?}", req.method, err); } } else { - send_error(req.id, &tx, METHOD_NOT_FOUND_CODE, METHOD_NOT_FOUND_MSG); + send_error(req.id, &tx, ErrorCode::MethodNotFound); } } Err(_e) => { - let (id, code, msg) = match serde_json::from_slice::(&body) { - Ok(req) => (req.id, INVALID_REQUEST_CODE, INVALID_REQUEST_MSG), - Err(_) => (None, PARSE_ERROR_CODE, PARSE_ERROR_MSG), + let (id, err) = match serde_json::from_slice::(&body) { + Ok(req) => (req.id, ErrorCode::InvalidRequest), + Err(_) => (None, ErrorCode::ParseError), }; - send_error(id, &tx, code, msg); + send_error(id, &tx, err); } }; diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs index 764639f666..659ab78122 100644 --- a/proc-macros/src/lib.rs +++ b/proc-macros/src/lib.rs @@ -238,7 +238,7 @@ fn build_client_functions(api: &api_def::ApiDefinition) -> Result #_crate::v2::JsonRpcParams::NoParams) + quote_spanned!(function.signature.span()=> #_crate::v2::params::JsonRpcParams::NoParams) } else if function.attributes.positional_params { quote_spanned!(function.signature.span()=> vec![#(#params_to_array),*].into()) } else { diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index aaa586710f..16195da1e2 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -31,7 +31,7 @@ mod helpers; use helpers::{http_server, websocket_server, websocket_server_with_subscription}; use jsonrpsee::{ http_client::{traits::Client, Error, HttpClientBuilder}, - ws_client::{traits::SubscriptionClient, v2::JsonRpcParams, JsonValue, Subscription, WsClientBuilder}, + ws_client::{traits::SubscriptionClient, v2::params::JsonRpcParams, JsonValue, Subscription, WsClientBuilder}, }; use std::sync::Arc; use std::time::Duration; diff --git a/types/src/client.rs b/types/src/client.rs index a51bd59639..a902334013 100644 --- a/types/src/client.rs +++ b/types/src/client.rs @@ -1,4 +1,4 @@ -use crate::{v2::SubscriptionId, Error}; +use crate::{v2::params::SubscriptionId, Error}; use core::marker::PhantomData; use futures_channel::{mpsc, oneshot}; use futures_util::{future::FutureExt, sink::SinkExt, stream::StreamExt}; diff --git a/types/src/traits.rs b/types/src/traits.rs index cab98a7344..a8692497b3 100644 --- a/types/src/traits.rs +++ b/types/src/traits.rs @@ -1,4 +1,4 @@ -use crate::v2::{JsonRpcParams, RpcParams}; +use crate::v2::params::{JsonRpcParams, RpcParams}; use crate::{error::RpcError, Error, Subscription}; use async_trait::async_trait; use serde::de::DeserializeOwned; diff --git a/types/src/v2/mod.rs b/types/src/v2/mod.rs index 3355d308db..a3f977ab7e 100644 --- a/types/src/v2/mod.rs +++ b/types/src/v2/mod.rs @@ -1,298 +1,15 @@ -use crate::{error::RpcError, Cow, Error}; -use alloc::collections::BTreeMap; -use serde::de::{self, DeserializeOwned, Deserializer, Unexpected, Visitor}; -use serde::ser::Serializer; -use serde::{Deserialize, Serialize}; -use serde_json::{value::RawValue, Value as JsonValue}; -use std::fmt; -use thiserror::Error; +use crate::Error; +use serde::de::DeserializeOwned; +use serde_json::value::RawValue; -/// JSON-RPC related error types. +/// JSON-RPC error related types. pub mod error; - -//// JSON-RPC request types. -//pub mod request; - -/// JSON-RPC response types. - -/// [JSON-RPC request object](https://www.jsonrpc.org/specification#request-object) -#[derive(Deserialize, Debug)] -#[serde(deny_unknown_fields)] -pub struct JsonRpcRequest<'a> { - /// JSON-RPC version. - pub jsonrpc: TwoPointZero, - /// Request ID - #[serde(borrow)] - pub id: Option<&'a RawValue>, - /// Name of the method to be invoked. - #[serde(borrow)] - pub method: Cow<'a, str>, - /// Parameter values of the request. - #[serde(borrow)] - pub params: Option<&'a RawValue>, -} - -/// Invalid request with known request ID. -#[derive(Deserialize, Debug)] -pub struct JsonRpcInvalidRequest<'a> { - /// Request ID - #[serde(borrow)] - pub id: Option<&'a RawValue>, -} - -/// JSON-RPC notification (a request object without a request ID). -#[derive(Serialize, Deserialize, Debug)] -pub struct JsonRpcNotification<'a> { - /// JSON-RPC version. - pub jsonrpc: TwoPointZero, - /// Name of the method to be invoked. - pub method: &'a str, - /// Parameter values of the request. - pub params: JsonRpcNotificationParams<'a>, -} - -/// JSON-RPC parameter values for subscriptions. -#[derive(Serialize, Deserialize, Debug)] -pub struct JsonRpcNotificationParams<'a> { - /// Subscription ID - pub subscription: u64, - /// Result. - #[serde(borrow)] - pub result: &'a RawValue, -} - -/// JSON-RPC successful response object. -#[derive(Serialize, Deserialize, Debug)] -pub struct JsonRpcResponse<'a, T> { - /// JSON-RPC version. - pub jsonrpc: TwoPointZero, - /// Result. - pub result: T, - /// Request ID - #[serde(borrow)] - pub id: Option<&'a RawValue>, -} - -/// JSON-RPC error response object. -#[derive(Serialize, Debug)] -pub struct JsonRpcError<'a> { - /// JSON-RPC version. - pub jsonrpc: TwoPointZero, - /// Error. - pub error: JsonRpcErrorParams<'a>, - /// Request ID - pub id: Option<&'a RawValue>, -} - -/// [Failed JSON-RPC response object](https://www.jsonrpc.org/specification#response_object). -#[derive(Error, Debug, Deserialize, PartialEq)] -pub struct JsonRpcErrorAlloc { - /// JSON-RPC version. - pub jsonrpc: TwoPointZero, - /// Error object. - pub error: error::ErrorCode, - /// Request ID. - pub id: u64, -} - -impl fmt::Display for JsonRpcErrorAlloc { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}: {}: {}", self.jsonrpc, self.error, self.id) - } -} - -/// [JSON-RPC error object](https://www.jsonrpc.org/specification#error-object) -#[derive(Serialize, Debug)] -pub struct JsonRpcErrorParams<'a> { - /// Error code. - pub code: i32, - /// Error message. - pub message: &'a str, -} - -/// JSON-RPC v2 marker type. -#[derive(Debug, Default, PartialEq)] -pub struct TwoPointZero; - -struct TwoPointZeroVisitor; - -impl<'de> Visitor<'de> for TwoPointZeroVisitor { - type Value = TwoPointZero; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str(r#"a string "2.0""#) - } - - fn visit_str(self, s: &str) -> Result - where - E: de::Error, - { - match s { - "2.0" => Ok(TwoPointZero), - _ => Err(de::Error::invalid_value(Unexpected::Str(s), &self)), - } - } -} - -impl<'de> Deserialize<'de> for TwoPointZero { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_str(TwoPointZeroVisitor) - } -} - -impl Serialize for TwoPointZero { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str("2.0") - } -} - -/// Parameters sent with the RPC request -#[derive(Clone, Copy, Debug)] -pub struct RpcParams<'a>(Option<&'a str>); - -impl<'a> RpcParams<'a> { - /// Create params - pub fn new(raw: Option<&'a str>) -> Self { - Self(raw) - } - - /// Attempt to parse all parameters as array or map into type T - pub fn parse(self) -> Result - where - T: Deserialize<'a>, - { - match self.0 { - None => Err(RpcError::InvalidParams), - Some(params) => serde_json::from_str(params).map_err(|_| RpcError::InvalidParams), - } - } - - /// Attempt to parse only the first parameter from an array into type T - pub fn one(self) -> Result - where - T: Deserialize<'a>, - { - self.parse::<[T; 1]>().map(|[res]| res) - } -} - -/// [JSON-RPC parameters](https://www.jsonrpc.org/specification#parameter_structures) -/// -/// If your type implement `Into` call that 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)] -#[serde(untagged)] -pub enum JsonRpcParams<'a> { - /// No params. - NoParams, - /// Positional params (heap allocated) - Array(Vec), - /// Positional params (slice) - ArrayRef(&'a [JsonValue]), - /// Params by name. - Map(BTreeMap<&'a str, JsonValue>), -} - -impl<'a> From> for JsonRpcParams<'a> { - fn from(map: BTreeMap<&'a str, JsonValue>) -> Self { - Self::Map(map) - } -} - -impl<'a> From> for JsonRpcParams<'a> { - fn from(arr: Vec) -> Self { - Self::Array(arr) - } -} - -impl<'a> From<&'a [JsonValue]> for JsonRpcParams<'a> { - fn from(slice: &'a [JsonValue]) -> Self { - Self::ArrayRef(slice) - } -} - -/// Serializable [JSON-RPC object](https://www.jsonrpc.org/specification#request-object) -#[derive(Serialize, Debug)] -pub struct JsonRpcCallSer<'a> { - /// JSON-RPC version. - pub jsonrpc: TwoPointZero, - /// Name of the method to be invoked. - pub method: &'a str, - /// Request ID - pub id: u64, - /// Parameter values of the request. - pub params: JsonRpcParams<'a>, -} - -impl<'a> JsonRpcCallSer<'a> { - /// Create a new serializable JSON-RPC request. - pub fn new(id: u64, method: &'a str, params: JsonRpcParams<'a>) -> Self { - Self { jsonrpc: TwoPointZero, id, method, params } - } -} - -/// Serializable [JSON-RPC notification object](https://www.jsonrpc.org/specification#request-object) -#[derive(Serialize, Debug)] -pub struct JsonRpcNotificationSer<'a> { - /// JSON-RPC version. - pub jsonrpc: TwoPointZero, - /// Name of the method to be invoked. - pub method: &'a str, - /// Parameter values of the request. - pub params: JsonRpcParams<'a>, -} - -impl<'a> JsonRpcNotificationSer<'a> { - /// Create a new serializable JSON-RPC request. - pub fn new(method: &'a str, params: JsonRpcParams<'a>) -> Self { - Self { jsonrpc: TwoPointZero, method, params } - } -} - -/// JSON-RPC parameter values for subscriptions. -#[derive(Deserialize, Debug)] -pub struct JsonRpcNotificationParamsAlloc { - /// Subscription ID - pub subscription: SubscriptionId, - /// Result. - pub result: T, -} - -/// JSON-RPC notification response. -#[derive(Deserialize, Debug)] -pub struct JsonRpcNotifAlloc { - /// JSON-RPC version. - pub jsonrpc: TwoPointZero, - /// Params. - pub params: JsonRpcNotificationParamsAlloc, -} - -/// Id of a subscription, communicated by the server. -#[derive(Debug, PartialEq, Clone, Hash, Eq, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -#[serde(untagged)] -pub enum SubscriptionId { - /// Numeric id - Num(u64), - /// String id - Str(String), -} - -impl From for JsonValue { - fn from(sub_id: SubscriptionId) -> Self { - match sub_id { - SubscriptionId::Num(n) => n.into(), - SubscriptionId::Str(s) => s.into(), - } - } -} +/// JSON_RPC params related types. +pub mod params; +/// JSON-RPC request object related types +pub mod request; +/// JSON-RPC response object related types. +pub mod response; /// Parse request ID from RawValue. pub fn parse_request_id(raw: Option<&RawValue>) -> Result { diff --git a/utils/src/server.rs b/utils/src/server.rs index 26c8471036..f28f81ae8e 100644 --- a/utils/src/server.rs +++ b/utils/src/server.rs @@ -1,8 +1,9 @@ //! Shared helpers for JSON-RPC Servers. use futures_channel::mpsc; -use jsonrpsee_types::v2::error::{INTERNAL_ERROR_CODE, INTERNAL_ERROR_MSG}; -use jsonrpsee_types::v2::{JsonRpcError, JsonRpcErrorParams, JsonRpcResponse, RpcParams, TwoPointZero}; +use jsonrpsee_types::v2::error::{ErrorCode, JsonRpcError}; +use jsonrpsee_types::v2::params::{RpcParams, TwoPointZero}; +use jsonrpsee_types::v2::response::JsonRpcResponse; use rustc_hash::FxHashMap; use serde::Serialize; use serde_json::value::RawValue; @@ -25,7 +26,7 @@ pub fn send_response(id: RpcId, tx: RpcSender, result: impl Serialize) { Err(err) => { log::error!("Error serializing response: {:?}", err); - return send_error(id, tx, INTERNAL_ERROR_CODE, INTERNAL_ERROR_MSG); + return send_error(id, tx, ErrorCode::InternalError); } }; @@ -35,12 +36,8 @@ pub fn send_response(id: RpcId, tx: RpcSender, result: impl Serialize) { } /// Helper for sending JSON-RPC errors to the client -pub fn send_error(id: RpcId, tx: RpcSender, code: i32, message: &str) { - let json = match serde_json::to_string(&JsonRpcError { - jsonrpc: TwoPointZero, - error: JsonRpcErrorParams { code, message }, - id, - }) { +pub fn send_error(id: RpcId, tx: RpcSender, error: ErrorCode) { + let json = match serde_json::to_string(&JsonRpcError { jsonrpc: TwoPointZero, error, id }) { Ok(json) => json, Err(err) => { log::error!("Error serializing error message: {:?}", err); diff --git a/ws-client/src/client.rs b/ws-client/src/client.rs index 0f20a6d248..5749445885 100644 --- a/ws-client/src/client.rs +++ b/ws-client/src/client.rs @@ -30,9 +30,10 @@ use crate::helpers::{ }; use crate::traits::{Client, SubscriptionClient}; use crate::transport::{parse_url, Receiver as WsReceiver, Sender as WsSender, WsTransportClientBuilder}; -use crate::v2::{ - JsonRpcCallSer, JsonRpcErrorAlloc, JsonRpcNotifAlloc, JsonRpcNotificationSer, JsonRpcParams, JsonRpcResponse, -}; +use crate::v2::error::JsonRpcErrorAlloc; +use crate::v2::params::{Id, JsonRpcParams}; +use crate::v2::request::{JsonRpcCallSer, JsonRpcNotificationSer}; +use crate::v2::response::{JsonRpcNotifResponse, JsonRpcResponse}; use crate::{ manager::RequestManager, BatchMessage, Error, FrontToBack, RequestMessage, Subscription, SubscriptionMessage, }; @@ -308,7 +309,8 @@ impl Client for WsClient { { let (send_back_tx, send_back_rx) = oneshot::channel(); let req_id = self.id_guard.next_request_id()?; - let raw = serde_json::to_string(&JsonRpcCallSer::new(req_id, method, params)).map_err(Error::ParseError)?; + let raw = serde_json::to_string(&JsonRpcCallSer::new(Id::Number(req_id), method, params)) + .map_err(Error::ParseError)?; log::trace!("[frontend]: send request: {:?}", raw); if self @@ -350,7 +352,7 @@ impl Client for WsClient { let mut batches = Vec::with_capacity(batch.len()); for (idx, (method, params)) in batch.into_iter().enumerate() { - batches.push(JsonRpcCallSer::new(batch_ids[idx], method, params)); + batches.push(JsonRpcCallSer::new(Id::Number(batch_ids[idx]), method, params)); } let (send_back_tx, send_back_rx) = oneshot::channel(); @@ -405,8 +407,8 @@ impl SubscriptionClient for WsClient { } let ids = self.id_guard.next_request_ids(2)?; - let raw = - serde_json::to_string(&JsonRpcCallSer::new(ids[0], subscribe_method, params)).map_err(Error::ParseError)?; + let raw = serde_json::to_string(&JsonRpcCallSer::new(Id::Number(ids[0]), subscribe_method, params)) + .map_err(Error::ParseError)?; let (send_back_tx, send_back_rx) = oneshot::channel(); if self @@ -543,7 +545,7 @@ async fn background_task( } } // Subscription response. - else if let Ok(notif) = serde_json::from_slice::>(&raw) { + else if let Ok(notif) = serde_json::from_slice::>(&raw) { log::debug!("[backend]: recv subscription {:?}", notif); if let Err(Some(unsub)) = process_subscription_response(&mut manager, notif) { let _ = stop_subscription(&mut sender, &mut manager, unsub).await; diff --git a/ws-client/src/helpers.rs b/ws-client/src/helpers.rs index 244ff1935a..1956cc606d 100644 --- a/ws-client/src/helpers.rs +++ b/ws-client/src/helpers.rs @@ -1,11 +1,11 @@ use crate::manager::{RequestManager, RequestStatus}; use crate::transport::Sender as WsSender; use futures::channel::mpsc; -use jsonrpsee_types::v2::{ - parse_request_id, JsonRpcCallSer, JsonRpcErrorAlloc, JsonRpcNotifAlloc, JsonRpcParams, JsonRpcResponse, - SubscriptionId, -}; -use jsonrpsee_types::{Error, RequestMessage}; +use jsonrpsee_types::v2::params::{Id, JsonRpcParams, SubscriptionId}; +use jsonrpsee_types::v2::parse_request_id; +use jsonrpsee_types::v2::request::JsonRpcCallSer; +use jsonrpsee_types::v2::response::{JsonRpcNotifResponse, JsonRpcResponse}; +use jsonrpsee_types::{v2::error::JsonRpcErrorAlloc, Error, RequestMessage}; use serde_json::Value as JsonValue; /// Attempts to process a batch response. @@ -47,7 +47,7 @@ pub fn process_batch_response(manager: &mut RequestManager, rps: Vec, + notif: JsonRpcNotifResponse, ) -> Result<(), Option> { let sub_id = notif.params.subscription; let request_id = match manager.get_request_id_by_subscription_id(&sub_id) { @@ -148,7 +148,7 @@ pub fn build_unsubscribe_message( } // TODO(niklasad): better type for params or maybe a macro?!. let params = JsonRpcParams::ArrayRef(sub_id_slice); - let raw = serde_json::to_string(&JsonRpcCallSer::new(unsub_req_id, &unsub, params)).ok()?; + let raw = serde_json::to_string(&JsonRpcCallSer::new(Id::Number(unsub_req_id), &unsub, params)).ok()?; Some(RequestMessage { raw, id: unsub_req_id, send_back: None }) } @@ -157,14 +157,15 @@ pub fn build_unsubscribe_message( /// Returns `Ok` if the response was successfully sent. /// Returns `Err(_)` if the response ID was not found. pub fn process_error_response(manager: &mut RequestManager, err: JsonRpcErrorAlloc) -> Result<(), Error> { - match manager.request_status(&err.id) { + let id = err.id.as_number().copied().ok_or(Error::InvalidRequestId)?; + match manager.request_status(&id) { RequestStatus::PendingMethodCall => { - let send_back = manager.complete_pending_call(err.id).expect("State checked above; qed"); + let send_back = manager.complete_pending_call(id).expect("State checked above; qed"); let _ = send_back.map(|s| s.send(Err(Error::Request(err)))); Ok(()) } RequestStatus::PendingSubscription => { - let (_, send_back, _) = manager.complete_pending_subscription(err.id).expect("State checked above; qed"); + let (_, send_back, _) = manager.complete_pending_subscription(id).expect("State checked above; qed"); let _ = send_back.send(Err(Error::Request(err))); Ok(()) } diff --git a/ws-client/src/manager.rs b/ws-client/src/manager.rs index dfe4dbdacf..34da0bb76c 100644 --- a/ws-client/src/manager.rs +++ b/ws-client/src/manager.rs @@ -8,7 +8,7 @@ use fnv::FnvHashMap; use futures::channel::{mpsc, oneshot}; -use jsonrpsee_types::{v2::SubscriptionId, Error, JsonValue}; +use jsonrpsee_types::{v2::params::SubscriptionId, Error, JsonValue}; use std::collections::hash_map::{Entry, HashMap}; #[derive(Debug)] @@ -247,7 +247,7 @@ impl RequestManager { mod tests { use super::{Error, RequestManager}; use futures::channel::{mpsc, oneshot}; - use jsonrpsee_types::v2::SubscriptionId; + use jsonrpsee_types::v2::params::SubscriptionId; use serde_json::Value as JsonValue; #[test] diff --git a/ws-client/src/tests.rs b/ws-client/src/tests.rs index d959db21a5..b6a7cfd5b7 100644 --- a/ws-client/src/tests.rs +++ b/ws-client/src/tests.rs @@ -1,6 +1,6 @@ #![cfg(test)] -use crate::v2::{error::*, JsonRpcParams}; +use crate::v2::{error::*, params::JsonRpcParams}; use crate::{ traits::{Client, SubscriptionClient}, Error, Subscription, WsClientBuilder, @@ -73,7 +73,7 @@ async fn subscription_works() { { let mut sub: Subscription = client.subscribe("subscribe_hello", JsonRpcParams::NoParams, "unsubscribe_hello").await.unwrap(); - let response: String = sub.next().await.unwrap().into(); + let response: String = sub.next().await.unwrap(); assert_eq!("hello my friend".to_owned(), response); } } @@ -142,6 +142,6 @@ fn assert_error_response(error: Error, code: i32, message: &str) { assert_eq!(e.error.code(), code); assert_eq!(e.error.message(), message); } - e @ _ => panic!("Expected error: \"{}\", got: {:?}", error, e), + e => panic!("Expected error: \"{}\", got: {:?}", error, e), }; } diff --git a/ws-server/src/server.rs b/ws-server/src/server.rs index 47404383fa..cfea120666 100644 --- a/ws-server/src/server.rs +++ b/ws-server/src/server.rs @@ -40,12 +40,9 @@ use tokio_stream::{wrappers::TcpListenerStream, StreamExt}; use tokio_util::compat::TokioAsyncReadCompatExt; use jsonrpsee_types::error::{Error, RpcError}; -use jsonrpsee_types::v2::error::{ - INVALID_REQUEST_CODE, INVALID_REQUEST_MSG, METHOD_NOT_FOUND_CODE, METHOD_NOT_FOUND_MSG, PARSE_ERROR_CODE, - PARSE_ERROR_MSG, -}; -use jsonrpsee_types::v2::{JsonRpcInvalidRequest, JsonRpcRequest, TwoPointZero}; -use jsonrpsee_types::v2::{JsonRpcNotification, JsonRpcNotificationParams, RpcParams}; +use jsonrpsee_types::v2::error::ErrorCode; +use jsonrpsee_types::v2::params::{JsonRpcNotificationParams, RpcParams, TwoPointZero}; +use jsonrpsee_types::v2::request::{JsonRpcInvalidRequest, JsonRpcNotification, JsonRpcRequest}; use jsonrpsee_utils::server::{send_error, ConnectionId, Methods}; mod module; @@ -192,16 +189,16 @@ async fn background_task(socket: tokio::net::TcpStream, methods: Arc, i if let Some(method) = methods.get(&*req.method) { (method)(req.id, params, &tx, id)?; } else { - send_error(req.id, &tx, METHOD_NOT_FOUND_CODE, METHOD_NOT_FOUND_MSG); + send_error(req.id, &tx, ErrorCode::MethodNotFound); } } Err(_) => { - let (id, code, msg) = match serde_json::from_slice::(&data) { - Ok(req) => (req.id, INVALID_REQUEST_CODE, INVALID_REQUEST_MSG), - Err(_) => (None, PARSE_ERROR_CODE, PARSE_ERROR_MSG), + let (id, err) = match serde_json::from_slice::(&data) { + Ok(req) => (req.id, ErrorCode::InvalidRequest), + Err(_) => (None, ErrorCode::ParseError), }; - send_error(id, &tx, code, msg); + send_error(id, &tx, err); } } } From 667048420e4e47da64ebf5704c6493d0bc77d6b1 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 19 Apr 2021 23:29:01 +0200 Subject: [PATCH 36/41] fix faulty test --- types/src/v2/error.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/src/v2/error.rs b/types/src/v2/error.rs index a79d3ba704..86be8d9cc2 100644 --- a/types/src/v2/error.rs +++ b/types/src/v2/error.rs @@ -186,7 +186,7 @@ mod tests { #[test] fn deserialize_works() { - let ser = r#"{"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null}"#; + let ser = r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":null}"#; let err: JsonRpcErrorAlloc = serde_json::from_str(ser).unwrap(); assert_eq!(err.jsonrpc, TwoPointZero); assert_eq!(err.error, ErrorCode::ParseError); @@ -195,7 +195,7 @@ mod tests { #[test] fn serialize_works() { - let exp = r#"{"jsonrpc": "2.0", "error": {"code": -32608, "message": "Internal error"}, "id": 1337}"#; + let exp = r#"{"jsonrpc":"2.0","error":{"code":-32603,"message":"Internal error"},"id":1337}"#; let raw_id = serde_json::value::to_raw_value(&1337).unwrap(); let err = JsonRpcError { jsonrpc: TwoPointZero, error: ErrorCode::InternalError, id: Some(&*raw_id) }; let ser = serde_json::to_string(&err).unwrap(); From 714113bc57caa0c5bf7abcfdc20464ee9d00d0a4 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 19 Apr 2021 23:31:27 +0200 Subject: [PATCH 37/41] add missed files --- types/src/v2/params.rs | 193 +++++++++++++++++++++++++++++++++++++++ types/src/v2/request.rs | 78 ++++++++++++++++ types/src/v2/response.rs | 24 +++++ 3 files changed, 295 insertions(+) create mode 100644 types/src/v2/params.rs create mode 100644 types/src/v2/request.rs create mode 100644 types/src/v2/response.rs diff --git a/types/src/v2/params.rs b/types/src/v2/params.rs new file mode 100644 index 0000000000..0053e2b35c --- /dev/null +++ b/types/src/v2/params.rs @@ -0,0 +1,193 @@ +use crate::error::RpcError; +use alloc::collections::BTreeMap; +use serde::de::{self, Deserializer, Unexpected, Visitor}; +use serde::ser::Serializer; +use serde::{Deserialize, Serialize}; +use serde_json::{value::RawValue, Value as JsonValue}; +use std::fmt; + +/// JSON-RPC parameter values for subscriptions. +#[derive(Serialize, Deserialize, Debug)] +pub struct JsonRpcNotificationParams<'a> { + /// Subscription ID + pub subscription: u64, + /// Result. + #[serde(borrow)] + pub result: &'a RawValue, +} + +/// JSON-RPC parameter values for subscriptions with support for number and strings. +#[derive(Deserialize, Debug)] +pub struct JsonRpcNotificationParamsAlloc { + /// Subscription ID + pub subscription: SubscriptionId, + /// Result. + pub result: T, +} + +/// JSON-RPC v2 marker type. +#[derive(Debug, Default, PartialEq)] +pub struct TwoPointZero; + +struct TwoPointZeroVisitor; + +impl<'de> Visitor<'de> for TwoPointZeroVisitor { + type Value = TwoPointZero; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(r#"a string "2.0""#) + } + + fn visit_str(self, s: &str) -> Result + where + E: de::Error, + { + match s { + "2.0" => Ok(TwoPointZero), + _ => Err(de::Error::invalid_value(Unexpected::Str(s), &self)), + } + } +} + +impl<'de> Deserialize<'de> for TwoPointZero { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(TwoPointZeroVisitor) + } +} + +impl Serialize for TwoPointZero { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str("2.0") + } +} + +/// Parameters sent with the RPC request +#[derive(Clone, Copy, Debug)] +pub struct RpcParams<'a>(Option<&'a str>); + +impl<'a> RpcParams<'a> { + /// Create params + pub fn new(raw: Option<&'a str>) -> Self { + Self(raw) + } + + /// Attempt to parse all parameters as array or map into type T + pub fn parse(self) -> Result + where + T: Deserialize<'a>, + { + match self.0 { + None => Err(RpcError::InvalidParams), + Some(params) => serde_json::from_str(params).map_err(|_| RpcError::InvalidParams), + } + } + + /// Attempt to parse only the first parameter from an array into type T + pub fn one(self) -> Result + where + T: Deserialize<'a>, + { + self.parse::<[T; 1]>().map(|[res]| res) + } +} + +/// [Serializable JSON-RPC parameters](https://www.jsonrpc.org/specification#parameter_structures) +/// +/// If your type implement `Into` call that 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)] +#[serde(untagged)] +pub enum JsonRpcParams<'a> { + /// No params. + NoParams, + /// Positional params (heap allocated) + Array(Vec), + /// Positional params (slice) + ArrayRef(&'a [JsonValue]), + /// Params by name. + Map(BTreeMap<&'a str, JsonValue>), +} + +impl<'a> From> for JsonRpcParams<'a> { + fn from(map: BTreeMap<&'a str, JsonValue>) -> Self { + Self::Map(map) + } +} + +impl<'a> From> for JsonRpcParams<'a> { + fn from(arr: Vec) -> Self { + Self::Array(arr) + } +} + +impl<'a> From<&'a [JsonValue]> for JsonRpcParams<'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)] +#[serde(untagged)] +pub enum SubscriptionId { + /// Numeric id + Num(u64), + /// String id + Str(String), +} + +impl From for JsonValue { + fn from(sub_id: SubscriptionId) -> Self { + match sub_id { + SubscriptionId::Num(n) => n.into(), + SubscriptionId::Str(s) => s.into(), + } + } +} + +/// Request Id +#[derive(Debug, PartialEq, Clone, Hash, Eq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +#[serde(untagged)] +pub enum Id { + /// Null + Null, + /// Numeric id + Number(u64), + /// String id + Str(String), +} + +impl Id { + /// If the Id is a number, returns the associated number. Returns None otherwise. + pub fn as_number(&self) -> Option<&u64> { + match self { + Id::Number(n) => Some(n), + _ => None, + } + } + + /// If the Id is a String, returns the associated str. Returns None otherwise. + pub fn as_str(&self) -> Option<&str> { + match self { + Id::Str(s) => Some(s), + _ => None, + } + } + + /// If the ID is Null, returns (). Returns None otherwise. + pub fn as_null(&self) -> Option<()> { + match self { + Id::Null => Some(()), + _ => None, + } + } +} diff --git a/types/src/v2/request.rs b/types/src/v2/request.rs new file mode 100644 index 0000000000..8baa1b392c --- /dev/null +++ b/types/src/v2/request.rs @@ -0,0 +1,78 @@ +use crate::v2::params::{Id, JsonRpcNotificationParams, JsonRpcParams, TwoPointZero}; +use beef::Cow; +use serde::{Deserialize, Serialize}; +use serde_json::value::RawValue; + +/// [JSON-RPC request object](https://www.jsonrpc.org/specification#request-object) +#[derive(Deserialize, Debug)] +#[serde(deny_unknown_fields)] +pub struct JsonRpcRequest<'a> { + /// JSON-RPC version. + pub jsonrpc: TwoPointZero, + /// Request ID + #[serde(borrow)] + pub id: Option<&'a RawValue>, + /// Name of the method to be invoked. + #[serde(borrow)] + pub method: Cow<'a, str>, + /// Parameter values of the request. + #[serde(borrow)] + pub params: Option<&'a RawValue>, +} + +/// Invalid request with known request ID. +#[derive(Deserialize, Debug)] +pub struct JsonRpcInvalidRequest<'a> { + /// Request ID + #[serde(borrow)] + pub id: Option<&'a RawValue>, +} + +/// JSON-RPC notification (a request object without a request ID). +#[derive(Serialize, Deserialize, Debug)] +pub struct JsonRpcNotification<'a> { + /// JSON-RPC version. + pub jsonrpc: TwoPointZero, + /// Name of the method to be invoked. + pub method: &'a str, + /// Parameter values of the request. + pub params: JsonRpcNotificationParams<'a>, +} + +/// Serializable [JSON-RPC object](https://www.jsonrpc.org/specification#request-object) +#[derive(Serialize, Debug)] +pub struct JsonRpcCallSer<'a> { + /// JSON-RPC version. + pub jsonrpc: TwoPointZero, + /// Name of the method to be invoked. + pub method: &'a str, + /// Request ID + pub id: Id, + /// Parameter values of the request. + pub params: JsonRpcParams<'a>, +} + +impl<'a> JsonRpcCallSer<'a> { + /// Create a new serializable JSON-RPC request. + pub fn new(id: Id, method: &'a str, params: JsonRpcParams<'a>) -> Self { + Self { jsonrpc: TwoPointZero, id, method, params } + } +} + +/// Serializable [JSON-RPC notification object](https://www.jsonrpc.org/specification#request-object) +#[derive(Serialize, Debug)] +pub struct JsonRpcNotificationSer<'a> { + /// JSON-RPC version. + pub jsonrpc: TwoPointZero, + /// Name of the method to be invoked. + pub method: &'a str, + /// Parameter values of the request. + pub params: JsonRpcParams<'a>, +} + +impl<'a> JsonRpcNotificationSer<'a> { + /// Create a new serializable JSON-RPC request. + pub fn new(method: &'a str, params: JsonRpcParams<'a>) -> Self { + Self { jsonrpc: TwoPointZero, method, params } + } +} diff --git a/types/src/v2/response.rs b/types/src/v2/response.rs new file mode 100644 index 0000000000..addc181867 --- /dev/null +++ b/types/src/v2/response.rs @@ -0,0 +1,24 @@ +use crate::v2::params::{JsonRpcNotificationParamsAlloc, TwoPointZero}; +use serde::{Deserialize, Serialize}; +use serde_json::value::RawValue; + +/// JSON-RPC successful response object. +#[derive(Serialize, Deserialize, Debug)] +pub struct JsonRpcResponse<'a, T> { + /// JSON-RPC version. + pub jsonrpc: TwoPointZero, + /// Result. + pub result: T, + /// Request ID + #[serde(borrow)] + pub id: Option<&'a RawValue>, +} + +/// JSON-RPC notification response. +#[derive(Deserialize, Debug)] +pub struct JsonRpcNotifResponse { + /// JSON-RPC version. + pub jsonrpc: TwoPointZero, + /// Params. + pub params: JsonRpcNotificationParamsAlloc, +} From 6c2210d771f62df6a9b34934975ff3a6e2223949 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 20 Apr 2021 12:31:50 +0200 Subject: [PATCH 38/41] [ws client]: req_manager reserve unsubscribe slot. --- tests/tests/integration_tests.rs | 14 ++++++++++++++ ws-client/src/client.rs | 33 ++++++++++++++++++++++---------- ws-client/src/helpers.rs | 4 ---- ws-client/src/manager.rs | 30 ++++++++++++++++++++++++++--- 4 files changed, 64 insertions(+), 17 deletions(-) diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index 3d37cc6400..c7743550c9 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -212,3 +212,17 @@ async fn http_with_non_ascii_url_doesnt_hang_or_panic() { let err: Result<(), Error> = client.request("system_chain", JsonRpcParams::NoParams).await; assert!(matches!(err, Err(Error::TransportError(_)))); } + +#[tokio::test] +async fn ws_unsubscribe_releases_request_slots() { + let server_addr = websocket_server_with_subscription().await; + let server_url = format!("ws://{}", server_addr); + + let client = WsClientBuilder::default().max_concurrent_requests(1).build(&server_url).await.unwrap(); + + let sub1: Subscription = + client.subscribe("subscribe_hello", JsonRpcParams::NoParams, "unsubscribe_hello").await.unwrap(); + drop(sub1); + let _: Subscription = + client.subscribe("subscribe_hello", JsonRpcParams::NoParams, "unsubscribe_hello").await.unwrap(); +} diff --git a/ws-client/src/client.rs b/ws-client/src/client.rs index 5749445885..f48366833e 100644 --- a/ws-client/src/client.rs +++ b/ws-client/src/client.rs @@ -293,7 +293,10 @@ impl Client for WsClient { // NOTE: we use this to guard against max number of concurrent requests. let _req_id = self.id_guard.next_request_id()?; let notif = JsonRpcNotificationSer::new(method, params); - let raw = serde_json::to_string(¬if).map_err(Error::ParseError)?; + let raw = serde_json::to_string(¬if).map_err(|e| { + self.id_guard.reclaim_request_id(); + Error::ParseError(e) + })?; log::trace!("[frontend]: send notification: {:?}", raw); let res = self.to_back.clone().send(FrontToBack::Notification(raw)).await; self.id_guard.reclaim_request_id(); @@ -309,8 +312,10 @@ impl Client for WsClient { { let (send_back_tx, send_back_rx) = oneshot::channel(); let req_id = self.id_guard.next_request_id()?; - let raw = serde_json::to_string(&JsonRpcCallSer::new(Id::Number(req_id), method, params)) - .map_err(Error::ParseError)?; + let raw = serde_json::to_string(&JsonRpcCallSer::new(Id::Number(req_id), method, params)).map_err(|e| { + self.id_guard.reclaim_request_id(); + Error::ParseError(e) + })?; log::trace!("[frontend]: send request: {:?}", raw); if self @@ -357,7 +362,10 @@ impl Client for WsClient { let (send_back_tx, send_back_rx) = oneshot::channel(); - let raw = serde_json::to_string(&batches).map_err(Error::ParseError)?; + let raw = serde_json::to_string(&batches).map_err(|e| { + self.id_guard.reclaim_request_id(); + Error::ParseError(e) + })?; log::trace!("[frontend]: send batch request: {:?}", raw); if self .to_back @@ -401,14 +409,16 @@ impl SubscriptionClient for WsClient { { log::trace!("[frontend]: subscribe: {:?}, unsubscribe: {:?}", subscribe_method, unsubscribe_method); - let unsub_method = unsubscribe_method.to_owned(); if subscribe_method == unsubscribe_method { - return Err(Error::SubscriptionNameConflict(unsub_method)); + return Err(Error::SubscriptionNameConflict(unsubscribe_method.to_owned())); } let ids = self.id_guard.next_request_ids(2)?; - let raw = serde_json::to_string(&JsonRpcCallSer::new(Id::Number(ids[0]), subscribe_method, params)) - .map_err(Error::ParseError)?; + let raw = + serde_json::to_string(&JsonRpcCallSer::new(Id::Number(ids[0]), subscribe_method, params)).map_err(|e| { + self.id_guard.reclaim_request_id(); + Error::ParseError(e) + })?; let (send_back_tx, send_back_rx) = oneshot::channel(); if self @@ -418,16 +428,19 @@ impl SubscriptionClient for WsClient { raw, subscribe_id: ids[0], unsubscribe_id: ids[1], - unsubscribe_method: unsub_method, + unsubscribe_method: unsubscribe_method.to_owned(), send_back: send_back_tx, })) .await .is_err() { + self.id_guard.reclaim_request_id(); return Err(self.read_error_from_backend().await); } - let (notifs_rx, id) = match send_back_rx.await { + let res = send_back_rx.await; + self.id_guard.reclaim_request_id(); + let (notifs_rx, id) = match res { Ok(Ok(val)) => val, Ok(Err(err)) => return Err(err), Err(_) => return Err(self.read_error_from_backend().await), diff --git a/ws-client/src/helpers.rs b/ws-client/src/helpers.rs index 1956cc606d..d4c4156c91 100644 --- a/ws-client/src/helpers.rs +++ b/ws-client/src/helpers.rs @@ -142,10 +142,6 @@ pub fn build_unsubscribe_message( ) -> Option { let (unsub_req_id, _, unsub, sub_id) = manager.remove_subscription(sub_req_id, sub_id)?; let sub_id_slice: &[JsonValue] = &[sub_id.into()]; - if manager.insert_pending_call(unsub_req_id, None).is_err() { - log::warn!("Unsubscribe message failed to get slot in the RequestManager"); - return None; - } // TODO(niklasad): better type for params or maybe a macro?!. let params = JsonRpcParams::ArrayRef(sub_id_slice); let raw = serde_json::to_string(&JsonRpcCallSer::new(Id::Number(unsub_req_id), &unsub, params)).ok()?; diff --git a/ws-client/src/manager.rs b/ws-client/src/manager.rs index 34da0bb76c..b92676bbb0 100644 --- a/ws-client/src/manager.rs +++ b/ws-client/src/manager.rs @@ -101,7 +101,7 @@ impl RequestManager { Err(send_back) } } - /// Tries to insert a new pending subscription. + /// Tries to insert a new pending subscription and reserves a slot for a "potential" unsubscription request. /// /// Returns `Ok` if the pending request was successfully inserted otherwise `Err`. pub fn insert_pending_subscription( @@ -111,8 +111,13 @@ impl RequestManager { send_back: PendingSubscriptionOneshot, unsubscribe_method: UnsubscribeMethod, ) -> Result<(), PendingSubscriptionOneshot> { - if let Entry::Vacant(v) = self.requests.entry(sub_req_id) { - v.insert(Kind::PendingSubscription((unsub_req_id, send_back, unsubscribe_method))); + // The request IDs are not in the manager and the `sub_id` and `unsub_id` are not equal. + if !self.requests.contains_key(&sub_req_id) + && !self.requests.contains_key(&unsub_req_id) + && sub_req_id != unsub_req_id + { + self.requests.insert(sub_req_id, Kind::PendingSubscription((unsub_req_id, send_back, unsubscribe_method))); + self.requests.insert(unsub_req_id, Kind::PendingMethodCall(None)); Ok(()) } else { Err(send_back) @@ -281,6 +286,25 @@ mod tests { assert!(manager.remove_subscription(1, SubscriptionId::Str("uniq_id_from_server".to_string())).is_some()); } + #[test] + fn insert_subscription_with_same_sub_and_unsub_id_should_err() { + let (tx1, _) = oneshot::channel::, SubscriptionId), Error>>(); + let (tx2, _) = oneshot::channel::, SubscriptionId), Error>>(); + let (tx3, _) = oneshot::channel::, SubscriptionId), Error>>(); + let (tx4, _) = oneshot::channel::, SubscriptionId), Error>>(); + let mut manager = RequestManager::new(); + assert!(manager.insert_pending_subscription(1, 1, tx1, "unsubscribe_method".into()).is_err()); + assert!(manager.insert_pending_subscription(0, 1, tx2, "unsubscribe_method".into()).is_ok()); + assert!( + manager.insert_pending_subscription(99, 0, tx3, "unsubscribe_method".into()).is_err(), + "unsub request ID already occupied" + ); + assert!( + manager.insert_pending_subscription(99, 1, tx4, "unsubscribe_method".into()).is_err(), + "sub request ID already occupied" + ); + } + #[test] fn pending_method_call_faulty() { let (request_tx1, _) = oneshot::channel::>(); From 6f0591af6c5e091b8dcf1c0e76aec90bf221c22e Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 20 Apr 2021 13:10:41 +0200 Subject: [PATCH 39/41] simplify test code --- http-client/src/tests.rs | 23 ++++++++--------------- ws-client/src/tests.rs | 20 ++++++++------------ 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/http-client/src/tests.rs b/http-client/src/tests.rs index eb5484da34..46d556ef00 100644 --- a/http-client/src/tests.rs +++ b/http-client/src/tests.rs @@ -1,8 +1,4 @@ -use crate::v2::error::{ - INTERNAL_ERROR_CODE, INTERNAL_ERROR_MSG, INVALID_PARAMS_CODE, INVALID_PARAMS_MSG, INVALID_REQUEST_CODE, - INVALID_REQUEST_MSG, METHOD_NOT_FOUND_CODE, METHOD_NOT_FOUND_MSG, PARSE_ERROR_CODE, PARSE_ERROR_MSG, -}; -use crate::v2::params::JsonRpcParams; +use crate::v2::{error::ErrorCode, params::JsonRpcParams}; use crate::{traits::Client, Error, HttpClientBuilder, JsonValue}; use jsonrpsee_test_utils::helpers::*; use jsonrpsee_test_utils::types::Id; @@ -33,31 +29,31 @@ async fn response_with_wrong_id() { #[tokio::test] async fn response_method_not_found() { let err = run_request_with_response(method_not_found(Id::Num(0))).await.unwrap_err(); - assert_jsonrpc_error_response(err, METHOD_NOT_FOUND_CODE, METHOD_NOT_FOUND_MSG); + assert_jsonrpc_error_response(err, ErrorCode::MethodNotFound); } #[tokio::test] async fn response_parse_error() { let err = run_request_with_response(parse_error(Id::Num(0))).await.unwrap_err(); - assert_jsonrpc_error_response(err, PARSE_ERROR_CODE, PARSE_ERROR_MSG); + assert_jsonrpc_error_response(err, ErrorCode::ParseError); } #[tokio::test] async fn invalid_request_works() { let err = run_request_with_response(invalid_request(Id::Num(0_u64))).await.unwrap_err(); - assert_jsonrpc_error_response(err, INVALID_REQUEST_CODE, INVALID_REQUEST_MSG); + assert_jsonrpc_error_response(err, ErrorCode::InvalidRequest); } #[tokio::test] async fn invalid_params_works() { let err = run_request_with_response(invalid_params(Id::Num(0_u64))).await.unwrap_err(); - assert_jsonrpc_error_response(err, INVALID_PARAMS_CODE, INVALID_PARAMS_MSG); + assert_jsonrpc_error_response(err, ErrorCode::InvalidParams); } #[tokio::test] async fn internal_error_works() { let err = run_request_with_response(internal_error(Id::Num(0_u64))).await.unwrap_err(); - assert_jsonrpc_error_response(err, INTERNAL_ERROR_CODE, INTERNAL_ERROR_MSG); + assert_jsonrpc_error_response(err, ErrorCode::InternalError); } #[tokio::test] @@ -108,12 +104,9 @@ async fn run_request_with_response(response: String) -> Result client.request("say_hello", JsonRpcParams::NoParams).await } -fn assert_jsonrpc_error_response(error: Error, code: i32, message: &str) { +fn assert_jsonrpc_error_response(error: Error, code: ErrorCode) { match &error { - Error::Request(e) => { - assert_eq!(e.error.code(), code); - assert_eq!(e.error.message(), message); - } + Error::Request(e) => assert_eq!(e.error, code), e => panic!("Expected error: \"{}\", got: {:?}", error, e), }; } diff --git a/ws-client/src/tests.rs b/ws-client/src/tests.rs index b6a7cfd5b7..c5c6c57444 100644 --- a/ws-client/src/tests.rs +++ b/ws-client/src/tests.rs @@ -1,6 +1,6 @@ #![cfg(test)] -use crate::v2::{error::*, params::JsonRpcParams}; +use crate::v2::{error::ErrorCode, params::JsonRpcParams}; use crate::{ traits::{Client, SubscriptionClient}, Error, Subscription, WsClientBuilder, @@ -33,31 +33,31 @@ async fn response_with_wrong_id() { #[tokio::test] async fn response_method_not_found() { let err = run_request_with_response(method_not_found(Id::Num(0))).await.unwrap_err(); - assert_error_response(err, METHOD_NOT_FOUND_CODE, METHOD_NOT_FOUND_MSG); + assert_error_response(err, ErrorCode::MethodNotFound); } #[tokio::test] async fn parse_error_works() { let err = run_request_with_response(parse_error(Id::Num(0))).await.unwrap_err(); - assert_error_response(err, PARSE_ERROR_CODE, PARSE_ERROR_MSG); + assert_error_response(err, ErrorCode::ParseError); } #[tokio::test] async fn invalid_request_works() { let err = run_request_with_response(invalid_request(Id::Num(0_u64))).await.unwrap_err(); - assert_error_response(err, INVALID_REQUEST_CODE, INVALID_REQUEST_MSG); + assert_error_response(err, ErrorCode::InvalidRequest); } #[tokio::test] async fn invalid_params_works() { let err = run_request_with_response(invalid_params(Id::Num(0_u64))).await.unwrap_err(); - assert_error_response(err, INVALID_PARAMS_CODE, INVALID_PARAMS_MSG); + assert_error_response(err, ErrorCode::InvalidParams); } #[tokio::test] async fn internal_error_works() { let err = run_request_with_response(internal_error(Id::Num(0_u64))).await.unwrap_err(); - assert_error_response(err, INTERNAL_ERROR_CODE, INTERNAL_ERROR_MSG); + assert_error_response(err, ErrorCode::InternalError); } #[tokio::test] @@ -80,7 +80,6 @@ async fn subscription_works() { #[tokio::test] async fn batch_request_works() { - let _ = env_logger::try_init(); let batch_request = vec![ ("say_hello", JsonRpcParams::NoParams), ("say_goodbye", JsonRpcParams::Array(vec![0_u64.into(), 1.into(), 2.into()])), @@ -136,12 +135,9 @@ async fn run_request_with_response(response: String) -> Result client.request("say_hello", JsonRpcParams::NoParams).await } -fn assert_error_response(error: Error, code: i32, message: &str) { +fn assert_error_response(error: Error, code: ErrorCode) { match &error { - Error::Request(e) => { - assert_eq!(e.error.code(), code); - assert_eq!(e.error.message(), message); - } + Error::Request(e) => assert_eq!(e.error, code), e => panic!("Expected error: \"{}\", got: {:?}", error, e), }; } From dea03d859ca7faf3809e85b09ec2d1f5a50aa569 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 20 Apr 2021 13:14:21 +0200 Subject: [PATCH 40/41] add tracking issue for TODO --- ws-client/src/helpers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ws-client/src/helpers.rs b/ws-client/src/helpers.rs index d4c4156c91..540b630095 100644 --- a/ws-client/src/helpers.rs +++ b/ws-client/src/helpers.rs @@ -142,7 +142,7 @@ pub fn build_unsubscribe_message( ) -> 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(niklasad): better type for params or maybe a macro?!. + // TODO: https://github.com/paritytech/jsonrpsee/issues/275 let params = JsonRpcParams::ArrayRef(sub_id_slice); let raw = serde_json::to_string(&JsonRpcCallSer::new(Id::Number(unsub_req_id), &unsub, params)).ok()?; Some(RequestMessage { raw, id: unsub_req_id, send_back: None }) From 2e5267bfa3cd55ef4b95ab335e245e64d5ae3fb7 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 20 Apr 2021 13:33:30 +0200 Subject: [PATCH 41/41] remove unused deps --- http-client/Cargo.toml | 3 --- jsonrpsee/Cargo.toml | 7 ------- 2 files changed, 10 deletions(-) diff --git a/http-client/Cargo.toml b/http-client/Cargo.toml index 18f0a1ef1c..c65ec9add0 100644 --- a/http-client/Cargo.toml +++ b/http-client/Cargo.toml @@ -8,7 +8,6 @@ license = "MIT" [dependencies] async-trait = "0.1" -env_logger = "0.8" hyper13-rustls = { package = "hyper-rustls", version = "0.21", optional = true } hyper14-rustls = { package = "hyper-rustls", version = "0.22", optional = true } hyper14 = { package = "hyper", version = "0.14", features = ["client", "http1", "http2", "tcp"], optional = true } @@ -19,7 +18,6 @@ log = "0.4" serde = { version = "1.0", default-features = false, features = ["derive"] } serde_json = "1.0" thiserror = "1.0" -unicase = "2.6" url = "2.2" fnv = "1" @@ -30,5 +28,4 @@ tokio02 = ["hyper13", "hyper13-rustls", "jsonrpsee-utils/hyper_13"] [dev-dependencies] jsonrpsee-test-utils = { path = "../test-utils" } -jsonrpsee-proc-macros = { path = "../proc-macros" } tokio = { version = "1.0", features = ["net", "rt-multi-thread", "macros"] } diff --git a/jsonrpsee/Cargo.toml b/jsonrpsee/Cargo.toml index c472cdff06..5894f58095 100644 --- a/jsonrpsee/Cargo.toml +++ b/jsonrpsee/Cargo.toml @@ -14,13 +14,6 @@ ws-server = { path = "../ws-server", version = "0.2.0-alpha.4", package = "jsonr proc-macros = { path = "../proc-macros", version = "0.2.0-alpha.4", package = "jsonrpsee-proc-macros", optional = true } types = { path = "../types", version = "0.2.0-alpha.4", package = "jsonrpsee-types", optional = true } -[dev-dependencies] -env_logger = "0.8" -futures-channel = { version = "0.3", default-features = false } -log = "0.4" -test-utils = { path = "../test-utils", version = "0.2.0-alpha.4", package = "jsonrpsee-test-utils" } -tokio = { version = "1", features = ["full"] } - [features] client = ["http-client", "ws-client"] server = ["http-server", "ws-server"]