1use thiserror::Error;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
8#[non_exhaustive]
9pub enum ErrorCode {
10 BadRequest,
14
15 NoRouteFound,
19
20 InsufficientLiquidity,
24
25 SolveTimeout,
29
30 ServiceUnavailable,
35
36 ServerError,
40
41 NotFound,
45
46 Unknown(String),
48}
49
50impl ErrorCode {
51 pub fn from_server_code(code: &str) -> Self {
55 match code {
56 "BAD_REQUEST" | "INVALID_ORDER" => Self::BadRequest,
57 "NO_ROUTE_FOUND" => Self::NoRouteFound,
58 "INSUFFICIENT_LIQUIDITY" => Self::InsufficientLiquidity,
59 "TIMEOUT" => Self::SolveTimeout,
60 "QUEUE_FULL" | "SERVICE_OVERLOADED" | "STALE_DATA" | "NOT_READY" => {
61 Self::ServiceUnavailable
62 }
63 "ALGORITHM_ERROR" | "INTERNAL_ERROR" | "FAILED_ENCODING" | "PRICE_CHECK_FAILED" => {
64 Self::ServerError
65 }
66 "NOT_FOUND" => Self::NotFound,
67 other => Self::Unknown(other.to_string()),
68 }
69 }
70
71 pub fn is_retryable(&self) -> bool {
77 matches!(self, Self::SolveTimeout | Self::ServiceUnavailable)
78 }
79}
80
81#[derive(Debug, Error)]
83pub enum FyndError {
84 #[error("HTTP error: {0}")]
88 Http(#[from] reqwest::Error),
89
90 #[error("provider error: {0}")]
93 Provider(#[from] alloy::transports::RpcError<alloy::transports::TransportErrorKind>),
94
95 #[error("API error ({code:?}): {message}")]
98 Api {
99 code: ErrorCode,
101 message: String,
103 },
104
105 #[error("protocol error: {0}")]
108 Protocol(String),
109
110 #[error("simulation failed: {0}")]
113 SimulationFailed(String),
114
115 #[error("transaction reverted: {0}")]
118 TransactionReverted(String),
119
120 #[error("configuration error: {0}")]
122 Config(String),
123}
124
125impl FyndError {
126 pub fn is_retryable(&self) -> bool {
131 match self {
132 Self::Http(_) => true,
133 Self::Api { code, .. } => code.is_retryable(),
134 _ => false,
135 }
136 }
137
138 pub fn is_revert(&self) -> bool {
140 matches!(self, Self::TransactionReverted(_))
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn error_code_from_known_server_codes() {
150 assert_eq!(ErrorCode::from_server_code("BAD_REQUEST"), ErrorCode::BadRequest);
151 assert_eq!(ErrorCode::from_server_code("NO_ROUTE_FOUND"), ErrorCode::NoRouteFound);
152 assert_eq!(
153 ErrorCode::from_server_code("INSUFFICIENT_LIQUIDITY"),
154 ErrorCode::InsufficientLiquidity
155 );
156 assert_eq!(ErrorCode::from_server_code("INVALID_ORDER"), ErrorCode::BadRequest);
157 assert_eq!(ErrorCode::from_server_code("TIMEOUT"), ErrorCode::SolveTimeout);
158 assert_eq!(ErrorCode::from_server_code("QUEUE_FULL"), ErrorCode::ServiceUnavailable);
159 assert_eq!(
160 ErrorCode::from_server_code("SERVICE_OVERLOADED"),
161 ErrorCode::ServiceUnavailable
162 );
163 assert_eq!(ErrorCode::from_server_code("STALE_DATA"), ErrorCode::ServiceUnavailable);
164 assert_eq!(ErrorCode::from_server_code("NOT_READY"), ErrorCode::ServiceUnavailable);
165 }
166
167 #[test]
168 fn error_code_server_error_for_server_fault_codes() {
169 assert_eq!(ErrorCode::from_server_code("ALGORITHM_ERROR"), ErrorCode::ServerError);
170 assert_eq!(ErrorCode::from_server_code("INTERNAL_ERROR"), ErrorCode::ServerError);
171 assert_eq!(ErrorCode::from_server_code("FAILED_ENCODING"), ErrorCode::ServerError);
172 }
173
174 #[test]
175 fn error_code_not_found_for_not_found_code() {
176 assert_eq!(ErrorCode::from_server_code("NOT_FOUND"), ErrorCode::NotFound);
177 }
178
179 #[test]
180 fn error_code_unknown_for_unrecognised_codes() {
181 assert!(matches!(ErrorCode::from_server_code("WHATEVER"), ErrorCode::Unknown(_)));
182 assert!(matches!(ErrorCode::from_server_code("SOME_FUTURE_CODE"), ErrorCode::Unknown(_)));
183 }
184
185 #[test]
186 fn is_retryable_true_for_retryable_codes() {
187 assert!(
188 FyndError::Api { code: ErrorCode::SolveTimeout, message: String::new() }.is_retryable()
189 );
190 assert!(FyndError::Api { code: ErrorCode::ServiceUnavailable, message: String::new() }
191 .is_retryable());
192 }
193
194 #[test]
195 fn is_retryable_false_for_non_retryable_errors() {
196 assert!(
197 !FyndError::Api { code: ErrorCode::BadRequest, message: String::new() }.is_retryable()
198 );
199 assert!(!FyndError::Api { code: ErrorCode::NoRouteFound, message: String::new() }
200 .is_retryable());
201 assert!(!FyndError::Protocol("bad data".into()).is_retryable());
202 assert!(!FyndError::Config("missing sender".into()).is_retryable());
203 }
204}