tycho_ethereum/
errors.rs

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            // Serialization/Deserialization errors
67            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            // In case the server did not respond to a batch request we re
77            AlloyRpcError::Transport(MissingBatchResponse(id)) => {
78                RPCError::UnknownError(format!("Missing batch response for request ID {}", id))
79            }
80
81            // Other transport errors
82            AlloyRpcError::Transport(err) => {
83                RPCError::RequestError(RequestError::Reqwest(ReqwestError {
84                    msg: "HTTP transport error".to_string(),
85                    source: err,
86                }))
87            }
88
89            // JSON-RPC error responses from the server
90            AlloyRpcError::ErrorResp(err) => {
91                RPCError::UnknownError(format!("RPC returned error response: {}", err))
92            }
93
94            // Null response when non-null expected
95            AlloyRpcError::NullResp => {
96                RPCError::UnknownError("RPC returned null response".to_string())
97            }
98
99            // Feature not supported by the RPC endpoint
100            AlloyRpcError::UnsupportedFeature(feature) => {
101                RPCError::UnknownError(format!("Unsupported RPC feature: {}", feature))
102            }
103
104            // Local usage/configuration errors
105            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
119/// Helper function to extract the full error chain including source errors
120pub(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}