// Copyright 2019 Parity Technologies (UK) Ltd. // // Permission is hereby granted, free of charge, to any // person obtaining a copy of this software and associated // documentation files (the "Software"), to deal in the // Software without restriction, including without // limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software // is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice // shall be included in all copies or substantial portions // of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. use super::{Error, Id, JsonValue, Version}; use alloc::{ fmt, string::{String, ToString as _}, vec, vec::Vec, }; use core::convert::TryFrom; use serde::{Deserialize, Serialize}; /// JSONRPC response. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] #[serde(deny_unknown_fields)] #[serde(untagged)] pub enum Response { /// Single response Single(Output), /// Response to batch request (batch of responses) Batch(Vec), /// Notification to an active subscription. Notif(SubscriptionNotif), } impl fmt::Display for Response { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", serde_json::to_string(self).expect("Response valid JSON; qed")) } } /// Successful response #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Success { /// Protocol version pub jsonrpc: Version, /// Result pub result: JsonValue, /// Correlation id pub id: Id, } /// Unsuccessful response #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Failure { /// Protocol version pub jsonrpc: Version, /// Error pub error: Error, /// Correlation id pub id: Id, } /// Represents output - failure or success #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] #[serde(deny_unknown_fields)] #[serde(untagged)] pub enum Output { /// Success Success(Success), /// Failure Failure(Failure), } /// Server notification about something the client is subscribed to. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct SubscriptionNotif { /// Protocol version pub jsonrpc: Version, /// A String containing the name of the method that was used for the subscription. pub method: String, /// Parameters of the notification. pub params: SubscriptionNotifParams, } /// Field of a [`SubscriptionNotif`]. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct SubscriptionNotifParams { /// Subscription id, as communicated during the subscription. pub subscription: SubscriptionId, /// Actual data that the server wants to communicate to us. pub result: JsonValue, } /// Id of a subscription, communicated by the server. #[derive(Debug, PartialEq, Clone, Hash, Eq, Deserialize, Serialize)] #[serde(deny_unknown_fields)] #[serde(untagged)] pub enum SubscriptionId { /// Numeric id Num(u64), /// String id Str(String), } impl Output { /// Creates new output given `Result`, `Id` and `Version`. pub fn from(result: Result, id: Id, jsonrpc: Version) -> Self { match result { Ok(result) => Output::Success(Success { jsonrpc, result, id }), Err(error) => Output::Failure(Failure { jsonrpc, error, id }), } } /// Get the jsonrpc protocol version. pub fn version(&self) -> Version { match *self { Output::Success(ref s) => s.jsonrpc, Output::Failure(ref f) => f.jsonrpc, } } /// Get the correlation id. pub fn id(&self) -> &Id { match *self { Output::Success(ref s) => &s.id, Output::Failure(ref f) => &f.id, } } } impl TryFrom for JsonValue { type Error = Error; fn try_from(output: Output) -> Result { match output { Output::Success(s) => Ok(s.result), Output::Failure(f) => Err(f.error), } } } impl Response { /// Creates new `Response` with given error and `Version` pub fn from(error: impl Into, jsonrpc: Version) -> Self { Failure { id: Id::Null, jsonrpc, error: error.into() }.into() } /// Deserialize `Response` from given JSON string. /// /// This method will handle an empty string as empty batch response. pub fn from_json(s: &str) -> Result { if s.is_empty() { Ok(Response::Batch(vec![])) } else { serde_json::from_str(s) } } } impl From for Response { fn from(failure: Failure) -> Self { Response::Single(Output::Failure(failure)) } } impl From for Response { fn from(success: Success) -> Self { Response::Single(Output::Success(success)) } } impl SubscriptionId { /// Turns the subscription ID into a string. pub fn into_string(self) -> String { match self { SubscriptionId::Num(n) => n.to_string(), SubscriptionId::Str(s) => s, } } } #[cfg(test)] mod tests { use super::{Error, Failure, Id, Output, Response, Success, Version}; use serde_json::Value; #[test] fn success_output_serialize() { let so = Output::Success(Success { jsonrpc: Version::V2, result: Value::from(1), id: Id::Num(1) }); let serialized = serde_json::to_string(&so).unwrap(); assert_eq!(serialized, r#"{"jsonrpc":"2.0","result":1,"id":1}"#); } #[test] fn success_output_deserialize() { let dso = r#"{"jsonrpc":"2.0","result":1,"id":1}"#; let deserialized: Output = serde_json::from_str(dso).unwrap(); assert_eq!( deserialized, Output::Success(Success { jsonrpc: Version::V2, result: Value::from(1), id: Id::Num(1) }) ); } #[test] fn failure_output_serialize() { let fo = Output::Failure(Failure { jsonrpc: Version::V2, error: Error::parse_error(), id: Id::Num(1) }); let serialized = serde_json::to_string(&fo).unwrap(); assert_eq!(serialized, r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":1}"#); } #[test] fn failure_output_deserialize() { let dfo = r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":1}"#; let deserialized: Output = serde_json::from_str(dfo).unwrap(); assert_eq!( deserialized, Output::Failure(Failure { jsonrpc: Version::V2, error: Error::parse_error(), id: Id::Num(1) }) ); } #[test] fn single_response_deserialize() { let dsr = r#"{"jsonrpc":"2.0","result":1,"id":1}"#; let deserialized: Response = serde_json::from_str(dsr).unwrap(); assert_eq!( deserialized, Response::Single(Output::Success(Success { jsonrpc: Version::V2, result: Value::from(1), id: Id::Num(1) })) ); } #[test] fn batch_response_deserialize() { let dbr = r#"[{"jsonrpc":"2.0","result":1,"id":1},{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":1}]"#; let deserialized: Response = serde_json::from_str(dbr).unwrap(); assert_eq!( deserialized, Response::Batch(vec![ Output::Success(Success { jsonrpc: Version::V2, result: Value::from(1), id: Id::Num(1) }), Output::Failure(Failure { jsonrpc: Version::V2, error: Error::parse_error(), id: Id::Num(1) }) ]) ); } #[test] fn handle_incorrect_responses() { let dsr = r#" { "id": 2, "jsonrpc": "2.0", "result": "0x62d3776be72cc7fa62cad6fe8ed873d9bc7ca2ee576e400d987419a3f21079d5", "error": { "message": "VM Exception while processing transaction: revert", "code": -32000, "data": {} } }"#; let deserialized: Result = serde_json::from_str(dsr); assert!(deserialized.is_err(), "Expected error when deserializing invalid payload."); } #[test] fn should_parse_empty_response_as_batch() { let dsr = r#""#; let deserialized1: Result = serde_json::from_str(dsr); let deserialized2: Result = Response::from_json(dsr); assert!(deserialized1.is_err(), "Empty string is not valid JSON, so we should get an error."); assert_eq!(deserialized2.unwrap(), Response::Batch(vec![])); } }