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 { code: ErrorCode, message: String },
97
98 #[error("protocol error: {0}")]
101 Protocol(String),
102
103 #[error("simulation failed: {0}")]
106 SimulationFailed(String),
107
108 #[error("transaction reverted: {0}")]
111 TransactionReverted(String),
112
113 #[error("configuration error: {0}")]
115 Config(String),
116}
117
118impl FyndError {
119 pub fn is_retryable(&self) -> bool {
124 match self {
125 Self::Http(_) => true,
126 Self::Api { code, .. } => code.is_retryable(),
127 _ => false,
128 }
129 }
130
131 pub fn is_revert(&self) -> bool {
133 matches!(self, Self::TransactionReverted(_))
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn error_code_from_known_server_codes() {
143 assert_eq!(ErrorCode::from_server_code("BAD_REQUEST"), ErrorCode::BadRequest);
144 assert_eq!(ErrorCode::from_server_code("NO_ROUTE_FOUND"), ErrorCode::NoRouteFound);
145 assert_eq!(
146 ErrorCode::from_server_code("INSUFFICIENT_LIQUIDITY"),
147 ErrorCode::InsufficientLiquidity
148 );
149 assert_eq!(ErrorCode::from_server_code("INVALID_ORDER"), ErrorCode::BadRequest);
150 assert_eq!(ErrorCode::from_server_code("TIMEOUT"), ErrorCode::SolveTimeout);
151 assert_eq!(ErrorCode::from_server_code("QUEUE_FULL"), ErrorCode::ServiceUnavailable);
152 assert_eq!(
153 ErrorCode::from_server_code("SERVICE_OVERLOADED"),
154 ErrorCode::ServiceUnavailable
155 );
156 assert_eq!(ErrorCode::from_server_code("STALE_DATA"), ErrorCode::ServiceUnavailable);
157 assert_eq!(ErrorCode::from_server_code("NOT_READY"), ErrorCode::ServiceUnavailable);
158 }
159
160 #[test]
161 fn error_code_server_error_for_server_fault_codes() {
162 assert_eq!(ErrorCode::from_server_code("ALGORITHM_ERROR"), ErrorCode::ServerError);
163 assert_eq!(ErrorCode::from_server_code("INTERNAL_ERROR"), ErrorCode::ServerError);
164 assert_eq!(ErrorCode::from_server_code("FAILED_ENCODING"), ErrorCode::ServerError);
165 }
166
167 #[test]
168 fn error_code_not_found_for_not_found_code() {
169 assert_eq!(ErrorCode::from_server_code("NOT_FOUND"), ErrorCode::NotFound);
170 }
171
172 #[test]
173 fn error_code_unknown_for_unrecognised_codes() {
174 assert!(matches!(ErrorCode::from_server_code("WHATEVER"), ErrorCode::Unknown(_)));
175 assert!(matches!(ErrorCode::from_server_code("SOME_FUTURE_CODE"), ErrorCode::Unknown(_)));
176 }
177
178 #[test]
179 fn is_retryable_true_for_retryable_codes() {
180 assert!(
181 FyndError::Api { code: ErrorCode::SolveTimeout, message: String::new() }.is_retryable()
182 );
183 assert!(FyndError::Api { code: ErrorCode::ServiceUnavailable, message: String::new() }
184 .is_retryable());
185 }
186
187 #[test]
188 fn is_retryable_false_for_non_retryable_errors() {
189 assert!(
190 !FyndError::Api { code: ErrorCode::BadRequest, message: String::new() }.is_retryable()
191 );
192 assert!(!FyndError::Api { code: ErrorCode::NoRouteFound, message: String::new() }
193 .is_retryable());
194 assert!(!FyndError::Protocol("bad data".into()).is_retryable());
195 assert!(!FyndError::Config("missing sender".into()).is_retryable());
196 }
197}