From 09fe5096d1d2b471570dff21f47b33cd13df17c9 Mon Sep 17 00:00:00 2001 From: Paolo Montesel Date: Thu, 25 Apr 2024 18:24:20 +0900 Subject: [PATCH 1/3] types: add hashmap for non-standard response fields closes #1349 --- types/src/response.rs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/types/src/response.rs b/types/src/response.rs index bf60e911b9..819efc5f18 100644 --- a/types/src/response.rs +++ b/types/src/response.rs @@ -27,6 +27,7 @@ //! Types pertaining to JSON-RPC responses. use std::borrow::Cow as StdCow; +use std::collections::HashMap; use std::fmt; use std::marker::PhantomData; @@ -45,17 +46,19 @@ pub struct Response<'a, T: Clone> { pub payload: ResponsePayload<'a, T>, /// Request ID pub id: Id<'a>, + /// Non-standard fields + pub other: HashMap, } impl<'a, T: Clone> Response<'a, T> { /// Create a new [`Response`]. pub fn new(payload: ResponsePayload<'a, T>, id: Id<'a>) -> Response<'a, T> { - Response { jsonrpc: Some(TwoPointZero), payload, id } + Response { jsonrpc: Some(TwoPointZero), payload, id, other: HashMap::new() } } /// Create an owned [`Response`]. pub fn into_owned(self) -> Response<'static, T> { - Response { jsonrpc: self.jsonrpc, payload: self.payload.into_owned(), id: self.id.into_owned() } + Response { jsonrpc: self.jsonrpc, payload: self.payload.into_owned(), id: self.id.into_owned(), other: HashMap::new() } } } @@ -195,7 +198,7 @@ where Result, Error, Id, - Ignore, + Other(String), } impl<'de> Deserialize<'de> for Field { @@ -221,7 +224,7 @@ where "result" => Ok(Field::Result), "error" => Ok(Field::Error), "id" => Ok(Field::Id), - _ => Ok(Field::Ignore), + _ => Ok(Field::Other(value.to_owned())), } } } @@ -255,6 +258,7 @@ where let mut result = None; let mut error = None; let mut id = None; + let mut other: HashMap = HashMap::new(); while let Some(key) = map.next_key()? { match key { Field::Result => { @@ -281,8 +285,10 @@ where } jsonrpc = Some(map.next_value()?); } - Field::Ignore => { - let _ = map.next_value::()?; + Field::Other(key) => { + if other.insert(key.to_owned(), map.next_value()?).is_some() { + return Err(serde::de::Error::custom(format!("duplicated field \"{key}\""))); + } } } } @@ -294,13 +300,13 @@ where return Err(serde::de::Error::duplicate_field("result and error are mutually exclusive")) } (Some(jsonrpc), Some(result), None) => { - Response { jsonrpc, payload: ResponsePayload::Success(result), id } + Response { jsonrpc, payload: ResponsePayload::Success(result), id, other } } - (Some(jsonrpc), None, Some(err)) => Response { jsonrpc, payload: ResponsePayload::Error(err), id }, + (Some(jsonrpc), None, Some(err)) => Response { jsonrpc, payload: ResponsePayload::Error(err), id, other }, (None, Some(result), _) => { - Response { jsonrpc: None, payload: ResponsePayload::Success(result), id } + Response { jsonrpc: None, payload: ResponsePayload::Success(result), id, other } } - (None, _, Some(err)) => Response { jsonrpc: None, payload: ResponsePayload::Error(err), id }, + (None, _, Some(err)) => Response { jsonrpc: None, payload: ResponsePayload::Error(err), id, other }, (_, None, None) => return Err(serde::de::Error::missing_field("result/error")), }; From 9f54ce1e0999116d4b6d49d84155cb524290fd25 Mon Sep 17 00:00:00 2001 From: Paolo Montesel Date: Thu, 25 Apr 2024 18:25:24 +0900 Subject: [PATCH 2/3] types: update tests --- types/src/response.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/types/src/response.rs b/types/src/response.rs index 819efc5f18..2d4c7135e5 100644 --- a/types/src/response.rs +++ b/types/src/response.rs @@ -345,7 +345,9 @@ where #[cfg(test)] mod tests { - use super::{Id, Response, TwoPointZero}; + use std::collections::HashMap; + +use super::{Id, Response, TwoPointZero}; use crate::{response::ResponsePayload, ErrorObjectOwned}; #[test] @@ -354,6 +356,7 @@ mod tests { jsonrpc: Some(TwoPointZero), payload: ResponsePayload::success("ok"), id: Id::Number(1), + other: HashMap::new(), }) .unwrap(); let exp = r#"{"jsonrpc":"2.0","result":"ok","id":1}"#; @@ -366,6 +369,7 @@ mod tests { jsonrpc: Some(TwoPointZero), payload: ResponsePayload::<()>::error(ErrorObjectOwned::owned(1, "lo", None::<()>)), id: Id::Number(1), + other: HashMap::new(), }) .unwrap(); let exp = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"lo"},"id":1}"#; @@ -378,6 +382,7 @@ mod tests { jsonrpc: None, payload: ResponsePayload::success("ok"), id: Id::Number(1), + other: HashMap::new(), }) .unwrap(); let exp = r#"{"result":"ok","id":1}"#; @@ -387,7 +392,7 @@ mod tests { #[test] fn deserialize_success_call() { let exp = - Response { jsonrpc: Some(TwoPointZero), payload: ResponsePayload::success(99_u64), id: Id::Number(11) }; + Response { jsonrpc: Some(TwoPointZero), payload: ResponsePayload::success(99_u64), id: Id::Number(11), other: HashMap::new() }; let dsr: Response = serde_json::from_str(r#"{"jsonrpc":"2.0", "result":99, "id":11}"#).unwrap(); assert_eq!(dsr.jsonrpc, exp.jsonrpc); assert_eq!(dsr.payload, exp.payload); @@ -400,6 +405,7 @@ mod tests { jsonrpc: Some(TwoPointZero), payload: ResponsePayload::error(ErrorObjectOwned::owned(1, "lo", None::<()>)), id: Id::Number(11), + other: HashMap::new(), }; let dsr: Response<()> = serde_json::from_str(r#"{"jsonrpc":"2.0","error":{"code":1,"message":"lo"},"id":11}"#).unwrap(); @@ -410,7 +416,7 @@ mod tests { #[test] fn deserialize_call_missing_version_field() { - let exp = Response { jsonrpc: None, payload: ResponsePayload::success(99_u64), id: Id::Number(11) }; + let exp = Response { jsonrpc: None, payload: ResponsePayload::success(99_u64), id: Id::Number(11), other: HashMap::new() }; let dsr: Response = serde_json::from_str(r#"{"jsonrpc":null, "result":99, "id":11}"#).unwrap(); assert_eq!(dsr.jsonrpc, exp.jsonrpc); assert_eq!(dsr.payload, exp.payload); @@ -418,12 +424,15 @@ mod tests { } #[test] - fn deserialize_with_unknown_field() { - let exp = Response { jsonrpc: None, payload: ResponsePayload::success(99_u64), id: Id::Number(11) }; + fn deserialize_with_other_field() { + let exp = Response { jsonrpc: None, payload: ResponsePayload::success(99_u64), id: Id::Number(11), other: HashMap::new() }; let dsr: Response = serde_json::from_str(r#"{"jsonrpc":null, "result":99, "id":11, "unknown":11}"#).unwrap(); assert_eq!(dsr.jsonrpc, exp.jsonrpc); assert_eq!(dsr.payload, exp.payload); assert_eq!(dsr.id, exp.id); + let (k, v) = dsr.other.iter().next().unwrap(); + assert_eq!(k, "unknown"); + assert_eq!(v.get(), "11"); } } From 3c8cc379b8efad08c4a68f5130a64cf46bbd01a7 Mon Sep 17 00:00:00 2001 From: Paolo Montesel Date: Thu, 25 Apr 2024 18:25:44 +0900 Subject: [PATCH 3/3] types: cargo fmt --- types/src/response.rs | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/types/src/response.rs b/types/src/response.rs index 2d4c7135e5..9a277035d7 100644 --- a/types/src/response.rs +++ b/types/src/response.rs @@ -58,7 +58,12 @@ impl<'a, T: Clone> Response<'a, T> { /// Create an owned [`Response`]. pub fn into_owned(self) -> Response<'static, T> { - Response { jsonrpc: self.jsonrpc, payload: self.payload.into_owned(), id: self.id.into_owned(), other: HashMap::new() } + Response { + jsonrpc: self.jsonrpc, + payload: self.payload.into_owned(), + id: self.id.into_owned(), + other: HashMap::new(), + } } } @@ -302,7 +307,9 @@ where (Some(jsonrpc), Some(result), None) => { Response { jsonrpc, payload: ResponsePayload::Success(result), id, other } } - (Some(jsonrpc), None, Some(err)) => Response { jsonrpc, payload: ResponsePayload::Error(err), id, other }, + (Some(jsonrpc), None, Some(err)) => { + Response { jsonrpc, payload: ResponsePayload::Error(err), id, other } + } (None, Some(result), _) => { Response { jsonrpc: None, payload: ResponsePayload::Success(result), id, other } } @@ -347,7 +354,7 @@ where mod tests { use std::collections::HashMap; -use super::{Id, Response, TwoPointZero}; + use super::{Id, Response, TwoPointZero}; use crate::{response::ResponsePayload, ErrorObjectOwned}; #[test] @@ -391,8 +398,12 @@ use super::{Id, Response, TwoPointZero}; #[test] fn deserialize_success_call() { - let exp = - Response { jsonrpc: Some(TwoPointZero), payload: ResponsePayload::success(99_u64), id: Id::Number(11), other: HashMap::new() }; + let exp = Response { + jsonrpc: Some(TwoPointZero), + payload: ResponsePayload::success(99_u64), + id: Id::Number(11), + other: HashMap::new(), + }; let dsr: Response = serde_json::from_str(r#"{"jsonrpc":"2.0", "result":99, "id":11}"#).unwrap(); assert_eq!(dsr.jsonrpc, exp.jsonrpc); assert_eq!(dsr.payload, exp.payload); @@ -416,7 +427,12 @@ use super::{Id, Response, TwoPointZero}; #[test] fn deserialize_call_missing_version_field() { - let exp = Response { jsonrpc: None, payload: ResponsePayload::success(99_u64), id: Id::Number(11), other: HashMap::new() }; + let exp = Response { + jsonrpc: None, + payload: ResponsePayload::success(99_u64), + id: Id::Number(11), + other: HashMap::new(), + }; let dsr: Response = serde_json::from_str(r#"{"jsonrpc":null, "result":99, "id":11}"#).unwrap(); assert_eq!(dsr.jsonrpc, exp.jsonrpc); assert_eq!(dsr.payload, exp.payload); @@ -425,7 +441,12 @@ use super::{Id, Response, TwoPointZero}; #[test] fn deserialize_with_other_field() { - let exp = Response { jsonrpc: None, payload: ResponsePayload::success(99_u64), id: Id::Number(11), other: HashMap::new() }; + let exp = Response { + jsonrpc: None, + payload: ResponsePayload::success(99_u64), + id: Id::Number(11), + other: HashMap::new(), + }; let dsr: Response = serde_json::from_str(r#"{"jsonrpc":null, "result":99, "id":11, "unknown":11}"#).unwrap(); assert_eq!(dsr.jsonrpc, exp.jsonrpc);