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..09111f012a 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,12 @@ 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(); + assert_eq!(this.error, exp); + } + e => panic!("Expected error: \"{}\", got: {:?}", err, e), }; } diff --git a/http-server/src/server.rs b/http-server/src/server.rs index 12f3038354..7ef3926ca4 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::request::{JsonRpcInvalidRequest, JsonRpcRequest}; -use jsonrpsee_types::v2::{error::JsonRpcErrorCode, params::RpcParams}; +use jsonrpsee_types::v2::error::JsonRpcErrorCode; +use jsonrpsee_types::v2::params::{Id, RpcParams}; +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}; @@ -162,7 +163,7 @@ 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.clone(), params, &tx, 0) { log::error!( "execution of method call '{}' failed: {:?}, request id={:?}", req.method, @@ -211,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; @@ -218,8 +221,10 @@ impl Server { execute(&tx, req); } } else { - send_error(None, &tx, JsonRpcErrorCode::InvalidRequest.into()); + 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={:?}", @@ -227,7 +232,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..a69288c984 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] @@ -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 acknowledge 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, ""); +} 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/error.rs b/types/src/error.rs index e83feb21ac..f3fe97d0d4 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 { @@ -16,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 { @@ -31,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 { @@ -48,7 +36,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..eac99b1025 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, PartialEq)] 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, + pub id: Id<'a>, } -impl fmt::Display for JsonRpcErrorAlloc { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}: {:?}: {:?}", self.jsonrpc, self.error, self.id) +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. -#[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 } - } -} - -/// 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>, } @@ -70,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. @@ -180,47 +162,44 @@ impl serde::Serialize for JsonRpcErrorCode { #[cfg(test)] mod tests { - use super::{ - Id, JsonRpcError, JsonRpcErrorAlloc, JsonRpcErrorCode, JsonRpcErrorObject, JsonRpcErrorObjectAlloc, - TwoPointZero, - }; + 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 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); + 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 err: JsonRpcErrorAlloc = serde_json::from_str(ser).unwrap(); - assert_eq!(err.jsonrpc, TwoPointZero); - assert_eq!( - err.error, - JsonRpcErrorObjectAlloc { + 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("vegan".into()) - } - ); - assert_eq!(err.id, Id::Null); + 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}"#; - 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/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 9e505662eb..aa719bb6ff 100644 --- a/types/src/v2/params.rs +++ b/types/src/v2/params.rs @@ -1,5 +1,6 @@ -use crate::error::InvalidParams; +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}; @@ -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; @@ -78,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>, { @@ -157,16 +158,17 @@ 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), + #[serde(borrow)] + 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 { @@ -192,6 +194,58 @@ impl Id { } } -/// 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, JsonValue, RpcParams}; + + #[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::const_str("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())]); + } + + #[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"]"#); + } + + #[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 8baa1b392c..889c3045e9 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; @@ -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>, @@ -21,22 +21,24 @@ 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)] - pub id: Option<&'a RawValue>, + pub id: Id<'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) @@ -47,14 +49,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 +78,72 @@ impl<'a> JsonRpcNotificationSer<'a> { Self { jsonrpc: TwoPointZero, method, params } } } + +#[cfg(test)] +mod test { + use super::{ + Id, JsonRpcCallSer, JsonRpcInvalidRequest, JsonRpcNotification, JsonRpcNotificationSer, JsonRpcRequest, + TwoPointZero, + }; + + #[test] + 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"); + assert_eq!(dsr.jsonrpc, TwoPointZero); + } + + #[test] + 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] + #[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_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 44b0582f6d..f14f19025b 100644 --- a/types/src/v2/response.rs +++ b/types/src/v2/response.rs @@ -1,9 +1,9 @@ -use crate::v2::params::{JsonRpcNotificationParamsAlloc, TwoPointZero}; +use crate::v2::params::{Id, JsonRpcNotificationParams, JsonRpcNotificationParamsAlloc, TwoPointZero}; use serde::{Deserialize, Serialize}; -use serde_json::value::RawValue; /// JSON-RPC successful response object. #[derive(Serialize, Deserialize, Debug)] +#[serde(deny_unknown_fields)] pub struct JsonRpcResponse<'a, T> { /// JSON-RPC version. pub jsonrpc: TwoPointZero, @@ -11,20 +11,35 @@ 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. +#[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, 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..59c105ade3 100644 --- a/utils/src/server/rpc_module.rs +++ b/utils/src/server/rpc_module.rs @@ -3,8 +3,8 @@ 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::request::JsonRpcNotification; +use jsonrpsee_types::v2::params::{Id, JsonRpcNotificationParams, RpcParams, TwoPointZero}; +use jsonrpsee_types::v2::response::JsonRpcSubscriptionResponse; use parking_lot::Mutex; use rustc_hash::FxHashMap; @@ -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. @@ -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 }, diff --git a/ws-client/src/client.rs b/ws-client/src/client.rs index 3ee46b08f8..6e5efe74b2 100644 --- a/ws-client/src/client.rs +++ b/ws-client/src/client.rs @@ -26,10 +26,10 @@ 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}; +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; @@ -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..c88e4e75d9 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::response::{JsonRpcNotifResponse, JsonRpcResponse, JsonRpcSubscriptionResponseAlloc}; +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)); } @@ -47,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) { @@ -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..9b0474a951 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,12 @@ 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(); + assert_eq!(this.error, exp); + } + e => panic!("Expected error: \"{}\", got: {:?}", err, e), }; } diff --git a/ws-server/src/server.rs b/ws-server/src/server.rs index 61c0b09048..e0623a1822 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,7 +141,7 @@ 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.clone(), 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()); } @@ -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());