alloy_json_rpc/error.rs
1use crate::{ErrorPayload, RpcRecv};
2use alloy_primitives::B256;
3use serde_json::value::RawValue;
4
5/// An RPC error.
6#[derive(Debug, thiserror::Error)]
7pub enum RpcError<E, ErrResp = Box<RawValue>> {
8 /// Server returned an error response.
9 #[error("server returned an error response: {0}")]
10 ErrorResp(ErrorPayload<ErrResp>),
11
12 /// Server returned a null response when a non-null response was expected.
13 #[error("server returned a null response when a non-null response was expected")]
14 NullResp,
15
16 /// Rpc server returned an unsupported feature.
17 #[error("unsupported feature: {0}")]
18 UnsupportedFeature(&'static str),
19
20 /// Returned when a local pre-processing step fails. This allows custom
21 /// errors from local signers or request pre-processors.
22 #[error("local usage error: {0}")]
23 LocalUsageError(#[source] Box<dyn std::error::Error + Send + Sync>),
24
25 /// JSON serialization error.
26 #[error("serialization error: {0}")]
27 SerError(
28 /// The underlying serde_json error.
29 // To avoid accidentally confusing ser and deser errors, we do not use
30 // the `#[from]` tag.
31 #[source]
32 serde_json::Error,
33 ),
34 /// JSON deserialization error.
35 #[error("deserialization error: {err}\n{text}")]
36 DeserError {
37 /// The underlying serde_json error.
38 // To avoid accidentally confusing ser and deser errors, we do not use
39 // the `#[from]` tag.
40 #[source]
41 err: serde_json::Error,
42 /// For deser errors, the text that failed to deserialize.
43 text: String,
44 },
45
46 /// Transport error.
47 ///
48 /// This variant is used when the error occurs during communication.
49 #[error(transparent)]
50 Transport(
51 /// The underlying transport error.
52 #[from]
53 E,
54 ),
55}
56
57impl<E, ErrResp> RpcError<E, ErrResp>
58where
59 ErrResp: RpcRecv,
60{
61 /// Instantiate a new `ErrorResp` from an error response.
62 pub const fn err_resp(err: ErrorPayload<ErrResp>) -> Self {
63 Self::ErrorResp(err)
64 }
65
66 /// Instantiate a new `LocalUsageError` from a custom error.
67 pub fn local_usage(err: impl std::error::Error + Send + Sync + 'static) -> Self {
68 Self::LocalUsageError(err.into())
69 }
70
71 /// Instantiate a new `LocalUsageError` from a custom error message.
72 pub fn local_usage_str(err: &str) -> Self {
73 Self::LocalUsageError(err.into())
74 }
75
76 /// Instantiate a new `DeserError` from a [`serde_json::Error`] and the
77 /// text. This should be called when the error occurs during
78 /// deserialization.
79 ///
80 /// Note: This will check if the response is actually an [ErrorPayload], if so it will return a
81 /// [RpcError::ErrorResp].
82 pub fn deser_err(err: serde_json::Error, text: impl AsRef<str>) -> Self {
83 let text = text.as_ref();
84
85 // check if the response is actually an `ErrorPayload`
86 if let Ok(err) = serde_json::from_str::<ErrorPayload<ErrResp>>(text) {
87 return Self::ErrorResp(err);
88 }
89
90 Self::DeserError { err, text: text.to_owned() }
91 }
92}
93
94impl<E, ErrResp> RpcError<E, ErrResp> {
95 /// Instantiate a new `SerError` from a [`serde_json::Error`]. This
96 /// should be called when the error occurs during serialization.
97 pub const fn ser_err(err: serde_json::Error) -> Self {
98 Self::SerError(err)
99 }
100
101 /// Check if the error is a serialization error.
102 pub const fn is_ser_error(&self) -> bool {
103 matches!(self, Self::SerError(_))
104 }
105
106 /// Check if the error is a deserialization error.
107 pub const fn is_deser_error(&self) -> bool {
108 matches!(self, Self::DeserError { .. })
109 }
110
111 /// Check if the error is a transport error.
112 pub const fn is_transport_error(&self) -> bool {
113 matches!(self, Self::Transport(_))
114 }
115
116 /// Check if the error is an error response.
117 pub const fn is_error_resp(&self) -> bool {
118 matches!(self, Self::ErrorResp(_))
119 }
120
121 /// Check if the error is a null response.
122 pub const fn is_null_resp(&self) -> bool {
123 matches!(self, Self::NullResp)
124 }
125
126 /// Check if the error is an unsupported feature error.
127 pub const fn is_unsupported_feature(&self) -> bool {
128 matches!(self, Self::UnsupportedFeature(_))
129 }
130
131 /// Check if the error is a local usage error.
132 pub const fn is_local_usage_error(&self) -> bool {
133 matches!(self, Self::LocalUsageError(_))
134 }
135
136 /// Fallible conversion to an error response.
137 pub const fn as_error_resp(&self) -> Option<&ErrorPayload<ErrResp>> {
138 match self {
139 Self::ErrorResp(err) => Some(err),
140 _ => None,
141 }
142 }
143
144 /// Returns the transport error if this is a [`RpcError::Transport`]
145 pub const fn as_transport_err(&self) -> Option<&E> {
146 match self {
147 Self::Transport(err) => Some(err),
148 _ => None,
149 }
150 }
151}
152
153impl<E> RpcError<E, Box<RawValue>> {
154 /// Parses the error data field as a hex string of specified length.
155 ///
156 /// Returns `Some(T)` if the data contains a valid hex string of the expected length.
157 fn parse_data<T: std::str::FromStr>(&self) -> Option<T> {
158 let error_payload = self.as_error_resp()?;
159 let data = error_payload.data.as_ref()?;
160 let data_str = data.get().trim_matches('"').trim();
161 data_str.parse().ok()
162 }
163
164 /// Extracts a transaction hash from the error data field.
165 ///
166 /// Useful for EIP-7966 `eth_sendRawTransactionSync` errors that return
167 /// the transaction hash even when the transaction fails.
168 ///
169 /// Returns `Some(hash)` if the data contains a valid 32-byte hex string.
170 ///
171 /// # Example
172 ///
173 /// ```rust
174 /// use alloy_json_rpc::{RpcError, ErrorPayload};
175 /// use alloy_primitives::B256;
176 ///
177 /// // Simulate an EIP-7966 error response
178 /// let json = r#"{"code":5,"message":"insufficient funds","data":"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"}"#;
179 /// let error_payload: ErrorPayload = serde_json::from_str(json).unwrap();
180 /// let rpc_error: RpcError<(), _> = RpcError::ErrorResp(error_payload);
181 ///
182 /// if let Some(tx_hash) = rpc_error.tx_hash_data() {
183 /// println!("Transaction hash: {}", tx_hash);
184 /// }
185 /// ```
186 pub fn tx_hash_data(&self) -> Option<B256> {
187 self.parse_data()
188 }
189}