From 653cf78169bed999c466b53d427d2ede9b752cee Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 18 May 2021 16:48:14 +0200 Subject: [PATCH 01/17] get started --- http-client/src/client.rs | 26 ++++------- http-client/src/tests.rs | 15 ++++--- http-server/src/server.rs | 13 +++--- http-server/src/tests.rs | 2 +- types/src/error.rs | 4 +- types/src/v2/error.rs | 80 +++++----------------------------- types/src/v2/params.rs | 53 +++++++++++++++++++--- types/src/v2/request.rs | 41 +++++++++++++++-- types/src/v2/response.rs | 5 +-- utils/src/server/helpers.rs | 8 ++-- utils/src/server/rpc_module.rs | 4 +- ws-client/src/client.rs | 4 +- ws-client/src/helpers.rs | 13 +++--- ws-client/src/tests.rs | 15 ++++--- ws-server/src/server.rs | 12 ++--- 15 files changed, 153 insertions(+), 142 deletions(-) diff --git a/http-client/src/client.rs b/http-client/src/client.rs index c1bc3164d1..8523f8c399 100644 --- a/http-client/src/client.rs +++ b/http-client/src/client.rs @@ -2,11 +2,11 @@ use crate::traits::Client; use crate::transport::HttpTransportClient; use crate::v2::request::{JsonRpcCallSer, JsonRpcNotificationSer}; use crate::v2::{ - error::JsonRpcErrorAlloc, + error::JsonRpcError, params::{Id, JsonRpcParams}, response::JsonRpcResponse, }; -use crate::{Error, JsonRawValue, TEN_MB_SIZE_BYTES}; +use crate::{Error, TEN_MB_SIZE_BYTES}; use async_trait::async_trait; use fnv::FnvHashMap; use serde::de::DeserializeOwned; @@ -76,12 +76,12 @@ impl Client for HttpClient { let response: JsonRpcResponse<_> = match serde_json::from_slice(&body) { Ok(response) => response, Err(_) => { - let err: JsonRpcErrorAlloc = serde_json::from_slice(&body).map_err(Error::ParseError)?; - return Err(Error::Request(err)); + let err: JsonRpcError = serde_json::from_slice(&body).map_err(Error::ParseError)?; + return Err(Error::Request(err.to_string())); } }; - let response_id = parse_request_id(response.id)?; + let response_id = response.id.as_number().copied().ok_or(Error::InvalidRequestId)?; if response_id == id { Ok(response.result) @@ -115,15 +115,15 @@ impl Client for HttpClient { let rps: Vec> = match serde_json::from_slice(&body) { Ok(response) => response, Err(_) => { - let err: JsonRpcErrorAlloc = serde_json::from_slice(&body).map_err(Error::ParseError)?; - return Err(Error::Request(err)); + let err: JsonRpcError = serde_json::from_slice(&body).map_err(Error::ParseError)?; + return Err(Error::Request(err.to_string())); } }; // 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 response_id = rp.id.as_number().copied().ok_or(Error::InvalidRequestId)?; let pos = match request_set.get(&response_id) { Some(pos) => *pos, None => return Err(Error::InvalidRequestId), @@ -133,13 +133,3 @@ impl Client for HttpClient { Ok(responses) } } - -fn parse_request_id(raw: Option<&JsonRawValue>) -> 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 db184bae7c..03da46f90b 100644 --- a/http-client/src/tests.rs +++ b/http-client/src/tests.rs @@ -1,5 +1,5 @@ use crate::v2::{ - error::{JsonRpcErrorCode, JsonRpcErrorObjectAlloc}, + error::{JsonRpcError, JsonRpcErrorCode, JsonRpcErrorObject}, params::JsonRpcParams, }; use crate::{traits::Client, Error, HttpClientBuilder, JsonValue}; @@ -107,9 +107,14 @@ async fn run_request_with_response(response: String) -> Result client.request("say_hello", JsonRpcParams::NoParams).await } -fn assert_jsonrpc_error_response(error: Error, code: JsonRpcErrorObjectAlloc) { - match &error { - Error::Request(e) => assert_eq!(e.error, code), - e => panic!("Expected error: \"{}\", got: {:?}", error, e), +fn assert_jsonrpc_error_response(err: Error, exp: JsonRpcErrorObject) { + match &err { + Error::Request(e) => { + let this: JsonRpcError = serde_json::from_str(&e).unwrap(); + // NOTE: `RawValue` doesn't implement PartialEq. + assert_eq!(this.error.code, exp.code); + assert_eq!(this.error.message, exp.message); + } + e => panic!("Expected error: \"{}\", got: {:?}", err, e), }; } diff --git a/http-server/src/server.rs b/http-server/src/server.rs index 12f3038354..c700069f7c 100644 --- a/http-server/src/server.rs +++ b/http-server/src/server.rs @@ -34,8 +34,9 @@ use hyper::{ Error as HyperError, }; use jsonrpsee_types::error::{CallError, Error, GenericTransportError}; +use jsonrpsee_types::v2::error::JsonRpcErrorCode; +use jsonrpsee_types::v2::params::{Id, RpcParams}; use jsonrpsee_types::v2::request::{JsonRpcInvalidRequest, JsonRpcRequest}; -use jsonrpsee_types::v2::{error::JsonRpcErrorCode, params::RpcParams}; use jsonrpsee_utils::hyper_helpers::read_response_to_body; use jsonrpsee_utils::server::helpers::{collect_batch_response, send_error}; use jsonrpsee_utils::server::rpc_module::{MethodSink, RpcModule}; @@ -162,17 +163,17 @@ impl Server { if let Some(method) = methods.get(&*req.method) { let params = RpcParams::new(req.params.map(|params| params.get())); // NOTE(niklasad1): connection ID is unused thus hardcoded to `0`. - if let Err(err) = (method)(req.id, params, &tx, 0) { + if let Err(err) = (method)(req.id.to_owned(), params, &tx, 0) { log::error!( "execution of method call '{}' failed: {:?}, request id={:?}", req.method, err, req.id ); - send_error(req.id, &tx, JsonRpcErrorCode::ServerError(-1).into()); + send_error(req.id.to_owned(), &tx, JsonRpcErrorCode::ServerError(-1).into()); } } else { - send_error(req.id, &tx, JsonRpcErrorCode::MethodNotFound.into()); + send_error(req.id.to_owned(), &tx, JsonRpcErrorCode::MethodNotFound.into()); } }; @@ -218,7 +219,7 @@ impl Server { execute(&tx, req); } } else { - send_error(None, &tx, JsonRpcErrorCode::InvalidRequest.into()); + send_error(Id::Null, &tx, JsonRpcErrorCode::InvalidRequest.into()); } } else { log::error!( @@ -227,7 +228,7 @@ impl Server { ); let (id, code) = match serde_json::from_slice::(&body) { Ok(req) => (req.id, JsonRpcErrorCode::InvalidRequest), - Err(_) => (None, JsonRpcErrorCode::ParseError), + Err(_) => (Id::Null, JsonRpcErrorCode::ParseError), }; send_error(id, &tx, code.into()); } diff --git a/http-server/src/tests.rs b/http-server/src/tests.rs index 2101a5174d..f111c98fce 100644 --- a/http-server/src/tests.rs +++ b/http-server/src/tests.rs @@ -83,7 +83,7 @@ async fn invalid_single_method_call() { let req = r#"{"jsonrpc":"2.0","method":1, "params": "bar"}"#; let response = http_request(req.into(), uri.clone()).await.unwrap(); assert_eq!(response.status, StatusCode::OK); - assert_eq!(response.body, invalid_request(Id::Null)); + assert_eq!(response.body, parse_error(Id::Null)); } #[tokio::test] diff --git a/types/src/error.rs b/types/src/error.rs index e83feb21ac..2283aaa708 100644 --- a/types/src/error.rs +++ b/types/src/error.rs @@ -1,6 +1,4 @@ -use crate::v2::error::JsonRpcErrorAlloc; use std::fmt; - /// Convenience type for displaying errors. #[derive(Clone, Debug, PartialEq)] pub struct Mismatch { @@ -48,7 +46,7 @@ pub enum Error { Transport(#[source] Box), /// JSON-RPC request error. #[error("JSON-RPC request error: {0:?}")] - Request(#[source] JsonRpcErrorAlloc), + Request(String), /// Frontend/backend channel error. #[error("Frontend/backend channel error: {0}")] Internal(#[source] futures_channel::mpsc::SendError), diff --git a/types/src/v2/error.rs b/types/src/v2/error.rs index e48875be65..18f4446c82 100644 --- a/types/src/v2/error.rs +++ b/types/src/v2/error.rs @@ -2,57 +2,30 @@ use crate::v2::params::{Id, TwoPointZero}; use serde::de::Deserializer; use serde::ser::Serializer; use serde::{Deserialize, Serialize}; -use serde_json::value::{RawValue, Value as JsonValue}; +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)] +#[derive(Serialize, Deserialize, Debug)] pub struct JsonRpcError<'a> { /// JSON-RPC version. pub jsonrpc: TwoPointZero, /// Error. + #[serde(borrow)] pub error: JsonRpcErrorObject<'a>, /// 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, - /// JSON-RPC error object. - pub error: JsonRpcErrorObjectAlloc, - /// 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) - } + pub id: Id<'a>, } -/// JSON-RPC error object. -#[derive(Debug, PartialEq, Clone, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct JsonRpcErrorObjectAlloc { - /// Code - pub code: JsonRpcErrorCode, - /// Message - pub message: String, - /// Optional data - pub data: Option, -} - -impl From for JsonRpcErrorObjectAlloc { - fn from(code: JsonRpcErrorCode) -> Self { - Self { code, message: code.message().to_owned(), data: None } +impl<'a> fmt::Display for JsonRpcError<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", serde_json::to_string(&self).expect("infallible; qed")) } } /// JSON-RPC error object with no extra allocations. -#[derive(Debug, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] #[serde(deny_unknown_fields)] pub struct JsonRpcErrorObject<'a> { /// Code @@ -61,6 +34,7 @@ pub struct JsonRpcErrorObject<'a> { pub message: &'a str, /// Optional data #[serde(skip_serializing_if = "Option::is_none")] + #[serde(borrow)] pub data: Option<&'a RawValue>, } @@ -180,47 +154,15 @@ impl serde::Serialize for JsonRpcErrorCode { #[cfg(test)] mod tests { - use super::{ - Id, JsonRpcError, JsonRpcErrorAlloc, JsonRpcErrorCode, JsonRpcErrorObject, JsonRpcErrorObjectAlloc, - TwoPointZero, - }; - - #[test] - 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, - JsonRpcErrorObjectAlloc { code: JsonRpcErrorCode::ParseError, message: "Parse error".into(), data: None } - ); - assert_eq!(err.id, Id::Null); - } - - #[test] - fn deserialize_with_optional_data() { - let ser = r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error", "data":"vegan"},"id":null}"#; - let err: JsonRpcErrorAlloc = serde_json::from_str(ser).unwrap(); - assert_eq!(err.jsonrpc, TwoPointZero); - assert_eq!( - err.error, - JsonRpcErrorObjectAlloc { - code: JsonRpcErrorCode::ParseError, - message: "Parse error".into(), - data: Some("vegan".into()) - } - ); - assert_eq!(err.id, Id::Null); - } + use super::{Id, JsonRpcError, JsonRpcErrorCode, JsonRpcErrorObject, TwoPointZero}; #[test] fn serialize_works() { 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: JsonRpcErrorObject { code: JsonRpcErrorCode::InternalError, message: "Internal error", data: None }, - id: Some(&*raw_id), + id: Id::Number(1337), }; let ser = serde_json::to_string(&err).unwrap(); assert_eq!(exp, ser); diff --git a/types/src/v2/params.rs b/types/src/v2/params.rs index 9e505662eb..4ecc39a78e 100644 --- a/types/src/v2/params.rs +++ b/types/src/v2/params.rs @@ -4,6 +4,7 @@ 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::borrow::Cow; use std::fmt; /// JSON-RPC parameter values for subscriptions. @@ -26,7 +27,7 @@ pub struct JsonRpcNotificationParamsAlloc { } /// JSON-RPC v2 marker type. -#[derive(Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct TwoPointZero; struct TwoPointZeroVisitor; @@ -157,16 +158,16 @@ impl From for JsonValue { #[derive(Debug, PartialEq, Clone, Hash, Eq, Deserialize, Serialize)] #[serde(deny_unknown_fields)] #[serde(untagged)] -pub enum Id { +pub enum Id<'a> { /// Null Null, /// Numeric id Number(u64), /// String id - Str(String), + Str(Cow<'a, str>), } -impl Id { +impl<'a> Id<'a> { /// If the Id is a number, returns the associated number. Returns None otherwise. pub fn as_number(&self) -> Option<&u64> { match self { @@ -190,8 +191,46 @@ impl Id { _ => None, } } + + /// Creates owned data from borrowed data, allocates only for Strings. + pub fn to_owned(&self) -> Id<'static> { + match self { + Id::Null => Id::Null, + Id::Number(n) => Id::Number(*n), + Id::Str(Cow::Borrowed(s)) => Id::Str(Cow::Owned(s.to_string())), + Id::Str(Cow::Owned(s)) => Id::Str(Cow::Owned(s.clone())), + } + } } -/// Untyped JSON-RPC ID. -// TODO(niklasad1): this should be enforced to only accept: String, Number, or Null. -pub type JsonRpcRawId<'a> = Option<&'a serde_json::value::RawValue>; +#[cfg(test)] +mod test { + use super::{Cow, Id}; + + #[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::Number(2)); + + let s = r#""2x""#; + let deserialized: Id = serde_json::from_str(s).unwrap(); + assert_eq!(deserialized, Id::Str(Cow::Borrowed("2x"))); + + let s = r#"[null, 0, 2, "3"]"#; + let deserialized: Vec = serde_json::from_str(s).unwrap(); + assert_eq!(deserialized, vec![Id::Null, Id::Number(0), Id::Number(2), Id::Str("3".into())]); + } + + #[test] + fn id_serialization() { + let d = + vec![Id::Null, Id::Number(0), Id::Number(2), Id::Number(3), Id::Str("3".into()), Id::Str("test".into())]; + let serialized = serde_json::to_string(&d).unwrap(); + assert_eq!(serialized, r#"[null,0,2,3,"3","test"]"#); + } +} diff --git a/types/src/v2/request.rs b/types/src/v2/request.rs index 8baa1b392c..bbdc734c45 100644 --- a/types/src/v2/request.rs +++ b/types/src/v2/request.rs @@ -11,7 +11,7 @@ pub struct JsonRpcRequest<'a> { pub jsonrpc: TwoPointZero, /// Request ID #[serde(borrow)] - pub id: Option<&'a RawValue>, + pub id: Id<'a>, /// Name of the method to be invoked. #[serde(borrow)] pub method: Cow<'a, str>, @@ -25,7 +25,7 @@ pub struct JsonRpcRequest<'a> { pub struct JsonRpcInvalidRequest<'a> { /// Request ID #[serde(borrow)] - pub id: Option<&'a RawValue>, + pub id: Id<'a>, } /// JSON-RPC notification (a request object without a request ID). @@ -47,14 +47,14 @@ pub struct JsonRpcCallSer<'a> { /// Name of the method to be invoked. pub method: &'a str, /// Request ID - pub id: Id, + pub id: Id<'a>, /// 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 { + pub fn new(id: Id<'a>, method: &'a str, params: JsonRpcParams<'a>) -> Self { Self { jsonrpc: TwoPointZero, id, method, params } } } @@ -76,3 +76,36 @@ impl<'a> JsonRpcNotificationSer<'a> { Self { jsonrpc: TwoPointZero, method, params } } } + +#[cfg(test)] +mod test { + use super::{JsonRpcRequest, TwoPointZero}; + + #[test] + fn deserialize_valid_request_works() { + let ser = r#"{"jsonrpc":"2.0","method":"say_hello","params":[1,"bar"],"id":1}"#; + let dsr: JsonRpcRequest = serde_json::from_str(ser).unwrap(); + assert_eq!(dsr.method, "say_hello"); + assert_eq!(dsr.jsonrpc, TwoPointZero); + } + + #[test] + fn deserialize_valid_request_without_params_works() { + let ser = r#"{"jsonrpc":"2.0","method":"say_hello", "id":1}"#; + let dsr: JsonRpcRequest = serde_json::from_str(ser).unwrap(); + assert_eq!(dsr.method, "say_hello"); + assert_eq!(dsr.jsonrpc, TwoPointZero); + } + + #[test] + fn deserialize_request_bad_params_should_fail() { + let ser = r#"{"jsonrpc":"2.0","method":"say_hello","params":"lol","id":1}"#; + assert!(serde_json::from_str::(ser).is_err()); + } + + #[test] + fn deserialize_request_bad_id_should_fail() { + let ser = r#"{"jsonrpc":"2.0","method":"say_hello","params":[],"id":{}}"#; + assert!(serde_json::from_str::(ser).is_err()); + } +} diff --git a/types/src/v2/response.rs b/types/src/v2/response.rs index 44b0582f6d..cd7a3b308f 100644 --- a/types/src/v2/response.rs +++ b/types/src/v2/response.rs @@ -1,6 +1,5 @@ -use crate::v2::params::{JsonRpcNotificationParamsAlloc, TwoPointZero}; +use crate::v2::params::{Id, JsonRpcNotificationParamsAlloc, TwoPointZero}; use serde::{Deserialize, Serialize}; -use serde_json::value::RawValue; /// JSON-RPC successful response object. #[derive(Serialize, Deserialize, Debug)] @@ -11,7 +10,7 @@ pub struct JsonRpcResponse<'a, T> { pub result: T, /// Request ID #[serde(borrow)] - pub id: Option<&'a RawValue>, + pub id: Id<'a>, } /// JSON-RPC subscription response. diff --git a/utils/src/server/helpers.rs b/utils/src/server/helpers.rs index 83719aac3c..178252de63 100644 --- a/utils/src/server/helpers.rs +++ b/utils/src/server/helpers.rs @@ -2,13 +2,13 @@ use crate::server::rpc_module::MethodSink; use futures_channel::mpsc; use futures_util::stream::StreamExt; use jsonrpsee_types::v2::error::{JsonRpcError, JsonRpcErrorCode, JsonRpcErrorObject}; -use jsonrpsee_types::v2::params::{JsonRpcRawId, TwoPointZero}; +use jsonrpsee_types::v2::params::{Id, TwoPointZero}; use jsonrpsee_types::v2::response::JsonRpcResponse; use serde::Serialize; /// Helper for sending JSON-RPC responses to the client -pub fn send_response(id: JsonRpcRawId, tx: &MethodSink, result: impl Serialize) { - let json = match serde_json::to_string(&JsonRpcResponse { jsonrpc: TwoPointZero, id, result }) { +pub fn send_response(id: Id, tx: &MethodSink, result: impl Serialize) { + let json = match serde_json::to_string(&JsonRpcResponse { jsonrpc: TwoPointZero, id: id.clone(), result }) { Ok(json) => json, Err(err) => { log::error!("Error serializing response: {:?}", err); @@ -23,7 +23,7 @@ pub fn send_response(id: JsonRpcRawId, tx: &MethodSink, result: impl Serialize) } /// Helper for sending JSON-RPC errors to the client -pub fn send_error(id: JsonRpcRawId, tx: &MethodSink, error: JsonRpcErrorObject) { +pub fn send_error(id: Id, tx: &MethodSink, error: JsonRpcErrorObject) { let json = match serde_json::to_string(&JsonRpcError { jsonrpc: TwoPointZero, error, id }) { Ok(json) => json, Err(err) => { diff --git a/utils/src/server/rpc_module.rs b/utils/src/server/rpc_module.rs index ad0fe323d1..7672a157ac 100644 --- a/utils/src/server/rpc_module.rs +++ b/utils/src/server/rpc_module.rs @@ -3,7 +3,7 @@ use futures_channel::mpsc; use jsonrpsee_types::error::{CallError, Error}; use jsonrpsee_types::traits::RpcMethod; use jsonrpsee_types::v2::error::{JsonRpcErrorCode, JsonRpcErrorObject, CALL_EXECUTION_FAILED_CODE}; -use jsonrpsee_types::v2::params::{JsonRpcNotificationParams, JsonRpcRawId, RpcParams, TwoPointZero}; +use jsonrpsee_types::v2::params::{Id, JsonRpcNotificationParams, RpcParams, TwoPointZero}; use jsonrpsee_types::v2::request::JsonRpcNotification; use parking_lot::Mutex; @@ -16,7 +16,7 @@ use std::sync::Arc; /// implemented as a function pointer to a `Fn` function taking four arguments: /// the `id`, `params`, a channel the function uses to communicate the result (or error) /// back to `jsonrpsee`, and the connection ID (useful for the websocket transport). -pub type Method = Box anyhow::Result<()>>; +pub type Method = Box anyhow::Result<()>>; /// A collection of registered [`Method`]s. pub type Methods = FxHashMap<&'static str, Method>; /// Connection ID, used for stateful protocol such as WebSockets. diff --git a/ws-client/src/client.rs b/ws-client/src/client.rs index 3ee46b08f8..561a95ed27 100644 --- a/ws-client/src/client.rs +++ b/ws-client/src/client.rs @@ -26,7 +26,7 @@ use crate::traits::{Client, SubscriptionClient}; use crate::transport::{parse_url, Receiver as WsReceiver, Sender as WsSender, WsTransportClientBuilder}; -use crate::v2::error::JsonRpcErrorAlloc; +use crate::v2::error::JsonRpcError; use crate::v2::params::{Id, JsonRpcParams}; use crate::v2::request::{JsonRpcCallSer, JsonRpcNotificationSer}; use crate::v2::response::{JsonRpcNotifResponse, JsonRpcResponse, JsonRpcSubscriptionResponse}; @@ -651,7 +651,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 26acad88a9..3c38983839 100644 --- a/ws-client/src/helpers.rs +++ b/ws-client/src/helpers.rs @@ -2,10 +2,9 @@ use crate::manager::{RequestManager, RequestStatus}; use crate::transport::Sender as WsSender; use futures::channel::mpsc; 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, JsonRpcSubscriptionResponse}; -use jsonrpsee_types::{v2::error::JsonRpcErrorAlloc, Error, RequestMessage}; +use jsonrpsee_types::{v2::error::JsonRpcError, Error, RequestMessage}; use serde_json::Value as JsonValue; /// Attempts to process a batch response. @@ -17,7 +16,7 @@ pub fn process_batch_response(manager: &mut RequestManager, rps: Vec = Vec::with_capacity(rps.len()); for rp in rps { - let id = parse_request_id(rp.id)?; + let id = rp.id.as_number().copied().ok_or(Error::InvalidRequestId)?; digest.push(id); rps_unordered.push((id, rp.result)); } @@ -103,7 +102,7 @@ pub fn process_single_response( response: JsonRpcResponse, max_capacity_per_subscription: usize, ) -> Result, Error> { - let response_id = parse_request_id(response.id)?; + let response_id = response.id.as_number().copied().ok_or(Error::InvalidRequestId)?; match manager.request_status(&response_id) { RequestStatus::PendingMethodCall => { let send_back_oneshot = match manager.complete_pending_call(response_id) { @@ -173,17 +172,17 @@ 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> { +pub fn process_error_response(manager: &mut RequestManager, err: JsonRpcError) -> Result<(), Error> { 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(id).expect("State checked above; qed"); - let _ = send_back.map(|s| s.send(Err(Error::Request(err)))); + let _ = send_back.map(|s| s.send(Err(Error::Request(err.to_string())))); Ok(()) } RequestStatus::PendingSubscription => { let (_, send_back, _) = manager.complete_pending_subscription(id).expect("State checked above; qed"); - let _ = send_back.send(Err(Error::Request(err))); + let _ = send_back.send(Err(Error::Request(err.to_string()))); Ok(()) } _ => Err(Error::InvalidRequestId), diff --git a/ws-client/src/tests.rs b/ws-client/src/tests.rs index e395bbc431..3c1f1f0d5a 100644 --- a/ws-client/src/tests.rs +++ b/ws-client/src/tests.rs @@ -1,7 +1,7 @@ #![cfg(test)] use crate::v2::{ - error::{JsonRpcErrorCode, JsonRpcErrorObjectAlloc}, + error::{JsonRpcError, JsonRpcErrorCode, JsonRpcErrorObject}, params::JsonRpcParams, }; use crate::{ @@ -186,9 +186,14 @@ async fn run_request_with_response(response: String) -> Result client.request("say_hello", JsonRpcParams::NoParams).await } -fn assert_error_response(error: Error, code: JsonRpcErrorObjectAlloc) { - match &error { - Error::Request(e) => assert_eq!(e.error, code), - e => panic!("Expected error: \"{}\", got: {:?}", error, e), +fn assert_error_response(err: Error, exp: JsonRpcErrorObject) { + match &err { + Error::Request(e) => { + let this: JsonRpcError = serde_json::from_str(&e).unwrap(); + // NOTE: `RawValue` doesn't implement PartialEq. + assert_eq!(this.error.code, exp.code); + assert_eq!(this.error.message, exp.message); + } + e => panic!("Expected error: \"{}\", got: {:?}", err, e), }; } diff --git a/ws-server/src/server.rs b/ws-server/src/server.rs index 61c0b09048..03a79c7d62 100644 --- a/ws-server/src/server.rs +++ b/ws-server/src/server.rs @@ -37,7 +37,7 @@ use tokio_util::compat::TokioAsyncReadCompatExt; use jsonrpsee_types::error::{CallError, Error}; use jsonrpsee_types::v2::error::JsonRpcErrorCode; -use jsonrpsee_types::v2::params::RpcParams; +use jsonrpsee_types::v2::params::{Id, RpcParams}; use jsonrpsee_types::v2::request::{JsonRpcInvalidRequest, JsonRpcRequest}; use jsonrpsee_utils::server::helpers::{collect_batch_response, send_error}; use jsonrpsee_utils::server::rpc_module::{ConnectionId, MethodSink, Methods, RpcModule, SubscriptionSink}; @@ -141,12 +141,12 @@ async fn background_task( let execute = move |tx: &MethodSink, req: JsonRpcRequest| { if let Some(method) = methods.get(&*req.method) { let params = RpcParams::new(req.params.map(|params| params.get())); - if let Err(err) = (method)(req.id, params, &tx, conn_id) { + if let Err(err) = (method)(req.id.to_owned(), params, &tx, conn_id) { log::error!("execution of method call '{}' failed: {:?}, request id={:?}", req.method, err, req.id); - send_error(req.id, &tx, JsonRpcErrorCode::ServerError(-1).into()); + send_error(req.id.to_owned(), &tx, JsonRpcErrorCode::ServerError(-1).into()); } } else { - send_error(req.id, &tx, JsonRpcErrorCode::MethodNotFound.into()); + send_error(req.id.to_owned(), &tx, JsonRpcErrorCode::MethodNotFound.into()); } }; @@ -180,12 +180,12 @@ async fn background_task( log::error!("Error sending batch response to the client: {:?}", err) } } else { - send_error(None, &tx, JsonRpcErrorCode::InvalidRequest.into()); + send_error(Id::Null, &tx, JsonRpcErrorCode::InvalidRequest.into()); } } else { let (id, code) = match serde_json::from_slice::(&data) { Ok(req) => (req.id, JsonRpcErrorCode::InvalidRequest), - Err(_) => (None, JsonRpcErrorCode::ParseError), + Err(_) => (Id::Null, JsonRpcErrorCode::ParseError), }; send_error(id, &tx, code.into()); From 00e7636369a3a8c2ad3faee9d143f8b2f177b3c0 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 18 May 2021 17:09:10 +0200 Subject: [PATCH 02/17] add additional test --- types/src/v2/params.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/types/src/v2/params.rs b/types/src/v2/params.rs index 4ecc39a78e..ac4c5de08a 100644 --- a/types/src/v2/params.rs +++ b/types/src/v2/params.rs @@ -221,6 +221,9 @@ mod test { let deserialized: Id = serde_json::from_str(s).unwrap(); assert_eq!(deserialized, Id::Str(Cow::Borrowed("2x"))); + let s = r#"[1337]"#; + assert!(serde_json::from_str::(s).is_err()); + let s = r#"[null, 0, 2, "3"]"#; let deserialized: Vec = serde_json::from_str(s).unwrap(); assert_eq!(deserialized, vec![Id::Null, Id::Number(0), Id::Number(2), Id::Str("3".into())]); From d03e092bed282b602fd09f121b2af4825736e00c Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 18 May 2021 17:26:07 +0200 Subject: [PATCH 03/17] fix nits --- http-server/src/server.rs | 6 +++--- types/src/v2/params.rs | 4 +++- ws-server/src/server.rs | 6 +++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/http-server/src/server.rs b/http-server/src/server.rs index c700069f7c..ae383308f8 100644 --- a/http-server/src/server.rs +++ b/http-server/src/server.rs @@ -163,17 +163,17 @@ impl Server { if let Some(method) = methods.get(&*req.method) { let params = RpcParams::new(req.params.map(|params| params.get())); // NOTE(niklasad1): connection ID is unused thus hardcoded to `0`. - if let Err(err) = (method)(req.id.to_owned(), params, &tx, 0) { + if let Err(err) = (method)(req.id.clone(), params, &tx, 0) { log::error!( "execution of method call '{}' failed: {:?}, request id={:?}", req.method, err, req.id ); - send_error(req.id.to_owned(), &tx, JsonRpcErrorCode::ServerError(-1).into()); + send_error(req.id.clone(), &tx, JsonRpcErrorCode::ServerError(-1).into()); } } else { - send_error(req.id.to_owned(), &tx, JsonRpcErrorCode::MethodNotFound.into()); + send_error(req.id.clone(), &tx, JsonRpcErrorCode::MethodNotFound.into()); } }; diff --git a/types/src/v2/params.rs b/types/src/v2/params.rs index ac4c5de08a..e7e6ab97c5 100644 --- a/types/src/v2/params.rs +++ b/types/src/v2/params.rs @@ -1,10 +1,11 @@ use crate::error::InvalidParams; use alloc::collections::BTreeMap; +use std::borrow::Cow; 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::borrow::Cow; + use std::fmt; /// JSON-RPC parameter values for subscriptions. @@ -164,6 +165,7 @@ pub enum Id<'a> { /// Numeric id Number(u64), /// String id + #[serde(borrow)] Str(Cow<'a, str>), } diff --git a/ws-server/src/server.rs b/ws-server/src/server.rs index 03a79c7d62..ec7427e353 100644 --- a/ws-server/src/server.rs +++ b/ws-server/src/server.rs @@ -141,12 +141,12 @@ async fn background_task( let execute = move |tx: &MethodSink, req: JsonRpcRequest| { if let Some(method) = methods.get(&*req.method) { let params = RpcParams::new(req.params.map(|params| params.get())); - if let Err(err) = (method)(req.id.to_owned(), params, &tx, conn_id) { + if let Err(err) = (method)(req.id.clone(), params, &tx, conn_id) { log::error!("execution of method call '{}' failed: {:?}, request id={:?}", req.method, err, req.id); - send_error(req.id.to_owned(), &tx, JsonRpcErrorCode::ServerError(-1).into()); + send_error(req.id.clone(), &tx, JsonRpcErrorCode::ServerError(-1).into()); } } else { - send_error(req.id.to_owned(), &tx, JsonRpcErrorCode::MethodNotFound.into()); + send_error(req.id.clone(), &tx, JsonRpcErrorCode::MethodNotFound.into()); } }; From 3affd4115e4a554fb1f8289c377b6c7f7b905039 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 18 May 2021 19:10:09 +0200 Subject: [PATCH 04/17] cargo fmt --- types/src/v2/params.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/src/v2/params.rs b/types/src/v2/params.rs index e7e6ab97c5..65ba26f020 100644 --- a/types/src/v2/params.rs +++ b/types/src/v2/params.rs @@ -1,10 +1,10 @@ use crate::error::InvalidParams; use alloc::collections::BTreeMap; -use std::borrow::Cow; 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::borrow::Cow; use std::fmt; From eb07a015851f79bde0bf2f2a9631231d309daf92 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 18 May 2021 21:03:42 +0200 Subject: [PATCH 05/17] [types]: write some tests. --- types/src/v2/params.rs | 33 +++++++++++++++++-------- types/src/v2/request.rs | 53 ++++++++++++++++++++++++++++++++++------ types/src/v2/response.rs | 22 ++++++++++++++--- 3 files changed, 87 insertions(+), 21 deletions(-) diff --git a/types/src/v2/params.rs b/types/src/v2/params.rs index 65ba26f020..d0dd03d5c3 100644 --- a/types/src/v2/params.rs +++ b/types/src/v2/params.rs @@ -194,20 +194,11 @@ impl<'a> Id<'a> { } } - /// Creates owned data from borrowed data, allocates only for Strings. - pub fn to_owned(&self) -> Id<'static> { - match self { - Id::Null => Id::Null, - Id::Number(n) => Id::Number(*n), - Id::Str(Cow::Borrowed(s)) => Id::Str(Cow::Owned(s.to_string())), - Id::Str(Cow::Owned(s)) => Id::Str(Cow::Owned(s.clone())), - } - } } #[cfg(test)] mod test { - use super::{Cow, Id}; + use super::{Cow, Id, RpcParams, JsonValue}; #[test] fn id_deserialization() { @@ -238,4 +229,26 @@ mod test { let serialized = serde_json::to_string(&d).unwrap(); assert_eq!(serialized, r#"[null,0,2,3,"3","test"]"#); } + + + #[test] + fn params_parse() { + let none = RpcParams::new(None); + assert!(none.one::().is_err()); + + let array_params = RpcParams::new(Some("[1, 2, 3]")); + let arr: Result<[u64; 3], _> = array_params.parse(); + assert!(arr.is_ok()); + + let arr: Result<(u64, u64, u64), _> = array_params.parse(); + assert!(arr.is_ok()); + + let array_one = RpcParams::new(Some("[1]")); + let one: Result = array_one.one(); + assert!(one.is_ok()); + + let object_params = RpcParams::new(Some(r#"{"beef":99,"dinner":0}"#)); + let obj: Result = object_params.parse(); + assert!(obj.is_ok()); + } } diff --git a/types/src/v2/request.rs b/types/src/v2/request.rs index bbdc734c45..2f4429693f 100644 --- a/types/src/v2/request.rs +++ b/types/src/v2/request.rs @@ -1,4 +1,4 @@ -use crate::v2::params::{Id, JsonRpcNotificationParams, JsonRpcParams, TwoPointZero}; +use crate::v2::params::{Id, JsonRpcParams, TwoPointZero}; use beef::Cow; use serde::{Deserialize, Serialize}; use serde_json::value::RawValue; @@ -21,7 +21,7 @@ pub struct JsonRpcRequest<'a> { } /// Invalid request with known request ID. -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, PartialEq)] pub struct JsonRpcInvalidRequest<'a> { /// Request ID #[serde(borrow)] @@ -30,13 +30,15 @@ pub struct JsonRpcInvalidRequest<'a> { /// JSON-RPC notification (a request object without a request ID). #[derive(Serialize, Deserialize, Debug)] +#[serde(deny_unknown_fields)] 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>, + #[serde(borrow)] + pub params: Option<&'a RawValue>, } /// Serializable [JSON-RPC object](https://www.jsonrpc.org/specification#request-object) @@ -79,10 +81,12 @@ impl<'a> JsonRpcNotificationSer<'a> { #[cfg(test)] mod test { - use super::{JsonRpcRequest, TwoPointZero}; + use super::{JsonRpcRequest, JsonRpcNotification, JsonRpcInvalidRequest, TwoPointZero, Id, + JsonRpcCallSer, JsonRpcNotificationSer + }; #[test] - fn deserialize_valid_request_works() { + fn deserialize_valid_call_works() { let ser = r#"{"jsonrpc":"2.0","method":"say_hello","params":[1,"bar"],"id":1}"#; let dsr: JsonRpcRequest = serde_json::from_str(ser).unwrap(); assert_eq!(dsr.method, "say_hello"); @@ -90,22 +94,55 @@ mod test { } #[test] - fn deserialize_valid_request_without_params_works() { + fn deserialize_valid_notif_works() { + let ser = r#"{"jsonrpc":"2.0","method":"say_hello","params":[]}"#; + let dsr: JsonRpcNotification = serde_json::from_str(ser).unwrap(); + assert_eq!(dsr.method, "say_hello"); + assert_eq!(dsr.jsonrpc, TwoPointZero); + } + + #[test] + fn deserialize_valid_call_without_params_works() { let ser = r#"{"jsonrpc":"2.0","method":"say_hello", "id":1}"#; let dsr: JsonRpcRequest = serde_json::from_str(ser).unwrap(); assert_eq!(dsr.method, "say_hello"); assert_eq!(dsr.jsonrpc, TwoPointZero); } + // TODO(niklasad1): merge the types `JsonRpcParams` and `RpcParams` and remove `RawValue`. #[test] - fn deserialize_request_bad_params_should_fail() { + #[ignore] + fn deserialize_call_bad_params_should_fail() { let ser = r#"{"jsonrpc":"2.0","method":"say_hello","params":"lol","id":1}"#; assert!(serde_json::from_str::(ser).is_err()); } #[test] - fn deserialize_request_bad_id_should_fail() { + fn deserialize_call_bad_id_should_fail() { let ser = r#"{"jsonrpc":"2.0","method":"say_hello","params":[],"id":{}}"#; assert!(serde_json::from_str::(ser).is_err()); } + + #[test] + fn deserialize_invalid_request() { + let s = r#"{"id":120,"method":"my_method","params":["foo", "bar"],"extra_field":[]}"#; + let deserialized: JsonRpcInvalidRequest = serde_json::from_str(s).unwrap(); + assert_eq!(deserialized, JsonRpcInvalidRequest { id: Id::Number(120) }); + } + + #[test] + fn serialize_call() { + let exp = r#"{"jsonrpc":"2.0","method":"say_hello","id":"bar","params":[]}"#; + let req = JsonRpcCallSer::new(Id::Str("bar".into()), "say_hello", vec![].into()); + let ser = serde_json::to_string(&req).unwrap(); + assert_eq!(exp, ser); + } + + #[test] + fn serialize_notif() { + let exp = r#"{"jsonrpc":"2.0","method":"say_hello","params":["hello"]}"#; + let req = JsonRpcNotificationSer::new("say_hello", vec!["hello".into()].into()); + let ser = serde_json::to_string(&req).unwrap(); + assert_eq!(exp, ser); + } } diff --git a/types/src/v2/response.rs b/types/src/v2/response.rs index cd7a3b308f..bd3f7fceba 100644 --- a/types/src/v2/response.rs +++ b/types/src/v2/response.rs @@ -1,8 +1,9 @@ -use crate::v2::params::{Id, JsonRpcNotificationParamsAlloc, TwoPointZero}; +use crate::v2::params::{Id, JsonRpcNotificationParamsAlloc, JsonRpcNotificationParams, TwoPointZero}; use serde::{Deserialize, Serialize}; /// JSON-RPC successful response object. #[derive(Serialize, Deserialize, Debug)] +#[serde(deny_unknown_fields)] pub struct JsonRpcResponse<'a, T> { /// JSON-RPC version. pub jsonrpc: TwoPointZero, @@ -13,17 +14,32 @@ pub struct JsonRpcResponse<'a, T> { pub id: Id<'a>, } +/// JSON-RPC subscription response. +#[derive(Serialize)] +pub struct JsonRpcSubscriptionResponse<'a> { + /// JSON-RPC version. + pub jsonrpc: TwoPointZero, + /// Method + pub method: &'a str, + /// Params. + pub params: JsonRpcNotificationParams<'a>, +} + /// JSON-RPC subscription response. #[derive(Deserialize, Debug)] -pub struct JsonRpcSubscriptionResponse { +#[serde(deny_unknown_fields)] +pub struct JsonRpcSubscriptionResponseAlloc<'a, T> { /// JSON-RPC version. pub jsonrpc: TwoPointZero, + /// Method + pub method: &'a str, /// Params. pub params: JsonRpcNotificationParamsAlloc, } /// JSON-RPC notification response. -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Serialize, Debug)] +#[serde(deny_unknown_fields)] pub struct JsonRpcNotifResponse<'a, T> { /// JSON-RPC version. pub jsonrpc: TwoPointZero, From e5e931273a2fe91bd6ad511b02817036025023c1 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 18 May 2021 21:04:19 +0200 Subject: [PATCH 06/17] [http server]: send empty response on notifs --- http-server/src/server.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/http-server/src/server.rs b/http-server/src/server.rs index ae383308f8..fb83a8f591 100644 --- a/http-server/src/server.rs +++ b/http-server/src/server.rs @@ -36,8 +36,8 @@ use hyper::{ use jsonrpsee_types::error::{CallError, Error, GenericTransportError}; use jsonrpsee_types::v2::error::JsonRpcErrorCode; use jsonrpsee_types::v2::params::{Id, RpcParams}; -use jsonrpsee_types::v2::request::{JsonRpcInvalidRequest, JsonRpcRequest}; -use jsonrpsee_utils::hyper_helpers::read_response_to_body; +use jsonrpsee_types::v2::request::{JsonRpcInvalidRequest, JsonRpcRequest, JsonRpcNotification}; +use jsonrpsee_utils::{hyper_helpers::read_response_to_body, server::helpers::send_response}; use jsonrpsee_utils::server::helpers::{collect_batch_response, send_error}; use jsonrpsee_utils::server::rpc_module::{MethodSink, RpcModule}; @@ -212,6 +212,8 @@ impl Server { // Our [issue](https://github.com/paritytech/jsonrpsee/issues/296). if let Ok(req) = serde_json::from_slice::(&body) { execute(&tx, req); + } else if let Ok(_req) = serde_json::from_slice::(&body) { + return Ok::<_, HyperError>(response::ok_response("".into())); } else if let Ok(batch) = serde_json::from_slice::>(&body) { if !batch.is_empty() { single = false; @@ -221,6 +223,8 @@ impl Server { } else { send_error(Id::Null, &tx, JsonRpcErrorCode::InvalidRequest.into()); } + } else if let Ok(_batch) = serde_json::from_slice::>(&body) { + return Ok::<_, HyperError>(response::ok_response("".into())); } else { log::error!( "[service_fn], Cannot parse request body={:?}", From 3c7d20f6261300de966df96499f2fb21e1ba6ba2 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 18 May 2021 21:05:31 +0200 Subject: [PATCH 07/17] [http server]: fix tests --- http-server/src/tests.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/http-server/src/tests.rs b/http-server/src/tests.rs index f111c98fce..624b8f51e7 100644 --- a/http-server/src/tests.rs +++ b/http-server/src/tests.rs @@ -169,14 +169,11 @@ async fn batched_notifications() { let addr = server().await; let uri = to_http_uri(addr); - let req = r#"[ - {"jsonrpc": "2.0", "method": "notif", "params": [1,2,4]}, - {"jsonrpc": "2.0", "method": "notif", "params": [7]} - ]"#; + let req = r#"[{"jsonrpc": "2.0", "method": "notif", "params": [1,2,4]},{"jsonrpc": "2.0", "method": "notif", "params": [7]}]"#; let response = http_request(req.into(), uri).await.unwrap(); assert_eq!(response.status, StatusCode::OK); - // Note: this is *not* according to spec. Response should be the empty string, `""`. - assert_eq!(response.body, r#"[{"jsonrpc":"2.0","result":"","id":null},{"jsonrpc":"2.0","result":"","id":null}]"#); + // Note: on HTTP we ack the notification with an empty response. + assert_eq!(response.body, ""); } #[tokio::test] @@ -248,3 +245,14 @@ async fn invalid_request_object() { assert_eq!(response.status, StatusCode::OK); assert_eq!(response.body, invalid_request(Id::Num(1))); } + +#[tokio::test] +async fn notif_works() { + let addr = server().await; + let uri = to_http_uri(addr); + + let req = r#"{"jsonrpc":"2.0","method":"bar"}"#; + let response = http_request(req.into(), uri).await.unwrap(); + assert_eq!(response.status, StatusCode::OK); + assert_eq!(response.body, ""); +} From ace5cb2d4c2686efd4335046abf64a582607df14 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 18 May 2021 21:06:01 +0200 Subject: [PATCH 08/17] [rpc module]: send subscription response --- utils/src/server/rpc_module.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/src/server/rpc_module.rs b/utils/src/server/rpc_module.rs index 7672a157ac..59c105ade3 100644 --- a/utils/src/server/rpc_module.rs +++ b/utils/src/server/rpc_module.rs @@ -4,7 +4,7 @@ use jsonrpsee_types::error::{CallError, Error}; use jsonrpsee_types::traits::RpcMethod; use jsonrpsee_types::v2::error::{JsonRpcErrorCode, JsonRpcErrorObject, CALL_EXECUTION_FAILED_CODE}; use jsonrpsee_types::v2::params::{Id, JsonRpcNotificationParams, RpcParams, TwoPointZero}; -use jsonrpsee_types::v2::request::JsonRpcNotification; +use jsonrpsee_types::v2::response::JsonRpcSubscriptionResponse; use parking_lot::Mutex; use rustc_hash::FxHashMap; @@ -236,7 +236,7 @@ impl SubscriptionSink { let mut subs = self.subscribers.lock(); for ((conn_id, sub_id), sender) in subs.iter() { - let msg = serde_json::to_string(&JsonRpcNotification { + let msg = serde_json::to_string(&JsonRpcSubscriptionResponse { jsonrpc: TwoPointZero, method: self.method, params: JsonRpcNotificationParams { subscription: *sub_id, result: &*result }, From 9eddc0cae1a70ea428609059284f0ec8e6f8b064 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 18 May 2021 21:12:03 +0200 Subject: [PATCH 09/17] Update types/src/v2/error.rs --- types/src/v2/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/src/v2/error.rs b/types/src/v2/error.rs index 18f4446c82..991a191287 100644 --- a/types/src/v2/error.rs +++ b/types/src/v2/error.rs @@ -24,7 +24,7 @@ impl<'a> fmt::Display for JsonRpcError<'a> { } } -/// JSON-RPC error object with no extra allocations. +/// JSON-RPC error object. #[derive(Debug, Deserialize, Serialize, Clone)] #[serde(deny_unknown_fields)] pub struct JsonRpcErrorObject<'a> { From 78fec4fd5ea5bf7f2e2ea4691adbfc6f29ffceef Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 18 May 2021 21:16:24 +0200 Subject: [PATCH 10/17] fix nits --- http-server/src/server.rs | 2 +- ws-client/src/client.rs | 4 ++-- ws-client/src/helpers.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/http-server/src/server.rs b/http-server/src/server.rs index fb83a8f591..d1e37eae52 100644 --- a/http-server/src/server.rs +++ b/http-server/src/server.rs @@ -37,7 +37,7 @@ use jsonrpsee_types::error::{CallError, Error, GenericTransportError}; use jsonrpsee_types::v2::error::JsonRpcErrorCode; use jsonrpsee_types::v2::params::{Id, RpcParams}; use jsonrpsee_types::v2::request::{JsonRpcInvalidRequest, JsonRpcRequest, JsonRpcNotification}; -use jsonrpsee_utils::{hyper_helpers::read_response_to_body, server::helpers::send_response}; +use jsonrpsee_utils::hyper_helpers::read_response_to_body; use jsonrpsee_utils::server::helpers::{collect_batch_response, send_error}; use jsonrpsee_utils::server::rpc_module::{MethodSink, RpcModule}; diff --git a/ws-client/src/client.rs b/ws-client/src/client.rs index 561a95ed27..6e5efe74b2 100644 --- a/ws-client/src/client.rs +++ b/ws-client/src/client.rs @@ -29,7 +29,7 @@ use crate::transport::{parse_url, Receiver as WsReceiver, Sender as WsSender, Ws use crate::v2::error::JsonRpcError; use crate::v2::params::{Id, JsonRpcParams}; use crate::v2::request::{JsonRpcCallSer, JsonRpcNotificationSer}; -use crate::v2::response::{JsonRpcNotifResponse, JsonRpcResponse, JsonRpcSubscriptionResponse}; +use crate::v2::response::{JsonRpcNotifResponse, JsonRpcResponse, JsonRpcSubscriptionResponseAlloc}; use crate::TEN_MB_SIZE_BYTES; use crate::{ helpers::{ @@ -631,7 +631,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 3c38983839..c88e4e75d9 100644 --- a/ws-client/src/helpers.rs +++ b/ws-client/src/helpers.rs @@ -3,7 +3,7 @@ use crate::transport::Sender as WsSender; use futures::channel::mpsc; use jsonrpsee_types::v2::params::{Id, JsonRpcParams, SubscriptionId}; use jsonrpsee_types::v2::request::JsonRpcCallSer; -use jsonrpsee_types::v2::response::{JsonRpcNotifResponse, JsonRpcResponse, JsonRpcSubscriptionResponse}; +use jsonrpsee_types::v2::response::{JsonRpcNotifResponse, JsonRpcResponse, JsonRpcSubscriptionResponseAlloc}; use jsonrpsee_types::{v2::error::JsonRpcError, Error, RequestMessage}; use serde_json::Value as JsonValue; @@ -46,7 +46,7 @@ pub fn process_batch_response(manager: &mut RequestManager, rps: Vec, + notif: JsonRpcSubscriptionResponseAlloc, ) -> Result<(), Option> { let sub_id = notif.params.subscription; let request_id = match manager.get_request_id_by_subscription_id(&sub_id) { From 659ce7d35c66e03cf3986b07307bae1ffedacaed Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 18 May 2021 21:17:32 +0200 Subject: [PATCH 11/17] cargo fmt --- http-server/src/server.rs | 2 +- types/src/v2/params.rs | 4 +--- types/src/v2/request.rs | 5 +++-- types/src/v2/response.rs | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/http-server/src/server.rs b/http-server/src/server.rs index d1e37eae52..38ae6cd90e 100644 --- a/http-server/src/server.rs +++ b/http-server/src/server.rs @@ -36,7 +36,7 @@ use hyper::{ use jsonrpsee_types::error::{CallError, Error, GenericTransportError}; use jsonrpsee_types::v2::error::JsonRpcErrorCode; use jsonrpsee_types::v2::params::{Id, RpcParams}; -use jsonrpsee_types::v2::request::{JsonRpcInvalidRequest, JsonRpcRequest, JsonRpcNotification}; +use jsonrpsee_types::v2::request::{JsonRpcInvalidRequest, JsonRpcNotification, JsonRpcRequest}; use jsonrpsee_utils::hyper_helpers::read_response_to_body; use jsonrpsee_utils::server::helpers::{collect_batch_response, send_error}; use jsonrpsee_utils::server::rpc_module::{MethodSink, RpcModule}; diff --git a/types/src/v2/params.rs b/types/src/v2/params.rs index d0dd03d5c3..41ea11bdc0 100644 --- a/types/src/v2/params.rs +++ b/types/src/v2/params.rs @@ -193,12 +193,11 @@ impl<'a> Id<'a> { _ => None, } } - } #[cfg(test)] mod test { - use super::{Cow, Id, RpcParams, JsonValue}; + use super::{Cow, Id, JsonValue, RpcParams}; #[test] fn id_deserialization() { @@ -230,7 +229,6 @@ mod test { assert_eq!(serialized, r#"[null,0,2,3,"3","test"]"#); } - #[test] fn params_parse() { let none = RpcParams::new(None); diff --git a/types/src/v2/request.rs b/types/src/v2/request.rs index 2f4429693f..889c3045e9 100644 --- a/types/src/v2/request.rs +++ b/types/src/v2/request.rs @@ -81,8 +81,9 @@ impl<'a> JsonRpcNotificationSer<'a> { #[cfg(test)] mod test { - use super::{JsonRpcRequest, JsonRpcNotification, JsonRpcInvalidRequest, TwoPointZero, Id, - JsonRpcCallSer, JsonRpcNotificationSer + use super::{ + Id, JsonRpcCallSer, JsonRpcInvalidRequest, JsonRpcNotification, JsonRpcNotificationSer, JsonRpcRequest, + TwoPointZero, }; #[test] diff --git a/types/src/v2/response.rs b/types/src/v2/response.rs index bd3f7fceba..f14f19025b 100644 --- a/types/src/v2/response.rs +++ b/types/src/v2/response.rs @@ -1,4 +1,4 @@ -use crate::v2::params::{Id, JsonRpcNotificationParamsAlloc, JsonRpcNotificationParams, TwoPointZero}; +use crate::v2::params::{Id, JsonRpcNotificationParams, JsonRpcNotificationParamsAlloc, TwoPointZero}; use serde::{Deserialize, Serialize}; /// JSON-RPC successful response object. From d5c9da92e544de236226efb9a3f353fd911206eb Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 18 May 2021 21:18:14 +0200 Subject: [PATCH 12/17] Update types/src/v2/params.rs --- types/src/v2/params.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/types/src/v2/params.rs b/types/src/v2/params.rs index 41ea11bdc0..61ddec3a46 100644 --- a/types/src/v2/params.rs +++ b/types/src/v2/params.rs @@ -5,7 +5,6 @@ use serde::ser::Serializer; use serde::{Deserialize, Serialize}; use serde_json::{value::RawValue, Value as JsonValue}; use std::borrow::Cow; - use std::fmt; /// JSON-RPC parameter values for subscriptions. From f81eb77945cb952e9605e8ed6ee0aaf7b7ab65db Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 19 May 2021 10:22:47 +0200 Subject: [PATCH 13/17] remove needless clone --- http-server/src/server.rs | 4 ++-- ws-server/src/server.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/http-server/src/server.rs b/http-server/src/server.rs index 38ae6cd90e..7ef3926ca4 100644 --- a/http-server/src/server.rs +++ b/http-server/src/server.rs @@ -170,10 +170,10 @@ impl Server { err, req.id ); - send_error(req.id.clone(), &tx, JsonRpcErrorCode::ServerError(-1).into()); + send_error(req.id, &tx, JsonRpcErrorCode::ServerError(-1).into()); } } else { - send_error(req.id.clone(), &tx, JsonRpcErrorCode::MethodNotFound.into()); + send_error(req.id, &tx, JsonRpcErrorCode::MethodNotFound.into()); } }; diff --git a/ws-server/src/server.rs b/ws-server/src/server.rs index ec7427e353..e0623a1822 100644 --- a/ws-server/src/server.rs +++ b/ws-server/src/server.rs @@ -143,10 +143,10 @@ async fn background_task( let params = RpcParams::new(req.params.map(|params| params.get())); if let Err(err) = (method)(req.id.clone(), params, &tx, conn_id) { log::error!("execution of method call '{}' failed: {:?}, request id={:?}", req.method, err, req.id); - send_error(req.id.clone(), &tx, JsonRpcErrorCode::ServerError(-1).into()); + send_error(req.id, &tx, JsonRpcErrorCode::ServerError(-1).into()); } } else { - send_error(req.id.clone(), &tx, JsonRpcErrorCode::MethodNotFound.into()); + send_error(req.id, &tx, JsonRpcErrorCode::MethodNotFound.into()); } }; From 26d3031be9ee8f660c8a21d1efdece75d1d63490 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 19 May 2021 10:28:38 +0200 Subject: [PATCH 14/17] remove dead code --- types/src/error.rs | 10 ---------- types/src/v2/mod.rs | 15 --------------- types/src/v2/params.rs | 10 +++++----- 3 files changed, 5 insertions(+), 30 deletions(-) diff --git a/types/src/error.rs b/types/src/error.rs index 2283aaa708..f3fe97d0d4 100644 --- a/types/src/error.rs +++ b/types/src/error.rs @@ -14,10 +14,6 @@ impl fmt::Display for Mismatch { } } -/// Invalid params. -#[derive(Debug)] -pub struct InvalidParams; - /// Error that occurs when a call failed. #[derive(Debug, thiserror::Error)] pub enum CallError { @@ -29,12 +25,6 @@ pub enum CallError { Failed(#[source] Box), } -impl From for CallError { - fn from(_params: InvalidParams) -> Self { - Self::InvalidParams - } -} - /// Error type. #[derive(Debug, thiserror::Error)] pub enum Error { diff --git a/types/src/v2/mod.rs b/types/src/v2/mod.rs index aba31248db..b801a9a2fd 100644 --- a/types/src/v2/mod.rs +++ b/types/src/v2/mod.rs @@ -1,7 +1,3 @@ -use crate::error::Error; -use serde::de::DeserializeOwned; -use serde_json::value::RawValue; - /// JSON-RPC error related types. pub mod error; /// JSON_RPC params related types. @@ -10,14 +6,3 @@ pub mod params; 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 { - match raw { - None => Err(Error::InvalidRequestId), - Some(v) => { - let val = serde_json::from_str(v.get()).map_err(|_| Error::InvalidRequestId)?; - Ok(val) - } - } -} diff --git a/types/src/v2/params.rs b/types/src/v2/params.rs index 61ddec3a46..42f6b51547 100644 --- a/types/src/v2/params.rs +++ b/types/src/v2/params.rs @@ -1,4 +1,4 @@ -use crate::error::InvalidParams; +use crate::error::CallError; use alloc::collections::BTreeMap; use serde::de::{self, Deserializer, Unexpected, Visitor}; use serde::ser::Serializer; @@ -79,18 +79,18 @@ impl<'a> RpcParams<'a> { } /// Attempt to parse all parameters as array or map into type T - pub fn parse(self) -> Result + pub fn parse(self) -> Result where T: Deserialize<'a>, { match self.0 { - None => Err(InvalidParams), - Some(params) => serde_json::from_str(params).map_err(|_| InvalidParams), + None => Err(CallError::InvalidParams), + Some(params) => serde_json::from_str(params).map_err(|_| CallError::InvalidParams), } } /// Attempt to parse only the first parameter from an array into type T - pub fn one(self) -> Result + pub fn one(self) -> Result where T: Deserialize<'a>, { From a2264bb1c5bbcbbb696f17aa31311b21289e7ae8 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 19 May 2021 11:03:13 +0200 Subject: [PATCH 15/17] [types]: impl PartialEq for JsonErrorObject + test --- http-client/src/tests.rs | 4 +--- types/src/v2/error.rs | 39 ++++++++++++++++++++++++++++++++++++++- ws-client/src/tests.rs | 4 +--- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/http-client/src/tests.rs b/http-client/src/tests.rs index 03da46f90b..09111f012a 100644 --- a/http-client/src/tests.rs +++ b/http-client/src/tests.rs @@ -111,9 +111,7 @@ fn assert_jsonrpc_error_response(err: Error, exp: JsonRpcErrorObject) { match &err { Error::Request(e) => { let this: JsonRpcError = serde_json::from_str(&e).unwrap(); - // NOTE: `RawValue` doesn't implement PartialEq. - assert_eq!(this.error.code, exp.code); - assert_eq!(this.error.message, exp.message); + assert_eq!(this.error, exp); } e => panic!("Expected error: \"{}\", got: {:?}", err, e), }; diff --git a/types/src/v2/error.rs b/types/src/v2/error.rs index 991a191287..eac99b1025 100644 --- a/types/src/v2/error.rs +++ b/types/src/v2/error.rs @@ -7,7 +7,7 @@ use std::fmt; use thiserror::Error; /// [Failed JSON-RPC response object](https://www.jsonrpc.org/specification#response_object). -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct JsonRpcError<'a> { /// JSON-RPC version. pub jsonrpc: TwoPointZero, @@ -44,6 +44,14 @@ impl<'a> From for JsonRpcErrorObject<'a> { } } +impl<'a> PartialEq for JsonRpcErrorObject<'a> { + fn eq(&self, other: &Self) -> bool { + let this_raw = self.data.map(|r| r.get()); + let other_raw = self.data.map(|r| r.get()); + self.code == other.code && self.message == other.message && this_raw == other_raw + } +} + /// Parse error code. pub const PARSE_ERROR_CODE: i32 = -32700; /// Internal error code. @@ -156,6 +164,35 @@ impl serde::Serialize for JsonRpcErrorCode { mod tests { use super::{Id, JsonRpcError, JsonRpcErrorCode, JsonRpcErrorObject, TwoPointZero}; + #[test] + fn deserialize_works() { + let ser = r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":null}"#; + let exp = JsonRpcError { + jsonrpc: TwoPointZero, + error: JsonRpcErrorObject { code: JsonRpcErrorCode::ParseError, message: "Parse error".into(), data: None }, + id: Id::Null, + }; + let err: JsonRpcError = serde_json::from_str(ser).unwrap(); + assert_eq!(exp, err); + } + + #[test] + fn deserialize_with_optional_data() { + let ser = r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error", "data":"vegan"},"id":null}"#; + let data = serde_json::value::to_raw_value(&"vegan").unwrap(); + let exp = JsonRpcError { + jsonrpc: TwoPointZero, + error: JsonRpcErrorObject { + code: JsonRpcErrorCode::ParseError, + message: "Parse error".into(), + data: Some(&*data), + }, + id: Id::Null, + }; + let err: JsonRpcError = serde_json::from_str(ser).unwrap(); + assert_eq!(exp, err); + } + #[test] fn serialize_works() { let exp = r#"{"jsonrpc":"2.0","error":{"code":-32603,"message":"Internal error"},"id":1337}"#; diff --git a/ws-client/src/tests.rs b/ws-client/src/tests.rs index 3c1f1f0d5a..9b0474a951 100644 --- a/ws-client/src/tests.rs +++ b/ws-client/src/tests.rs @@ -190,9 +190,7 @@ fn assert_error_response(err: Error, exp: JsonRpcErrorObject) { match &err { Error::Request(e) => { let this: JsonRpcError = serde_json::from_str(&e).unwrap(); - // NOTE: `RawValue` doesn't implement PartialEq. - assert_eq!(this.error.code, exp.code); - assert_eq!(this.error.message, exp.message); + assert_eq!(this.error, exp); } e => panic!("Expected error: \"{}\", got: {:?}", err, e), }; From 84c00cde200eae809b38ff0265020ddc6491203c Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 19 May 2021 16:15:43 +0200 Subject: [PATCH 16/17] use beef::Cow --- types/Cargo.toml | 2 +- types/src/v2/params.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/types/Cargo.toml b/types/Cargo.toml index 360c40598a..6416dc0357 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -11,7 +11,7 @@ documentation = "https://docs.rs/jsonrpsee-types" [dependencies] async-trait = "0.1" -beef = "0.5" +beef = { version = "0.5", features = ["impl_serde"] } futures-channel = { version = "0.3", features = ["sink"] } futures-util = { version = "0.3", default-features = false, features = ["std", "sink", "channel"] } log = { version = "0.4", default-features = false } diff --git a/types/src/v2/params.rs b/types/src/v2/params.rs index 42f6b51547..aa719bb6ff 100644 --- a/types/src/v2/params.rs +++ b/types/src/v2/params.rs @@ -1,10 +1,10 @@ use crate::error::CallError; use alloc::collections::BTreeMap; +use beef::Cow; 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::borrow::Cow; use std::fmt; /// JSON-RPC parameter values for subscriptions. @@ -210,7 +210,7 @@ mod test { let s = r#""2x""#; let deserialized: Id = serde_json::from_str(s).unwrap(); - assert_eq!(deserialized, Id::Str(Cow::Borrowed("2x"))); + assert_eq!(deserialized, Id::Str(Cow::const_str("2x"))); let s = r#"[1337]"#; assert!(serde_json::from_str::(s).is_err()); From 7f901b4bada08ebb99ec911498fc4a62c8d93291 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 19 May 2021 16:31:50 +0200 Subject: [PATCH 17/17] Update http-server/src/tests.rs --- http-server/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http-server/src/tests.rs b/http-server/src/tests.rs index 624b8f51e7..a69288c984 100644 --- a/http-server/src/tests.rs +++ b/http-server/src/tests.rs @@ -172,7 +172,7 @@ async fn batched_notifications() { let req = r#"[{"jsonrpc": "2.0", "method": "notif", "params": [1,2,4]},{"jsonrpc": "2.0", "method": "notif", "params": [7]}]"#; let response = http_request(req.into(), uri).await.unwrap(); assert_eq!(response.status, StatusCode::OK); - // Note: on HTTP we ack the notification with an empty response. + // Note: on HTTP acknowledge the notification with an empty response. assert_eq!(response.body, ""); }