Skip to content

Commit

Permalink
refactor: RpcError and RpcResult and TransportError and TransportResu…
Browse files Browse the repository at this point in the history
…lt (#28)
  • Loading branch information
prestwich authored Nov 17, 2023
1 parent 29761eb commit b530c78
Show file tree
Hide file tree
Showing 20 changed files with 371 additions and 509 deletions.
1 change: 1 addition & 0 deletions crates/json-rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ exclude.workspace = true
alloy-primitives.workspace = true
serde.workspace = true
serde_json = { workspace = true, features = ["raw_value"] }
thiserror.workspace = true
12 changes: 12 additions & 0 deletions crates/json-rpc/src/common.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt::Display;

use serde::{de::Visitor, Deserialize, Serialize};

/// A JSON-RPC 2.0 ID object. This may be a number, a string, or null.
Expand Down Expand Up @@ -31,6 +33,16 @@ pub enum Id {
None,
}

impl Display for Id {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Id::Number(n) => write!(f, "{}", n),
Id::String(s) => write!(f, "{}", s),
Id::None => write!(f, "null"),
}
}
}

impl Serialize for Id {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Expand Down
95 changes: 95 additions & 0 deletions crates/json-rpc/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use serde_json::value::RawValue;

use crate::{ErrorPayload, RpcReturn};

/// An RPC error.
#[derive(thiserror::Error, Debug)]
pub enum RpcError<E, ErrResp = Box<RawValue>> {
/// Server returned an error response.
#[error("Server returned an error response: {0}")]
ErrorResp(ErrorPayload<ErrResp>),

/// JSON serialization error.
#[error("Serialization error: {0}")]
SerError(
/// The underlying serde_json error.
// To avoid accidentally confusing ser and deser errors, we do not use
// the `#[from]` tag.
#[source]
serde_json::Error,
),
/// JSON deserialization error.
#[error("Deserialization error: {err}")]
DeserError {
/// The underlying serde_json error.
// To avoid accidentally confusing ser and deser errors, we do not use
// the `#[from]` tag.
#[source]
err: serde_json::Error,
/// For deser errors, the text that failed to deserialize.
text: String,
},

/// Transport error.
///
/// This variant is used when the error occurs during communication.
#[error("Error during transport: {0}")]
Transport(
/// The underlying transport error.
#[from]
E,
),
}

impl<E, ErrResp> RpcError<E, ErrResp>
where
ErrResp: RpcReturn,
{
/// Instantiate a new `TransportError` from an error response.
pub const fn err_resp(err: ErrorPayload<ErrResp>) -> Self {
Self::ErrorResp(err)
}
}

impl<E, ErrResp> RpcError<E, ErrResp> {
/// Instantiate a new `TransportError` from a [`serde_json::Error`]. This
/// should be called when the error occurs during serialization.
pub const fn ser_err(err: serde_json::Error) -> Self {
Self::SerError(err)
}

/// Instantiate a new `TransportError` from a [`serde_json::Error`] and the
/// text. This should be called when the error occurs during
/// deserialization.
pub fn deser_err(err: serde_json::Error, text: impl AsRef<str>) -> Self {
Self::DeserError { err, text: text.as_ref().to_owned() }
}

/// Check if the error is a serialization error.
pub const fn is_ser_error(&self) -> bool {
matches!(self, Self::SerError(_))
}

/// Check if the error is a deserialization error.
pub const fn is_deser_error(&self) -> bool {
matches!(self, Self::DeserError { .. })
}

/// Check if the error is a transport error.
pub const fn is_transport_error(&self) -> bool {
matches!(self, Self::Transport(_))
}

/// Check if the error is an error response.
pub const fn is_error_resp(&self) -> bool {
matches!(self, Self::ErrorResp(_))
}

/// Fallible conversion to an error response.
pub const fn as_error_resp(&self) -> Option<&ErrorPayload<ErrResp>> {
match self {
Self::ErrorResp(err) => Some(err),
_ => None,
}
}
}
48 changes: 40 additions & 8 deletions crates/json-rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,34 @@
//!
//! [`alloy-transports`]: https://docs.rs/alloy-transports/latest/alloy-transports
//!
//! ## Usage
//!
//! This crate models the JSON-RPC 2.0 protocol data-types. It is intended to
//! be used to build JSON-RPC clients or servers. Most users will not need to
//! import this crate.
//!
//! This crate provides the following low-level data types:
//!
//! - [`Request`] - A JSON-RPC request.
//! - [`Response`] - A JSON-RPC response.
//! - [`ErrorPayload`] - A JSON-RPC error response payload, including code and message.
//! - [`ResponsePayload`] - The payload of a JSON-RPC response, either a success payload, or an
//! [`ErrorPayload`].
//!
//! For client-side Rust ergonomics, we want to map responses to [`Result`]s.
//! To that end, we provide the following types:
//!
//! - [`RpcError`] - An error that can occur during JSON-RPC communication. This type aggregates
//! errors that are common to all transports, such as (de)serialization, error responses, and
//! includes a generic transport error.
//! - [`RpcResult`] - A result modeling an Rpc outcome as `Result<T,
//! RpcError<E>>`.
//!
//! We recommend that transport implementors use [`RpcResult`] as the return
//! type for their transport methods, parameterized by their transport error
//! type. This will allow them to return either a successful response or an
//! error.
//!
//! ## Note On (De)Serialization
//!
//! [`Request`], [`Response`], and similar types are generic over the
Expand All @@ -35,9 +63,8 @@
//!
//! In general, partially deserialized responses can be further deserialized.
//! E.g. an [`BorrowedRpcResult`] may have success responses deserialized
//! with [`RpcResult::deserialize_success::<U>`], which will transform it to an
//! [`RpcResult<U>`]. Or the caller may use [`RpcResult::try_success_as::<U>`]
//! to attempt to deserialize without transforming the [`RpcResult`].
//! with [`crate::try_deserialize_ok::<U>`], which will transform it to an
//! [`RpcResult<U>`].

#![doc(
html_logo_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/alloy.jpg",
Expand All @@ -55,7 +82,11 @@
#![deny(unused_must_use, rust_2018_idioms)]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]

use serde::{de::DeserializeOwned, Serialize};
mod common;
pub use common::Id;

mod error;
pub use error::RpcError;

mod notification;
pub use notification::{EthNotification, PubSubItem};
Expand All @@ -72,11 +103,12 @@ pub use response::{
ResponsePayload,
};

mod common;
pub use common::Id;

mod result;
pub use result::{BorrowedRpcResult, RpcResult};
pub use result::{
transform_response, transform_result, try_deserialize_ok, BorrowedRpcResult, RpcResult,
};

use serde::{de::DeserializeOwned, Serialize};

/// An object that can be used as a JSON-RPC parameter.
///
Expand Down
12 changes: 12 additions & 0 deletions crates/json-rpc/src/response/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ pub struct ErrorPayload<ErrData = Box<RawValue>> {
pub data: Option<ErrData>,
}

impl<ErrData> std::fmt::Display for ErrorPayload<ErrData> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"ErrorPayload code {}, message: \"{}\", contains payload: {}",
self.code,
self.message,
self.data.is_some()
)
}
}

/// A [`ErrorPayload`] that has been partially deserialized, borrowing its
/// contents from the deserializer. This is used primarily for intermediate
/// deserialization. Most users will not require it.
Expand Down
Loading

0 comments on commit b530c78

Please sign in to comment.