use crate::{ErrorPayload, RpcRecv};
use alloy_primitives::B256;
use serde_json::value::RawValue;
pub trait ErrorResponseCode {
fn error_response_code(&self) -> Option<i64>;
}
#[derive(Debug, thiserror::Error)]
pub enum RpcError<E, ErrResp = Box<RawValue>> {
#[error("server returned an error response: {0}")]
ErrorResp(ErrorPayload<ErrResp>),
#[error("server returned a null response when a non-null response was expected")]
NullResp,
#[error("unsupported feature: {0}")]
UnsupportedFeature(&'static str),
#[error("local usage error: {0}")]
LocalUsageError(#[source] Box<dyn std::error::Error + Send + Sync>),
#[error("serialization error: {0}")]
SerError(
#[source]
serde_json::Error,
),
#[error("deserialization error: {err}\n{text}")]
DeserError {
#[source]
err: serde_json::Error,
text: String,
},
#[error(transparent)]
Transport(
#[from]
E,
),
}
impl<E, ErrResp> RpcError<E, ErrResp>
where
ErrResp: RpcRecv,
{
pub const fn err_resp(err: ErrorPayload<ErrResp>) -> Self {
Self::ErrorResp(err)
}
pub fn local_usage(err: impl std::error::Error + Send + Sync + 'static) -> Self {
Self::LocalUsageError(err.into())
}
pub fn local_usage_str(err: &str) -> Self {
Self::LocalUsageError(err.into())
}
pub fn deser_err(err: serde_json::Error, text: impl AsRef<str>) -> Self {
let text = text.as_ref();
if let Ok(err) = serde_json::from_str::<ErrorPayload<ErrResp>>(text) {
return Self::ErrorResp(err);
}
Self::DeserError { err, text: text.to_owned() }
}
}
impl<E, ErrResp> RpcError<E, ErrResp> {
pub const fn ser_err(err: serde_json::Error) -> Self {
Self::SerError(err)
}
pub const fn is_ser_error(&self) -> bool {
matches!(self, Self::SerError(_))
}
pub const fn is_deser_error(&self) -> bool {
matches!(self, Self::DeserError { .. })
}
pub const fn is_transport_error(&self) -> bool {
matches!(self, Self::Transport(_))
}
pub const fn is_error_resp(&self) -> bool {
matches!(self, Self::ErrorResp(_))
}
pub const fn is_null_resp(&self) -> bool {
matches!(self, Self::NullResp)
}
pub const fn is_unsupported_feature(&self) -> bool {
matches!(self, Self::UnsupportedFeature(_))
}
pub const fn is_local_usage_error(&self) -> bool {
matches!(self, Self::LocalUsageError(_))
}
pub const fn as_error_resp(&self) -> Option<&ErrorPayload<ErrResp>> {
match self {
Self::ErrorResp(err) => Some(err),
_ => None,
}
}
pub const fn as_transport_err(&self) -> Option<&E> {
match self {
Self::Transport(err) => Some(err),
_ => None,
}
}
}
impl<E: ErrorResponseCode, ErrResp> RpcError<E, ErrResp> {
pub fn error_code(&self) -> Option<i64> {
if let Some(payload) = self.as_error_resp() {
return Some(payload.code);
}
self.as_transport_err()?.error_response_code()
}
}
impl<E> RpcError<E, Box<RawValue>> {
fn parse_data<T: std::str::FromStr>(&self) -> Option<T> {
let error_payload = self.as_error_resp()?;
let data = error_payload.data.as_ref()?;
let data_str = data.get().trim_matches('"').trim();
data_str.parse().ok()
}
pub fn tx_hash_data(&self) -> Option<B256> {
self.parse_data()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct Body(Option<i64>);
impl ErrorResponseCode for Body {
fn error_response_code(&self) -> Option<i64> {
self.0
}
}
fn transport(code: Option<i64>) -> RpcError<Body> {
RpcError::Transport(Body(code))
}
#[test]
fn error_code_from_error_response() {
let error: RpcError<Body> = RpcError::ErrorResp(ErrorPayload::invalid_request());
assert_eq!(error.error_code(), Some(-32600));
}
#[test]
fn error_code_from_transport_body() {
assert_eq!(transport(Some(-32601)).error_code(), Some(-32601));
}
#[test]
fn error_code_none_for_unrelated_errors() {
assert_eq!(transport(None).error_code(), None);
assert_eq!(RpcError::<Body>::NullResp.error_code(), None);
}
#[test]
fn error_code_is_callable_generically() {
fn code<E: ErrorResponseCode, ErrResp>(err: &RpcError<E, ErrResp>) -> Option<i64> {
err.error_code()
}
assert_eq!(code(&transport(Some(-32602))), Some(-32602));
}
}