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