1use std::{error::Error, fmt::Display};
2
3use alloy::transports::{
4 RpcError as AlloyRpcError, TransportErrorKind, TransportErrorKind::MissingBatchResponse,
5};
6use thiserror::Error;
7
8#[derive(Error, Debug)]
9pub struct SerdeJsonError {
10 pub msg: String,
11 #[source]
12 pub source: serde_json::Error,
13}
14
15impl Display for SerdeJsonError {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 write!(f, "{}: {}", self.msg, self.source)
18 }
19}
20
21#[derive(Error, Debug)]
22pub struct ReqwestError {
23 pub msg: String,
24 #[source]
25 pub source: TransportErrorKind,
26}
27
28impl Display for ReqwestError {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 write!(f, "{}: {}", self.msg, self.source)
31 }
32}
33
34#[derive(Error, Debug)]
35pub enum RequestError {
36 Reqwest(ReqwestError),
37 Other(String),
38}
39
40impl Display for RequestError {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 match self {
43 RequestError::Reqwest(e) => write!(f, "{}: {}", e.msg, e.source),
44 RequestError::Other(e) => write!(f, "{e}"),
45 }
46 }
47}
48
49#[derive(Error, Debug)]
50pub enum RPCError {
51 #[error("RPC setup error: {0}")]
52 SetupError(String),
53 #[error("Request error: {0}")]
54 RequestError(RequestError),
55 #[error("Tracing failure: {0}")]
56 TracingFailure(String),
57 #[error("Serialize error: {0}")]
58 SerializeError(SerdeJsonError),
59 #[error("Unknown error: {0}")]
60 UnknownError(String),
61}
62
63impl From<AlloyRpcError<TransportErrorKind>> for RPCError {
64 fn from(e: AlloyRpcError<TransportErrorKind>) -> Self {
65 match e {
66 AlloyRpcError::SerError(err) => RPCError::SerializeError(SerdeJsonError {
68 msg: "JSON serialization failed".to_string(),
69 source: err,
70 }),
71 AlloyRpcError::DeserError { err, text } => RPCError::SerializeError(SerdeJsonError {
72 msg: format!("JSON deserialization failed: {}", text),
73 source: err,
74 }),
75
76 AlloyRpcError::Transport(MissingBatchResponse(id)) => {
78 RPCError::UnknownError(format!("Missing batch response for request ID {}", id))
79 }
80
81 AlloyRpcError::Transport(err) => {
83 RPCError::RequestError(RequestError::Reqwest(ReqwestError {
84 msg: "HTTP transport error".to_string(),
85 source: err,
86 }))
87 }
88
89 AlloyRpcError::ErrorResp(err) => {
91 RPCError::UnknownError(format!("RPC returned error response: {}", err))
92 }
93
94 AlloyRpcError::NullResp => {
96 RPCError::UnknownError("RPC returned null response".to_string())
97 }
98
99 AlloyRpcError::UnsupportedFeature(feature) => {
101 RPCError::UnknownError(format!("Unsupported RPC feature: {}", feature))
102 }
103
104 AlloyRpcError::LocalUsageError(err) => RPCError::UnknownError(format!(
106 "Local RPC usage error: {}",
107 extract_error_chain(err.as_ref())
108 )),
109 }
110 }
111}
112
113impl RPCError {
114 pub fn should_retry(&self) -> bool {
115 matches!(self, Self::RequestError(_))
116 }
117}
118
119pub(crate) fn extract_error_chain(error: &dyn Error) -> String {
121 let mut chain = vec![error.to_string()];
122 let mut source = error.source();
123
124 while let Some(err) = source {
125 chain.push(err.to_string());
126 source = err.source();
127 }
128
129 if chain.len() == 1 {
130 chain[0].clone()
131 } else {
132 format!("{} (caused by: {})", chain[0], chain[1..].join(" -> "))
133 }
134}