1use serde::{Deserialize, Serialize};
19use std::fmt;
20use uuid::Uuid;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
27#[serde(transparent)]
28pub struct TraceId(pub Uuid);
29
30impl TraceId {
31 pub fn new(uuid: Uuid) -> Self {
33 Self(uuid)
34 }
35
36 pub fn as_uuid(&self) -> Uuid {
38 self.0
39 }
40}
41
42impl fmt::Display for TraceId {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 write!(f, "{}", self.0)
45 }
46}
47
48impl From<Uuid> for TraceId {
49 fn from(uuid: Uuid) -> Self {
50 Self(uuid)
51 }
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum ErrorCategory {
57 General,
59 Algo,
61 InternalService,
63 Validation,
65 Internal,
67 Unknown,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub enum OdosErrorCode {
77 ApiError,
80
81 NoViablePath,
84 AlgoValidationError,
86 AlgoConnectionError,
88 AlgoTimeout,
90 AlgoInternal,
92
93 InternalServiceError,
96
97 ConfigInternal,
100 ConfigConnectionError,
102 ConfigTimeout,
104
105 TxnAssemblyInternal,
108 TxnAssemblyConnectionError,
110 TxnAssemblyTimeout,
112
113 ChainDataInternal,
116 ChainDataConnectionError,
118 ChainDataTimeout,
120
121 PricingInternal,
124 PricingConnectionError,
126 PricingTimeout,
128
129 GasInternal,
132 GasConnectionError,
134 GasTimeout,
136 GasUnavailable,
138
139 InvalidRequest,
142
143 InvalidChainId,
146 InvalidInputTokens,
148 InvalidOutputTokens,
150 InvalidUserAddr,
152 BlockedUserAddr,
154 TooSlippery,
156 SameInputOutput,
158 MultiZapOutput,
160 InvalidTokenCount,
162 InvalidTokenAddr,
164 NonIntegerTokenAmount,
166 NegativeTokenAmount,
168 SameInputOutputTokens,
170 TokenBlacklisted,
172 InvalidTokenProportions,
174 TokenRoutingUnavailable,
176 InvalidReferralCode,
178 InvalidTokenAmount,
180 NonStringTokenAmount,
182
183 InvalidAssemblyRequest,
186 InvalidAssemblyUserAddr,
188 InvalidReceiverAddr,
190
191 InvalidSwapRequest,
194 UserAddrRequired,
196
197 InternalError,
200 SwapUnavailable,
202 PriceCheckFailure,
204 DefaultGasFailure,
206
207 Unknown(u16),
209}
210
211impl OdosErrorCode {
212 pub fn code(&self) -> u16 {
214 match self {
215 Self::ApiError => 1000,
216 Self::NoViablePath => 2000,
217 Self::AlgoValidationError => 2400,
218 Self::AlgoConnectionError => 2997,
219 Self::AlgoTimeout => 2998,
220 Self::AlgoInternal => 2999,
221 Self::InternalServiceError => 3000,
222 Self::ConfigInternal => 3100,
223 Self::ConfigConnectionError => 3101,
224 Self::ConfigTimeout => 3102,
225 Self::TxnAssemblyInternal => 3110,
226 Self::TxnAssemblyConnectionError => 3111,
227 Self::TxnAssemblyTimeout => 3112,
228 Self::ChainDataInternal => 3120,
229 Self::ChainDataConnectionError => 3121,
230 Self::ChainDataTimeout => 3122,
231 Self::PricingInternal => 3130,
232 Self::PricingConnectionError => 3131,
233 Self::PricingTimeout => 3132,
234 Self::GasInternal => 3140,
235 Self::GasConnectionError => 3141,
236 Self::GasTimeout => 3142,
237 Self::GasUnavailable => 3143,
238 Self::InvalidRequest => 4000,
239 Self::InvalidChainId => 4001,
240 Self::InvalidInputTokens => 4002,
241 Self::InvalidOutputTokens => 4003,
242 Self::InvalidUserAddr => 4004,
243 Self::BlockedUserAddr => 4005,
244 Self::TooSlippery => 4006,
245 Self::SameInputOutput => 4007,
246 Self::MultiZapOutput => 4008,
247 Self::InvalidTokenCount => 4009,
248 Self::InvalidTokenAddr => 4010,
249 Self::NonIntegerTokenAmount => 4011,
250 Self::NegativeTokenAmount => 4012,
251 Self::SameInputOutputTokens => 4013,
252 Self::TokenBlacklisted => 4014,
253 Self::InvalidTokenProportions => 4015,
254 Self::TokenRoutingUnavailable => 4016,
255 Self::InvalidReferralCode => 4017,
256 Self::InvalidTokenAmount => 4018,
257 Self::NonStringTokenAmount => 4019,
258 Self::InvalidAssemblyRequest => 4100,
259 Self::InvalidAssemblyUserAddr => 4101,
260 Self::InvalidReceiverAddr => 4102,
261 Self::InvalidSwapRequest => 4200,
262 Self::UserAddrRequired => 4201,
263 Self::InternalError => 5000,
264 Self::SwapUnavailable => 5001,
265 Self::PriceCheckFailure => 5002,
266 Self::DefaultGasFailure => 5003,
267 Self::Unknown(code) => *code,
268 }
269 }
270
271 pub fn category(&self) -> ErrorCategory {
273 let code = self.code();
274 match code {
275 1000..=1999 => ErrorCategory::General,
276 2000..=2999 => ErrorCategory::Algo,
277 3000..=3999 => ErrorCategory::InternalService,
278 4000..=4999 => ErrorCategory::Validation,
279 5000..=5999 => ErrorCategory::Internal,
280 _ => ErrorCategory::Unknown,
281 }
282 }
283
284 pub fn is_general_error(&self) -> bool {
286 matches!(self.category(), ErrorCategory::General)
287 }
288
289 pub fn is_algo_error(&self) -> bool {
291 matches!(self.category(), ErrorCategory::Algo)
292 }
293
294 pub fn is_internal_service_error(&self) -> bool {
296 matches!(self.category(), ErrorCategory::InternalService)
297 }
298
299 pub fn is_validation_error(&self) -> bool {
301 matches!(self.category(), ErrorCategory::Validation)
302 }
303
304 pub fn is_internal_error(&self) -> bool {
306 matches!(self.category(), ErrorCategory::Internal)
307 }
308
309 pub fn is_no_viable_path(&self) -> bool {
311 matches!(self, Self::NoViablePath)
312 }
313
314 pub fn is_invalid_chain_id(&self) -> bool {
316 matches!(self, Self::InvalidChainId)
317 }
318
319 pub fn is_blocked_user(&self) -> bool {
321 matches!(self, Self::BlockedUserAddr)
322 }
323
324 pub fn is_timeout(&self) -> bool {
326 matches!(
327 self,
328 Self::AlgoTimeout
329 | Self::ConfigTimeout
330 | Self::TxnAssemblyTimeout
331 | Self::ChainDataTimeout
332 | Self::PricingTimeout
333 | Self::GasTimeout
334 )
335 }
336
337 pub fn is_connection_error(&self) -> bool {
339 matches!(
340 self,
341 Self::AlgoConnectionError
342 | Self::ConfigConnectionError
343 | Self::TxnAssemblyConnectionError
344 | Self::ChainDataConnectionError
345 | Self::PricingConnectionError
346 | Self::GasConnectionError
347 )
348 }
349
350 pub fn is_retryable(&self) -> bool {
358 self.is_timeout()
359 || self.is_connection_error()
360 || matches!(
361 self,
362 Self::AlgoInternal
363 | Self::ConfigInternal
364 | Self::TxnAssemblyInternal
365 | Self::ChainDataInternal
366 | Self::PricingInternal
367 | Self::GasInternal
368 | Self::GasUnavailable
369 | Self::InternalServiceError
370 | Self::InternalError
371 )
372 }
373}
374
375impl From<u16> for OdosErrorCode {
376 fn from(code: u16) -> Self {
377 match code {
378 1000 => Self::ApiError,
379 2000 => Self::NoViablePath,
380 2400 => Self::AlgoValidationError,
381 2997 => Self::AlgoConnectionError,
382 2998 => Self::AlgoTimeout,
383 2999 => Self::AlgoInternal,
384 3000 => Self::InternalServiceError,
385 3100 => Self::ConfigInternal,
386 3101 => Self::ConfigConnectionError,
387 3102 => Self::ConfigTimeout,
388 3110 => Self::TxnAssemblyInternal,
389 3111 => Self::TxnAssemblyConnectionError,
390 3112 => Self::TxnAssemblyTimeout,
391 3120 => Self::ChainDataInternal,
392 3121 => Self::ChainDataConnectionError,
393 3122 => Self::ChainDataTimeout,
394 3130 => Self::PricingInternal,
395 3131 => Self::PricingConnectionError,
396 3132 => Self::PricingTimeout,
397 3140 => Self::GasInternal,
398 3141 => Self::GasConnectionError,
399 3142 => Self::GasTimeout,
400 3143 => Self::GasUnavailable,
401 4000 => Self::InvalidRequest,
402 4001 => Self::InvalidChainId,
403 4002 => Self::InvalidInputTokens,
404 4003 => Self::InvalidOutputTokens,
405 4004 => Self::InvalidUserAddr,
406 4005 => Self::BlockedUserAddr,
407 4006 => Self::TooSlippery,
408 4007 => Self::SameInputOutput,
409 4008 => Self::MultiZapOutput,
410 4009 => Self::InvalidTokenCount,
411 4010 => Self::InvalidTokenAddr,
412 4011 => Self::NonIntegerTokenAmount,
413 4012 => Self::NegativeTokenAmount,
414 4013 => Self::SameInputOutputTokens,
415 4014 => Self::TokenBlacklisted,
416 4015 => Self::InvalidTokenProportions,
417 4016 => Self::TokenRoutingUnavailable,
418 4017 => Self::InvalidReferralCode,
419 4018 => Self::InvalidTokenAmount,
420 4019 => Self::NonStringTokenAmount,
421 4100 => Self::InvalidAssemblyRequest,
422 4101 => Self::InvalidAssemblyUserAddr,
423 4102 => Self::InvalidReceiverAddr,
424 4200 => Self::InvalidSwapRequest,
425 4201 => Self::UserAddrRequired,
426 5000 => Self::InternalError,
427 5001 => Self::SwapUnavailable,
428 5002 => Self::PriceCheckFailure,
429 5003 => Self::DefaultGasFailure,
430 _ => Self::Unknown(code),
431 }
432 }
433}
434
435impl From<OdosErrorCode> for u16 {
436 fn from(error_code: OdosErrorCode) -> Self {
437 error_code.code()
438 }
439}
440
441impl fmt::Display for OdosErrorCode {
442 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
443 match self {
444 Self::ApiError => write!(f, "1000 (API_ERROR)"),
445 Self::NoViablePath => write!(f, "2000 (NO_VIABLE_PATH)"),
446 Self::AlgoValidationError => write!(f, "2400 (ALGO_VALIDATION_ERR)"),
447 Self::AlgoConnectionError => write!(f, "2997 (ALGO_CONN_ERR)"),
448 Self::AlgoTimeout => write!(f, "2998 (ALGO_TIMEOUT)"),
449 Self::AlgoInternal => write!(f, "2999 (ALGO_INTERNAL)"),
450 Self::InternalServiceError => write!(f, "3000 (INTERNAL_SERVICE_ERROR)"),
451 Self::ConfigInternal => write!(f, "3100 (CONFIG_INTERNAL)"),
452 Self::ConfigConnectionError => write!(f, "3101 (CONFIG_CONN_ERR)"),
453 Self::ConfigTimeout => write!(f, "3102 (CONFIG_TIMEOUT)"),
454 Self::TxnAssemblyInternal => write!(f, "3110 (TXN_ASSEMBLY_INTERNAL)"),
455 Self::TxnAssemblyConnectionError => write!(f, "3111 (TXN_ASSEMBLY_CONN_ERR)"),
456 Self::TxnAssemblyTimeout => write!(f, "3112 (TXN_ASSEMBLY_TIMEOUT)"),
457 Self::ChainDataInternal => write!(f, "3120 (CHAIN_DATA_INTERNAL)"),
458 Self::ChainDataConnectionError => write!(f, "3121 (CHAIN_DATA_CONN_ERR)"),
459 Self::ChainDataTimeout => write!(f, "3122 (CHAIN_DATA_TIMEOUT)"),
460 Self::PricingInternal => write!(f, "3130 (PRICING_INTERNAL)"),
461 Self::PricingConnectionError => write!(f, "3131 (PRICING_CONN_ERR)"),
462 Self::PricingTimeout => write!(f, "3132 (PRICING_TIMEOUT)"),
463 Self::GasInternal => write!(f, "3140 (GAS_INTERNAL)"),
464 Self::GasConnectionError => write!(f, "3141 (GAS_CONN_ERR)"),
465 Self::GasTimeout => write!(f, "3142 (GAS_TIMEOUT)"),
466 Self::GasUnavailable => write!(f, "3143 (GAS_UNAVAILABLE)"),
467 Self::InvalidRequest => write!(f, "4000 (INVALID_REQUEST)"),
468 Self::InvalidChainId => write!(f, "4001 (INVALID_CHAIN_ID)"),
469 Self::InvalidInputTokens => write!(f, "4002 (INVALID_INPUT_TOKENS)"),
470 Self::InvalidOutputTokens => write!(f, "4003 (INVALID_OUTPUT_TOKENS)"),
471 Self::InvalidUserAddr => write!(f, "4004 (INVALID_USER_ADDR)"),
472 Self::BlockedUserAddr => write!(f, "4005 (BLOCKED_USER_ADDR)"),
473 Self::TooSlippery => write!(f, "4006 (TOO_SLIPPERY)"),
474 Self::SameInputOutput => write!(f, "4007 (SAME_INPUT_OUTPUT)"),
475 Self::MultiZapOutput => write!(f, "4008 (MULTI_ZAP_OUTPUT)"),
476 Self::InvalidTokenCount => write!(f, "4009 (INVALID_TOKEN_COUNT)"),
477 Self::InvalidTokenAddr => write!(f, "4010 (INVALID_TOKEN_ADDR)"),
478 Self::NonIntegerTokenAmount => write!(f, "4011 (NON_INTEGER_TOKEN_AMOUNT)"),
479 Self::NegativeTokenAmount => write!(f, "4012 (NEGATIVE_TOKEN_AMOUNT)"),
480 Self::SameInputOutputTokens => write!(f, "4013 (SAME_INPUT_OUTPUT_TOKENS)"),
481 Self::TokenBlacklisted => write!(f, "4014 (TOKEN_BLACKLISTED)"),
482 Self::InvalidTokenProportions => write!(f, "4015 (INVALID_TOKEN_PROPORTIONS)"),
483 Self::TokenRoutingUnavailable => write!(f, "4016 (TOKEN_ROUTING_UNAVAILABLE)"),
484 Self::InvalidReferralCode => write!(f, "4017 (INVALID_REFERRAL_CODE)"),
485 Self::InvalidTokenAmount => write!(f, "4018 (INVALID_TOKEN_AMOUNT)"),
486 Self::NonStringTokenAmount => write!(f, "4019 (NON_STRING_TOKEN_AMOUNT)"),
487 Self::InvalidAssemblyRequest => write!(f, "4100 (INVALID_ASSEMBLY_REQUEST)"),
488 Self::InvalidAssemblyUserAddr => write!(f, "4101 (INVALID_USER_ADDR)"),
489 Self::InvalidReceiverAddr => write!(f, "4102 (INVALID_RECEIVER_ADDR)"),
490 Self::InvalidSwapRequest => write!(f, "4200 (INVALID_SWAP_REQUEST)"),
491 Self::UserAddrRequired => write!(f, "4201 (USER_ADDR_REQ)"),
492 Self::InternalError => write!(f, "5000 (INTERNAL_ERROR)"),
493 Self::SwapUnavailable => write!(f, "5001 (SWAP_UNAVAILABLE)"),
494 Self::PriceCheckFailure => write!(f, "5002 (PRICE_CHECK_FAILURE)"),
495 Self::DefaultGasFailure => write!(f, "5003 (DEFAULT_GAS_FAILURE)"),
496 Self::Unknown(code) => write!(f, "{code} (UNKNOWN)"),
497 }
498 }
499}
500
501#[cfg(test)]
502mod tests {
503 use super::*;
504
505 #[test]
506 fn test_trace_id_creation() {
507 let uuid = Uuid::parse_str("10becdc8-a021-4491-8201-a17b657204e0").unwrap();
508 let trace_id = TraceId::new(uuid);
509 assert_eq!(trace_id.as_uuid(), uuid);
510 assert_eq!(trace_id.to_string(), "10becdc8-a021-4491-8201-a17b657204e0");
511 }
512
513 #[test]
514 fn test_error_code_from_u16() {
515 assert_eq!(OdosErrorCode::from(2999), OdosErrorCode::AlgoInternal);
516 assert_eq!(OdosErrorCode::from(4001), OdosErrorCode::InvalidChainId);
517 assert_eq!(OdosErrorCode::from(3142), OdosErrorCode::GasTimeout);
518 assert_eq!(OdosErrorCode::from(9999), OdosErrorCode::Unknown(9999));
519 }
520
521 #[test]
522 fn test_error_code_to_u16() {
523 assert_eq!(OdosErrorCode::AlgoInternal.code(), 2999);
524 assert_eq!(OdosErrorCode::InvalidChainId.code(), 4001);
525 assert_eq!(OdosErrorCode::Unknown(9999).code(), 9999);
526 }
527
528 #[test]
529 fn test_error_categories() {
530 assert!(OdosErrorCode::ApiError.is_general_error());
531 assert!(OdosErrorCode::NoViablePath.is_algo_error());
532 assert!(OdosErrorCode::ConfigInternal.is_internal_service_error());
533 assert!(OdosErrorCode::InvalidChainId.is_validation_error());
534 assert!(OdosErrorCode::InternalError.is_internal_error());
535 }
536
537 #[test]
538 fn test_specific_error_checks() {
539 assert!(OdosErrorCode::NoViablePath.is_no_viable_path());
540 assert!(OdosErrorCode::InvalidChainId.is_invalid_chain_id());
541 assert!(OdosErrorCode::BlockedUserAddr.is_blocked_user());
542
543 assert!(!OdosErrorCode::ApiError.is_no_viable_path());
544 assert!(!OdosErrorCode::AlgoInternal.is_invalid_chain_id());
545 }
546
547 #[test]
548 fn test_timeout_detection() {
549 assert!(OdosErrorCode::AlgoTimeout.is_timeout());
550 assert!(OdosErrorCode::ConfigTimeout.is_timeout());
551 assert!(OdosErrorCode::TxnAssemblyTimeout.is_timeout());
552 assert!(OdosErrorCode::ChainDataTimeout.is_timeout());
553 assert!(OdosErrorCode::PricingTimeout.is_timeout());
554 assert!(OdosErrorCode::GasTimeout.is_timeout());
555
556 assert!(!OdosErrorCode::AlgoInternal.is_timeout());
557 assert!(!OdosErrorCode::InvalidChainId.is_timeout());
558 }
559
560 #[test]
561 fn test_connection_error_detection() {
562 assert!(OdosErrorCode::AlgoConnectionError.is_connection_error());
563 assert!(OdosErrorCode::ConfigConnectionError.is_connection_error());
564 assert!(OdosErrorCode::GasConnectionError.is_connection_error());
565
566 assert!(!OdosErrorCode::AlgoInternal.is_connection_error());
567 assert!(!OdosErrorCode::InvalidChainId.is_connection_error());
568 }
569
570 #[test]
571 fn test_retryability() {
572 assert!(OdosErrorCode::AlgoTimeout.is_retryable());
574 assert!(OdosErrorCode::GasTimeout.is_retryable());
575
576 assert!(OdosErrorCode::AlgoConnectionError.is_retryable());
578 assert!(OdosErrorCode::PricingConnectionError.is_retryable());
579
580 assert!(OdosErrorCode::AlgoInternal.is_retryable());
582 assert!(OdosErrorCode::InternalServiceError.is_retryable());
583 assert!(OdosErrorCode::GasUnavailable.is_retryable());
584
585 assert!(!OdosErrorCode::InvalidChainId.is_retryable());
587 assert!(!OdosErrorCode::BlockedUserAddr.is_retryable());
588 assert!(!OdosErrorCode::InvalidTokenAmount.is_retryable());
589
590 assert!(!OdosErrorCode::NoViablePath.is_retryable());
592 }
593
594 #[test]
595 fn test_display_format() {
596 assert_eq!(
597 OdosErrorCode::AlgoInternal.to_string(),
598 "2999 (ALGO_INTERNAL)"
599 );
600 assert_eq!(
601 OdosErrorCode::InvalidChainId.to_string(),
602 "4001 (INVALID_CHAIN_ID)"
603 );
604 assert_eq!(OdosErrorCode::Unknown(9999).to_string(), "9999 (UNKNOWN)");
605 }
606}