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" => Self::ServerError,
64 "NOT_FOUND" => Self::NotFound,
65 other => Self::Unknown(other.to_string()),
66 }
67 }
68
69 pub fn is_retryable(&self) -> bool {
75 matches!(self, Self::SolveTimeout | Self::ServiceUnavailable)
76 }
77}
78
79#[derive(Debug, Error)]
81pub enum FyndError {
82 #[error("HTTP error: {0}")]
86 Http(#[from] reqwest::Error),
87
88 #[error("provider error: {0}")]
91 Provider(#[from] alloy::transports::RpcError<alloy::transports::TransportErrorKind>),
92
93 #[error("API error ({code:?}): {message}")]
96 Api {
97 code: ErrorCode,
99 message: String,
101 },
102
103 #[error("protocol error: {0}")]
106 Protocol(String),
107
108 #[error("simulation failed: {0}")]
111 SimulationFailed(String),
112
113 #[error("transaction reverted: {0}")]
116 TransactionReverted(String),
117
118 #[error("configuration error: {0}")]
120 Config(String),
121}
122
123impl FyndError {
124 pub fn is_retryable(&self) -> bool {
129 match self {
130 Self::Http(_) => true,
131 Self::Api { code, .. } => code.is_retryable(),
132 _ => false,
133 }
134 }
135
136 pub fn is_revert(&self) -> bool {
138 matches!(self, Self::TransactionReverted(_))
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn error_code_from_known_server_codes() {
148 assert_eq!(ErrorCode::from_server_code("BAD_REQUEST"), ErrorCode::BadRequest);
149 assert_eq!(ErrorCode::from_server_code("NO_ROUTE_FOUND"), ErrorCode::NoRouteFound);
150 assert_eq!(
151 ErrorCode::from_server_code("INSUFFICIENT_LIQUIDITY"),
152 ErrorCode::InsufficientLiquidity
153 );
154 assert_eq!(ErrorCode::from_server_code("INVALID_ORDER"), ErrorCode::BadRequest);
155 assert_eq!(ErrorCode::from_server_code("TIMEOUT"), ErrorCode::SolveTimeout);
156 assert_eq!(ErrorCode::from_server_code("QUEUE_FULL"), ErrorCode::ServiceUnavailable);
157 assert_eq!(
158 ErrorCode::from_server_code("SERVICE_OVERLOADED"),
159 ErrorCode::ServiceUnavailable
160 );
161 assert_eq!(ErrorCode::from_server_code("STALE_DATA"), ErrorCode::ServiceUnavailable);
162 assert_eq!(ErrorCode::from_server_code("NOT_READY"), ErrorCode::ServiceUnavailable);
163 }
164
165 #[test]
166 fn error_code_server_error_for_server_fault_codes() {
167 assert_eq!(ErrorCode::from_server_code("ALGORITHM_ERROR"), ErrorCode::ServerError);
168 assert_eq!(ErrorCode::from_server_code("INTERNAL_ERROR"), ErrorCode::ServerError);
169 assert_eq!(ErrorCode::from_server_code("FAILED_ENCODING"), ErrorCode::ServerError);
170 }
171
172 #[test]
173 fn error_code_not_found_for_not_found_code() {
174 assert_eq!(ErrorCode::from_server_code("NOT_FOUND"), ErrorCode::NotFound);
175 }
176
177 #[test]
178 fn error_code_unknown_for_unrecognised_codes() {
179 assert!(matches!(ErrorCode::from_server_code("WHATEVER"), ErrorCode::Unknown(_)));
180 assert!(matches!(ErrorCode::from_server_code("SOME_FUTURE_CODE"), ErrorCode::Unknown(_)));
181 }
182
183 #[test]
184 fn is_retryable_true_for_retryable_codes() {
185 assert!(
186 FyndError::Api { code: ErrorCode::SolveTimeout, message: String::new() }.is_retryable()
187 );
188 assert!(FyndError::Api { code: ErrorCode::ServiceUnavailable, message: String::new() }
189 .is_retryable());
190 }
191
192 #[test]
193 fn is_retryable_false_for_non_retryable_errors() {
194 assert!(
195 !FyndError::Api { code: ErrorCode::BadRequest, message: String::new() }.is_retryable()
196 );
197 assert!(!FyndError::Api { code: ErrorCode::NoRouteFound, message: String::new() }
198 .is_retryable());
199 assert!(!FyndError::Protocol("bad data".into()).is_retryable());
200 assert!(!FyndError::Config("missing sender".into()).is_retryable());
201 }
202}