Skip to content

Commit

Permalink
refactors ipc transport internals (gakonst#1174)
Browse files Browse the repository at this point in the history
* refactors ipc transport internals

ran cargo +nightly fmt

fixes typo

remove some commented out code

* remove one unnecessary return stmt

Co-authored-by: Oliver Giersch <oliver.giersch@b-tu.de>
Co-authored-by: Oliver Giersch <oliver.giersch@gmail.com>
  • Loading branch information
3 people authored Apr 24, 2022
1 parent 77bd9d4 commit a115e95
Show file tree
Hide file tree
Showing 6 changed files with 323 additions and 279 deletions.
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions ethers-providers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ tracing-futures = { version = "0.2.5", default-features = false, features = ["st

bytes = { version = "1.1.0", default-features = false, optional = true }
once_cell = "1.10.0"
hashers = "1.0.1"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
# tokio
Expand Down
155 changes: 83 additions & 72 deletions ethers-providers/src/transports/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,54 +48,21 @@ impl<'a, T> Request<'a, T> {
}
}

/// A JSON-RPC Notifcation
#[allow(unused)]
#[derive(Deserialize, Debug)]
pub struct Notification<'a> {
#[serde(alias = "JSONRPC")]
jsonrpc: &'a str,
method: &'a str,
#[serde(borrow)]
pub params: Subscription<'a>,
/// A JSON-RPC response
#[derive(Debug)]
pub enum Response<'a> {
Success { id: u64, result: &'a RawValue },
Error { id: u64, error: JsonRpcError },
Notification { method: &'a str, params: Params<'a> },
}

#[derive(Deserialize, Debug)]
pub struct Subscription<'a> {
pub struct Params<'a> {
pub subscription: U256,
#[serde(borrow)]
pub result: &'a RawValue,
}

#[derive(Debug)]
pub enum Response<'a> {
Success { id: u64, jsonrpc: &'a str, result: &'a RawValue },
Error { id: u64, jsonrpc: &'a str, error: JsonRpcError },
}

#[allow(unused)]
impl Response<'_> {
pub fn id(&self) -> u64 {
match self {
Self::Success { id, .. } => *id,
Self::Error { id, .. } => *id,
}
}

pub fn as_result(&self) -> Result<&RawValue, &JsonRpcError> {
match self {
Self::Success { result, .. } => Ok(*result),
Self::Error { error, .. } => Err(error),
}
}

pub fn into_result(self) -> Result<Box<RawValue>, JsonRpcError> {
match self {
Self::Success { result, .. } => Ok(result.to_owned()),
Self::Error { error, .. } => Err(error),
}
}
}

// FIXME: ideally, this could be auto-derived as an untagged enum, but due to
// https://github.com/serde-rs/serde/issues/1183 this currently fails
impl<'de: 'a, 'a> Deserialize<'de> for Response<'a> {
Expand All @@ -115,62 +82,96 @@ impl<'de: 'a, 'a> Deserialize<'de> for Response<'a> {
where
A: MapAccess<'de>,
{
let mut jsonrpc = false;

// response & error
let mut id = None;
let mut jsonrpc = None;
// only response
let mut result = None;
// only error
let mut error = None;
// only notification
let mut method = None;
let mut params = None;

while let Some(key) = map.next_key()? {
match key {
"id" => {
let value: u64 = map.next_value()?;
let prev = id.replace(value);
if prev.is_some() {
return Err(de::Error::duplicate_field("id"))
}
}
"jsonrpc" => {
let value: &'de str = map.next_value()?;
if jsonrpc {
return Err(de::Error::duplicate_field("jsonrpc"))
}

let value = map.next_value()?;
if value != "2.0" {
return Err(de::Error::invalid_value(Unexpected::Str(value), &"2.0"))
}

let prev = jsonrpc.replace(value);
if prev.is_some() {
return Err(de::Error::duplicate_field("jsonrpc"))
jsonrpc = true;
}
"id" => {
if id.is_some() {
return Err(de::Error::duplicate_field("id"))
}

let value: u64 = map.next_value()?;
id = Some(value);
}
"result" => {
let value: &RawValue = map.next_value()?;
let prev = result.replace(value);
if prev.is_some() {
if result.is_some() {
return Err(de::Error::duplicate_field("result"))
}

let value: &RawValue = map.next_value()?;
result = Some(value);
}
"error" => {
let value: JsonRpcError = map.next_value()?;
let prev = error.replace(value);
if prev.is_some() {
if error.is_some() {
return Err(de::Error::duplicate_field("error"))
}

let value: JsonRpcError = map.next_value()?;
error = Some(value);
}
"method" => {
if method.is_some() {
return Err(de::Error::duplicate_field("method"))
}

let value: &str = map.next_value()?;
method = Some(value);
}
"params" => {
if params.is_some() {
return Err(de::Error::duplicate_field("params"))
}

let value: Params = map.next_value()?;
params = Some(value);
}
key => {
return Err(de::Error::unknown_field(
key,
&["id", "jsonrpc", "result", "error"],
&["id", "jsonrpc", "result", "error", "params", "method"],
))
}
}
}

let id = id.ok_or_else(|| de::Error::missing_field("id"))?;
let jsonrpc = jsonrpc.ok_or_else(|| de::Error::missing_field("jsonrpc"))?;
// jsonrpc version must be present in all responses
if !jsonrpc {
return Err(de::Error::missing_field("jsonrpc"))
}

match (result, error) {
(Some(result), None) => Ok(Response::Success { id, jsonrpc, result }),
(None, Some(error)) => Ok(Response::Error { id, jsonrpc, error }),
match (id, result, error, method, params) {
(Some(id), Some(result), None, None, None) => {
Ok(Response::Success { id, result })
}
(Some(id), None, Some(error), None, None) => Ok(Response::Error { id, error }),
(None, None, None, Some(method), Some(params)) => {
Ok(Response::Notification { method, params })
}
_ => Err(de::Error::custom(
"response must have either a `result` or `error` field",
"response must be either a success/error or notification object",
)),
}
}
Expand Down Expand Up @@ -223,19 +224,29 @@ mod tests {
let response: Response<'_> =
serde_json::from_str(r#"{"jsonrpc":"2.0","result":19,"id":1}"#).unwrap();

assert_eq!(response.id(), 1);
let result: u64 = serde_json::from_str(response.into_result().unwrap().get()).unwrap();
assert_eq!(result, 19);
match response {
Response::Success { id, result } => {
assert_eq!(id, 1);
let result: u64 = serde_json::from_str(result.get()).unwrap();
assert_eq!(result, 19);
}
_ => panic!("expected `Success` response"),
}

let response: Response<'_> = serde_json::from_str(
r#"{"jsonrpc":"2.0","error":{"code":-32000,"message":"error occurred"},"id":2}"#,
)
.unwrap();

assert_eq!(response.id(), 2);
let err = response.into_result().unwrap_err();
assert_eq!(err.code, -32000);
assert_eq!(err.message, "error occurred");
match response {
Response::Error { id, error } => {
assert_eq!(id, 2);
assert_eq!(error.code, -32000);
assert_eq!(error.message, "error occurred");
assert!(error.data.is_none());
}
_ => panic!("expected `Error` response"),
}
}

#[test]
Expand Down
14 changes: 11 additions & 3 deletions ethers-providers/src/transports/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,20 @@ impl JsonRpcClient for Provider {

let res = self.client.post(self.url.as_ref()).json(&payload).send().await?;
let text = res.text().await?;
let response: Response<'_> = match serde_json::from_str(&text) {
Ok(response) => response,

let raw = match serde_json::from_str(&text) {
Ok(Response::Success { result, .. }) => result.to_owned(),
Ok(Response::Error { error, .. }) => return Err(error.into()),
Ok(_) => {
let err = ClientError::SerdeJson {
err: serde::de::Error::custom("unexpected notification over HTTP transport"),
text,
};
return Err(err)
}
Err(err) => return Err(ClientError::SerdeJson { err, text }),
};

let raw = response.as_result().map_err(Clone::clone)?;
let res = serde_json::from_str(raw.get())
.map_err(|err| ClientError::SerdeJson { err, text: raw.to_string() })?;

Expand Down
Loading

0 comments on commit a115e95

Please sign in to comment.