From 62d233b10ec2577655adbdcd60e3ea288a2fad2d Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 25 Jun 2021 21:42:23 +0200 Subject: [PATCH 1/5] feat: customizable error via RpcError trait This commit introduces a new trait for defining user customizable error codes and messages --- http-server/src/tests.rs | 35 ++++++++++++++++++++++++++++++---- test-utils/src/helpers.rs | 9 +++++++++ types/src/error.rs | 19 ++++++++++++++---- types/src/traits.rs | 17 +++++++++++++++++ utils/src/server/rpc_module.rs | 19 ++++++++---------- ws-server/src/tests.rs | 33 ++++++++++++++++++++++++-------- 6 files changed, 105 insertions(+), 27 deletions(-) diff --git a/http-server/src/tests.rs b/http-server/src/tests.rs index 14532e04b1..c97c7b09ff 100644 --- a/http-server/src/tests.rs +++ b/http-server/src/tests.rs @@ -8,9 +8,36 @@ use jsonrpsee_test_utils::helpers::*; use jsonrpsee_test_utils::types::{Id, StatusCode, TestContext}; use jsonrpsee_test_utils::TimeoutFutureExt; use jsonrpsee_types::error::{CallError, Error}; +use jsonrpsee_types::traits::JsonRpcErrorT; use serde_json::Value as JsonValue; use tokio::task::JoinHandle; +#[derive(Debug, thiserror::Error)] +enum TestError { + #[error("One")] + One, + #[error("Two")] + Two, +} + +impl JsonRpcErrorT for TestError { + fn code(&self) -> i32 { + match self { + Self::One => 1, + Self::Two => 2, + } + } + fn message(&self) -> String { + match self { + Self::One => "error one".to_string(), + Self::Two => "error two".to_string(), + } + } + fn data(&self) -> Option<&serde_json::value::RawValue> { + None + } +} + async fn server() -> SocketAddr { server_with_handles().await.0 } @@ -39,21 +66,21 @@ async fn server_with_handles() -> (SocketAddr, JoinHandle>, St module.register_method("notif", |_, _| Ok("")).unwrap(); module .register_method("should_err", |_, ctx| { - let _ = ctx.err().map_err(|e| CallError::Failed(e.into()))?; + let _ = ctx.err().map_err(|_| CallError::Failed(Box::new(TestError::One)))?; Ok("err") }) .unwrap(); module .register_method("should_ok", |_, ctx| { - let _ = ctx.ok().map_err(|e| CallError::Failed(e.into()))?; + let _ = ctx.ok().map_err(|_| CallError::Failed(Box::new(TestError::One)))?; Ok("ok") }) .unwrap(); module .register_async_method("should_ok_async", |_p, ctx| { async move { - let _ = ctx.ok().map_err(|e| CallError::Failed(e.into()))?; + let _ = ctx.ok().map_err(|_| CallError::Failed(Box::new(TestError::Two)))?; Ok("ok") } .boxed() @@ -147,7 +174,7 @@ async fn single_method_call_with_faulty_context() { let req = r#"{"jsonrpc":"2.0","method":"should_err", "params":[],"id":1}"#; let response = http_request(req.into(), uri).with_default_timeout().await.unwrap().unwrap(); assert_eq!(response.status, StatusCode::OK); - assert_eq!(response.body, call_execution_failed("RPC context failed", Id::Num(1))); + assert_eq!(response.body, user_defined_error("error one", Id::Num(1), 1)); } #[tokio::test] diff --git a/test-utils/src/helpers.rs b/test-utils/src/helpers.rs index e4340b6c3c..992f907b8e 100644 --- a/test-utils/src/helpers.rs +++ b/test-utils/src/helpers.rs @@ -83,6 +83,15 @@ pub fn server_error(id: Id) -> String { ) } +pub fn user_defined_error(msg: &str, id: Id, error_code: i32) -> String { + format!( + r#"{{"jsonrpc":"2.0","error":{{"code":{},"message":"{}"}},"id":{}}}"#, + error_code, + msg, + serde_json::to_string(&id).unwrap() + ) +} + /// Hardcoded server response when a client initiates a new subscription. /// /// NOTE: works only for one subscription because the subscription ID is hardcoded. diff --git a/types/src/error.rs b/types/src/error.rs index 5fe57efadf..3cdadc1572 100644 --- a/types/src/error.rs +++ b/types/src/error.rs @@ -1,3 +1,4 @@ +use crate::traits::JsonRpcErrorMarker; use serde::{Deserialize, Serialize}; use std::fmt; @@ -17,14 +18,24 @@ impl fmt::Display for Mismatch { } /// Error that occurs when a call failed. -#[derive(Debug, thiserror::Error)] +#[derive(Debug)] pub enum CallError { - #[error("Invalid params in the RPC call")] /// Invalid params in the call. InvalidParams, - #[error("RPC Call failed: {0}")] /// The call failed. - Failed(#[source] Box), + Failed(Box), +} + +impl std::error::Error for CallError {} + +impl fmt::Display for CallError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = match self { + Self::InvalidParams => "Invalid params in the call".to_string(), + Self::Failed(err) => format!("RPC Call failed: {}", err), + }; + f.write_str(&s) + } } /// Error type. diff --git a/types/src/traits.rs b/types/src/traits.rs index a831d74d53..adffdd0bb5 100644 --- a/types/src/traits.rs +++ b/types/src/traits.rs @@ -55,3 +55,20 @@ pub trait SubscriptionClient: Client { where Notif: DeserializeOwned; } + +/// Marker trait for an type that implements [`JsonRpcErrorT`] and [`std::error::Error`]. +pub trait JsonRpcErrorMarker: Send + Sync + std::error::Error + JsonRpcErrorT {} + +impl JsonRpcErrorMarker for T {} + +/// Trait for customizable [JSON-RPC error object](https://www.jsonrpc.org/specification#error-object) +pub trait JsonRpcErrorT { + /// Error code (-32768 to -32000 are reserved and should not be used) + fn code(&self) -> i32; + /// Short description of the error. + // NOTE(niklasad1): this is a String because the underlying error may not be an constant + // and need to be included in the error. + fn message(&self) -> String; + /// A Primitive or Structured value that contains additional information about the error. + fn data(&self) -> Option<&serde_json::value::RawValue>; +} diff --git a/utils/src/server/rpc_module.rs b/utils/src/server/rpc_module.rs index f001cf29c1..19cd666ad2 100644 --- a/utils/src/server/rpc_module.rs +++ b/utils/src/server/rpc_module.rs @@ -2,7 +2,7 @@ use crate::server::helpers::{send_error, send_response}; use futures_channel::{mpsc, oneshot}; use futures_util::{future::BoxFuture, FutureExt}; use jsonrpsee_types::error::{CallError, Error, SubscriptionClosedError}; -use jsonrpsee_types::v2::error::{JsonRpcErrorCode, JsonRpcErrorObject, CALL_EXECUTION_FAILED_CODE}; +use jsonrpsee_types::v2::error::{JsonRpcErrorCode, JsonRpcErrorObject}; use jsonrpsee_types::v2::params::{ Id, JsonRpcSubscriptionParams, OwnedId, OwnedRpcParams, RpcParams, SubscriptionId as JsonRpcSubscriptionId, TwoPointZero, @@ -69,7 +69,7 @@ impl MethodCallback { if let Err(err) = result { log::error!("execution of method call '{}' failed: {:?}, request id={:?}", req.method, err, id); - send_error(id, &tx, JsonRpcErrorCode::ServerError(-1).into()); + send_error(id, tx, JsonRpcErrorCode::ServerError(-1).into()); } } } @@ -191,11 +191,8 @@ impl RpcModule { Ok(res) => send_response(id, tx, res), Err(CallError::InvalidParams) => send_error(id, tx, JsonRpcErrorCode::InvalidParams.into()), Err(CallError::Failed(err)) => { - let err = JsonRpcErrorObject { - code: JsonRpcErrorCode::ServerError(CALL_EXECUTION_FAILED_CODE), - message: &err.to_string(), - data: None, - }; + let err = + JsonRpcErrorObject { code: err.code().into(), message: &err.message(), data: err.data() }; send_error(id, tx, err) } }; @@ -230,9 +227,9 @@ impl RpcModule { Err(CallError::Failed(err)) => { log::error!("Call failed with: {}", err); let err = JsonRpcErrorObject { - code: JsonRpcErrorCode::ServerError(CALL_EXECUTION_FAILED_CODE), - message: &err.to_string(), - data: None, + code: err.code().into(), + message: &err.message(), + data: err.data(), }; send_error(id, &tx, err) } @@ -323,7 +320,7 @@ impl RpcModule { MethodCallback::Sync(Arc::new(move |id, params, tx, conn_id| { let sub_id = params.one()?; subscribers.lock().remove(&SubscriptionKey { conn_id, sub_id }); - send_response(id, &tx, "Unsubscribed"); + send_response(id, tx, "Unsubscribed"); Ok(()) })), diff --git a/ws-server/src/tests.rs b/ws-server/src/tests.rs index fdf138502b..46b85a9e30 100644 --- a/ws-server/src/tests.rs +++ b/ws-server/src/tests.rs @@ -5,6 +5,7 @@ use futures_util::FutureExt; use jsonrpsee_test_utils::helpers::*; use jsonrpsee_test_utils::types::{Id, TestContext, WebSocketTestClient}; use jsonrpsee_test_utils::TimeoutFutureExt; +use jsonrpsee_types::traits::JsonRpcErrorT; use jsonrpsee_types::{ error::{CallError, Error}, v2::params::RpcParams, @@ -17,13 +18,29 @@ use tokio::task::JoinHandle; /// Applications can/should provide their own error. #[derive(Debug)] struct MyAppError; + impl fmt::Display for MyAppError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "MyAppError") + write!(f, "this is needed for error impl") } } + impl std::error::Error for MyAppError {} +impl JsonRpcErrorT for MyAppError { + fn code(&self) -> i32 { + -32000 + } + + fn message(&self) -> String { + "MyAppError".to_string() + } + + fn data(&self) -> Option<&serde_json::value::RawValue> { + None + } +} + /// Spawns a dummy `JSONRPC v2 WebSocket` /// It has two hardcoded methods: "say_hello" and "add" async fn server() -> SocketAddr { @@ -87,14 +104,14 @@ async fn server_with_context() -> SocketAddr { rpc_module .register_method("should_err", |_p, ctx| { - let _ = ctx.err().map_err(|e| CallError::Failed(e.into()))?; + let _ = ctx.err().map_err(|_e| CallError::Failed(Box::new(MyAppError)))?; Ok("err") }) .unwrap(); rpc_module .register_method("should_ok", |_p, ctx| { - let _ = ctx.ok().map_err(|e| CallError::Failed(e.into()))?; + let _ = ctx.ok().map_err(|_e| CallError::Failed(Box::new(MyAppError)))?; Ok("ok") }) .unwrap(); @@ -102,7 +119,7 @@ async fn server_with_context() -> SocketAddr { rpc_module .register_async_method("should_ok_async", |_p, ctx| { async move { - let _ = ctx.ok().map_err(|e| CallError::Failed(e.into()))?; + let _ = ctx.ok().map_err(|_| CallError::Failed(Box::new(MyAppError)))?; // Call some async function inside. Ok(futures_util::future::ready("ok!").await) } @@ -113,9 +130,9 @@ async fn server_with_context() -> SocketAddr { rpc_module .register_async_method("err_async", |_p, ctx| { async move { - let _ = ctx.ok().map_err(|e| CallError::Failed(e.into()))?; + let _ = ctx.ok().map_err(|_| CallError::Failed(Box::new(MyAppError)))?; // Async work that returns an error - futures_util::future::err::<(), CallError>(CallError::Failed(String::from("nah").into())).await + futures_util::future::err::<(), CallError>(CallError::Failed(Box::new(MyAppError))).await } .boxed() }) @@ -285,7 +302,7 @@ async fn single_method_call_with_faulty_context() { let req = r#"{"jsonrpc":"2.0","method":"should_err", "params":[],"id":1}"#; let response = client.send_request_text(req).with_default_timeout().await.unwrap().unwrap(); - assert_eq!(response, call_execution_failed("RPC context failed", Id::Num(1))); + assert_eq!(response, call_execution_failed("MyAppError", Id::Num(1))); } #[tokio::test] @@ -315,7 +332,7 @@ async fn async_method_call_that_fails() { let req = r#"{"jsonrpc":"2.0","method":"err_async", "params":[],"id":1}"#; let response = client.send_request_text(req).await.unwrap(); - assert_eq!(response, call_execution_failed("nah", Id::Num(1))); + assert_eq!(response, call_execution_failed("MyAppError", Id::Num(1))); } #[tokio::test] From a6308621b5ac768fd95178621a72563c25aeb3c0 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 28 Jun 2021 22:46:34 +0200 Subject: [PATCH 2/5] revert trait stuff --- http-server/src/tests.rs | 35 ++++----------------------- types/src/error.rs | 24 +++++++------------ types/src/traits.rs | 17 -------------- utils/src/server/rpc_module.rs | 32 ++++++++++++++++++------- ws-server/src/tests.rs | 43 +++++++++++----------------------- 5 files changed, 49 insertions(+), 102 deletions(-) diff --git a/http-server/src/tests.rs b/http-server/src/tests.rs index c97c7b09ff..14532e04b1 100644 --- a/http-server/src/tests.rs +++ b/http-server/src/tests.rs @@ -8,36 +8,9 @@ use jsonrpsee_test_utils::helpers::*; use jsonrpsee_test_utils::types::{Id, StatusCode, TestContext}; use jsonrpsee_test_utils::TimeoutFutureExt; use jsonrpsee_types::error::{CallError, Error}; -use jsonrpsee_types::traits::JsonRpcErrorT; use serde_json::Value as JsonValue; use tokio::task::JoinHandle; -#[derive(Debug, thiserror::Error)] -enum TestError { - #[error("One")] - One, - #[error("Two")] - Two, -} - -impl JsonRpcErrorT for TestError { - fn code(&self) -> i32 { - match self { - Self::One => 1, - Self::Two => 2, - } - } - fn message(&self) -> String { - match self { - Self::One => "error one".to_string(), - Self::Two => "error two".to_string(), - } - } - fn data(&self) -> Option<&serde_json::value::RawValue> { - None - } -} - async fn server() -> SocketAddr { server_with_handles().await.0 } @@ -66,21 +39,21 @@ async fn server_with_handles() -> (SocketAddr, JoinHandle>, St module.register_method("notif", |_, _| Ok("")).unwrap(); module .register_method("should_err", |_, ctx| { - let _ = ctx.err().map_err(|_| CallError::Failed(Box::new(TestError::One)))?; + let _ = ctx.err().map_err(|e| CallError::Failed(e.into()))?; Ok("err") }) .unwrap(); module .register_method("should_ok", |_, ctx| { - let _ = ctx.ok().map_err(|_| CallError::Failed(Box::new(TestError::One)))?; + let _ = ctx.ok().map_err(|e| CallError::Failed(e.into()))?; Ok("ok") }) .unwrap(); module .register_async_method("should_ok_async", |_p, ctx| { async move { - let _ = ctx.ok().map_err(|_| CallError::Failed(Box::new(TestError::Two)))?; + let _ = ctx.ok().map_err(|e| CallError::Failed(e.into()))?; Ok("ok") } .boxed() @@ -174,7 +147,7 @@ async fn single_method_call_with_faulty_context() { let req = r#"{"jsonrpc":"2.0","method":"should_err", "params":[],"id":1}"#; let response = http_request(req.into(), uri).with_default_timeout().await.unwrap().unwrap(); assert_eq!(response.status, StatusCode::OK); - assert_eq!(response.body, user_defined_error("error one", Id::Num(1), 1)); + assert_eq!(response.body, call_execution_failed("RPC context failed", Id::Num(1))); } #[tokio::test] diff --git a/types/src/error.rs b/types/src/error.rs index 3cdadc1572..ff4b18f2d4 100644 --- a/types/src/error.rs +++ b/types/src/error.rs @@ -1,4 +1,3 @@ -use crate::traits::JsonRpcErrorMarker; use serde::{Deserialize, Serialize}; use std::fmt; @@ -18,24 +17,17 @@ impl fmt::Display for Mismatch { } /// Error that occurs when a call failed. -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum CallError { /// Invalid params in the call. + #[error("Invalid params in the call")] InvalidParams, - /// The call failed. - Failed(Box), -} - -impl std::error::Error for CallError {} - -impl fmt::Display for CallError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = match self { - Self::InvalidParams => "Invalid params in the call".to_string(), - Self::Failed(err) => format!("RPC Call failed: {}", err), - }; - f.write_str(&s) - } + /// The call failed (let jsonrpsee assign default error code and error message). + #[error("RPC Call failed: {0}")] + Failed(Box), + /// Custom error with specific JSON-RPC error code, message and data. + #[error("RPC Call failed")] + Custom { code: i32, message: String, data: Option }, } /// Error type. diff --git a/types/src/traits.rs b/types/src/traits.rs index adffdd0bb5..a831d74d53 100644 --- a/types/src/traits.rs +++ b/types/src/traits.rs @@ -55,20 +55,3 @@ pub trait SubscriptionClient: Client { where Notif: DeserializeOwned; } - -/// Marker trait for an type that implements [`JsonRpcErrorT`] and [`std::error::Error`]. -pub trait JsonRpcErrorMarker: Send + Sync + std::error::Error + JsonRpcErrorT {} - -impl JsonRpcErrorMarker for T {} - -/// Trait for customizable [JSON-RPC error object](https://www.jsonrpc.org/specification#error-object) -pub trait JsonRpcErrorT { - /// Error code (-32768 to -32000 are reserved and should not be used) - fn code(&self) -> i32; - /// Short description of the error. - // NOTE(niklasad1): this is a String because the underlying error may not be an constant - // and need to be included in the error. - fn message(&self) -> String; - /// A Primitive or Structured value that contains additional information about the error. - fn data(&self) -> Option<&serde_json::value::RawValue>; -} diff --git a/utils/src/server/rpc_module.rs b/utils/src/server/rpc_module.rs index 19cd666ad2..79d5e6ff29 100644 --- a/utils/src/server/rpc_module.rs +++ b/utils/src/server/rpc_module.rs @@ -2,7 +2,7 @@ use crate::server::helpers::{send_error, send_response}; use futures_channel::{mpsc, oneshot}; use futures_util::{future::BoxFuture, FutureExt}; use jsonrpsee_types::error::{CallError, Error, SubscriptionClosedError}; -use jsonrpsee_types::v2::error::{JsonRpcErrorCode, JsonRpcErrorObject}; +use jsonrpsee_types::v2::error::{JsonRpcErrorCode, JsonRpcErrorObject, CALL_EXECUTION_FAILED_CODE}; use jsonrpsee_types::v2::params::{ Id, JsonRpcSubscriptionParams, OwnedId, OwnedRpcParams, RpcParams, SubscriptionId as JsonRpcSubscriptionId, TwoPointZero, @@ -12,6 +12,7 @@ use jsonrpsee_types::v2::request::{JsonRpcNotification, JsonRpcRequest}; use parking_lot::Mutex; use rustc_hash::FxHashMap; use serde::Serialize; +use serde_json::value::to_raw_value; use std::fmt::Debug; use std::sync::Arc; @@ -190,9 +191,17 @@ impl RpcModule { match callback(params, &*ctx) { Ok(res) => send_response(id, tx, res), Err(CallError::InvalidParams) => send_error(id, tx, JsonRpcErrorCode::InvalidParams.into()), - Err(CallError::Failed(err)) => { - let err = - JsonRpcErrorObject { code: err.code().into(), message: &err.message(), data: err.data() }; + Err(CallError::Failed(e)) => { + let err = JsonRpcErrorObject { + code: JsonRpcErrorCode::ServerError(CALL_EXECUTION_FAILED_CODE), + message: &e.to_string(), + data: None, + }; + send_error(id, tx, err) + } + Err(CallError::Custom { code, message, data }) => { + let data = data.and_then(|v| to_raw_value(&v).ok()); + let err = JsonRpcErrorObject { code: code.into(), message: &message, data: data.as_deref() }; send_error(id, tx, err) } }; @@ -224,15 +233,20 @@ impl RpcModule { match callback(params, ctx).await { Ok(res) => send_response(id, &tx, res), Err(CallError::InvalidParams) => send_error(id, &tx, JsonRpcErrorCode::InvalidParams.into()), - Err(CallError::Failed(err)) => { - log::error!("Call failed with: {}", err); + Err(CallError::Failed(e)) => { let err = JsonRpcErrorObject { - code: err.code().into(), - message: &err.message(), - data: err.data(), + code: JsonRpcErrorCode::ServerError(CALL_EXECUTION_FAILED_CODE), + message: &e.to_string(), + data: None, }; send_error(id, &tx, err) } + Err(CallError::Custom { code, message, data }) => { + let data = data.and_then(|v| to_raw_value(&v).ok()); + let err = + JsonRpcErrorObject { code: code.into(), message: &message, data: data.as_deref() }; + send_error(id, &tx, err) + } }; Ok(()) }; diff --git a/ws-server/src/tests.rs b/ws-server/src/tests.rs index 46b85a9e30..bf3b286bb4 100644 --- a/ws-server/src/tests.rs +++ b/ws-server/src/tests.rs @@ -5,7 +5,6 @@ use futures_util::FutureExt; use jsonrpsee_test_utils::helpers::*; use jsonrpsee_test_utils::types::{Id, TestContext, WebSocketTestClient}; use jsonrpsee_test_utils::TimeoutFutureExt; -use jsonrpsee_types::traits::JsonRpcErrorT; use jsonrpsee_types::{ error::{CallError, Error}, v2::params::RpcParams, @@ -18,29 +17,13 @@ use tokio::task::JoinHandle; /// Applications can/should provide their own error. #[derive(Debug)] struct MyAppError; - impl fmt::Display for MyAppError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "this is needed for error impl") + write!(f, "MyAppError") } } - impl std::error::Error for MyAppError {} -impl JsonRpcErrorT for MyAppError { - fn code(&self) -> i32 { - -32000 - } - - fn message(&self) -> String { - "MyAppError".to_string() - } - - fn data(&self) -> Option<&serde_json::value::RawValue> { - None - } -} - /// Spawns a dummy `JSONRPC v2 WebSocket` /// It has two hardcoded methods: "say_hello" and "add" async fn server() -> SocketAddr { @@ -104,14 +87,14 @@ async fn server_with_context() -> SocketAddr { rpc_module .register_method("should_err", |_p, ctx| { - let _ = ctx.err().map_err(|_e| CallError::Failed(Box::new(MyAppError)))?; + let _ = ctx.err().map_err(|e| CallError::Failed(e.into()))?; Ok("err") }) .unwrap(); rpc_module .register_method("should_ok", |_p, ctx| { - let _ = ctx.ok().map_err(|_e| CallError::Failed(Box::new(MyAppError)))?; + let _ = ctx.ok().map_err(|e| CallError::Failed(e.into()))?; Ok("ok") }) .unwrap(); @@ -119,7 +102,7 @@ async fn server_with_context() -> SocketAddr { rpc_module .register_async_method("should_ok_async", |_p, ctx| { async move { - let _ = ctx.ok().map_err(|_| CallError::Failed(Box::new(MyAppError)))?; + let _ = ctx.ok().map_err(|e| CallError::Failed(e.into()))?; // Call some async function inside. Ok(futures_util::future::ready("ok!").await) } @@ -130,9 +113,9 @@ async fn server_with_context() -> SocketAddr { rpc_module .register_async_method("err_async", |_p, ctx| { async move { - let _ = ctx.ok().map_err(|_| CallError::Failed(Box::new(MyAppError)))?; + let _ = ctx.ok().map_err(|e| CallError::Failed(e.into()))?; // Async work that returns an error - futures_util::future::err::<(), CallError>(CallError::Failed(Box::new(MyAppError))).await + futures_util::future::err::<(), CallError>(CallError::Failed(String::from("nah").into())).await } .boxed() }) @@ -188,10 +171,12 @@ async fn can_set_max_connections() { assert!(conn2.is_ok()); // Third connection is rejected assert!(conn3.is_err()); - let err = conn3.unwrap_err(); - assert!(err.to_string().contains("WebSocketHandshake failed")); - assert!(err.to_string().contains("Connection reset by peer")); - // Err(Io(Os { code: 54, kind: ConnectionReset, message: \"Connection reset by peer\" }))"); + + let err = match conn3 { + Err(soketto::handshake::Error::Io(err)) => err, + _ => panic!("Invalid error kind; expected std::io::Error"), + }; + assert_eq!(err.kind(), std::io::ErrorKind::ConnectionReset); // Decrement connection count drop(conn2); @@ -302,7 +287,7 @@ async fn single_method_call_with_faulty_context() { let req = r#"{"jsonrpc":"2.0","method":"should_err", "params":[],"id":1}"#; let response = client.send_request_text(req).with_default_timeout().await.unwrap().unwrap(); - assert_eq!(response, call_execution_failed("MyAppError", Id::Num(1))); + assert_eq!(response, call_execution_failed("RPC context failed", Id::Num(1))); } #[tokio::test] @@ -332,7 +317,7 @@ async fn async_method_call_that_fails() { let req = r#"{"jsonrpc":"2.0","method":"err_async", "params":[],"id":1}"#; let response = client.send_request_text(req).await.unwrap(); - assert_eq!(response, call_execution_failed("MyAppError", Id::Num(1))); + assert_eq!(response, call_execution_failed("nah", Id::Num(1))); } #[tokio::test] From 3e63e748b207788d7ce0b1007503a20ded92dcbc Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 28 Jun 2021 23:16:43 +0200 Subject: [PATCH 3/5] use RawValue --- types/src/error.rs | 5 +++-- utils/src/server/rpc_module.rs | 3 --- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/types/src/error.rs b/types/src/error.rs index ff4b18f2d4..326113fc6d 100644 --- a/types/src/error.rs +++ b/types/src/error.rs @@ -1,4 +1,5 @@ use serde::{Deserialize, Serialize}; +use serde_json::value::RawValue; use std::fmt; /// Convenience type for displaying errors. @@ -26,8 +27,8 @@ pub enum CallError { #[error("RPC Call failed: {0}")] Failed(Box), /// Custom error with specific JSON-RPC error code, message and data. - #[error("RPC Call failed")] - Custom { code: i32, message: String, data: Option }, + #[error("RPC Call failed: code: {code}, message: {message}, data: {data:?}")] + Custom { code: i32, message: String, data: Option> }, } /// Error type. diff --git a/utils/src/server/rpc_module.rs b/utils/src/server/rpc_module.rs index 79d5e6ff29..46995fce72 100644 --- a/utils/src/server/rpc_module.rs +++ b/utils/src/server/rpc_module.rs @@ -12,7 +12,6 @@ use jsonrpsee_types::v2::request::{JsonRpcNotification, JsonRpcRequest}; use parking_lot::Mutex; use rustc_hash::FxHashMap; use serde::Serialize; -use serde_json::value::to_raw_value; use std::fmt::Debug; use std::sync::Arc; @@ -200,7 +199,6 @@ impl RpcModule { send_error(id, tx, err) } Err(CallError::Custom { code, message, data }) => { - let data = data.and_then(|v| to_raw_value(&v).ok()); let err = JsonRpcErrorObject { code: code.into(), message: &message, data: data.as_deref() }; send_error(id, tx, err) } @@ -242,7 +240,6 @@ impl RpcModule { send_error(id, &tx, err) } Err(CallError::Custom { code, message, data }) => { - let data = data.and_then(|v| to_raw_value(&v).ok()); let err = JsonRpcErrorObject { code: code.into(), message: &message, data: data.as_deref() }; send_error(id, &tx, err) From 1c8215fda46800bf7d741e32f1e0eb1e78884540 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 28 Jun 2021 23:29:13 +0200 Subject: [PATCH 4/5] fix docs --- test-utils/src/helpers.rs | 9 --------- types/src/error.rs | 9 ++++++++- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/test-utils/src/helpers.rs b/test-utils/src/helpers.rs index 992f907b8e..e4340b6c3c 100644 --- a/test-utils/src/helpers.rs +++ b/test-utils/src/helpers.rs @@ -83,15 +83,6 @@ pub fn server_error(id: Id) -> String { ) } -pub fn user_defined_error(msg: &str, id: Id, error_code: i32) -> String { - format!( - r#"{{"jsonrpc":"2.0","error":{{"code":{},"message":"{}"}},"id":{}}}"#, - error_code, - msg, - serde_json::to_string(&id).unwrap() - ) -} - /// Hardcoded server response when a client initiates a new subscription. /// /// NOTE: works only for one subscription because the subscription ID is hardcoded. diff --git a/types/src/error.rs b/types/src/error.rs index 326113fc6d..7ca23178ad 100644 --- a/types/src/error.rs +++ b/types/src/error.rs @@ -28,7 +28,14 @@ pub enum CallError { Failed(Box), /// Custom error with specific JSON-RPC error code, message and data. #[error("RPC Call failed: code: {code}, message: {message}, data: {data:?}")] - Custom { code: i32, message: String, data: Option> }, + Custom { + /// JSON-RPC error code + code: i32, + /// Short description of the error. + message: String, + /// A primitive or structured value that contains additional information about the error. + data: Option>, + }, } /// Error type. From b57ff80a589a808525171f1536ba8e469316247b Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 29 Jun 2021 10:17:24 +0200 Subject: [PATCH 5/5] rexport to_json_raw_value --- types/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/types/src/lib.rs b/types/src/lib.rs index 9d14f62b92..0009aee4b6 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -24,4 +24,7 @@ pub use beef::Cow; pub use client::*; pub use error::Error; pub use serde::{de::DeserializeOwned, Serialize}; -pub use serde_json::{to_value as to_json_value, value::RawValue as JsonRawValue, Value as JsonValue}; +pub use serde_json::{ + to_value as to_json_value, value::to_raw_value as to_json_raw_value, value::RawValue as JsonRawValue, + Value as JsonValue, +};