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,
104
105 InternalServiceError,
108
109 ConfigInternal,
112 ConfigConnectionError,
114 ConfigTimeout,
116
117 TxnAssemblyInternal,
120 TxnAssemblyConnectionError,
122 TxnAssemblyTimeout,
124
125 ChainDataInternal,
128 ChainDataConnectionError,
130 ChainDataTimeout,
132
133 PricingInternal,
136 PricingConnectionError,
138 PricingTimeout,
140
141 GasInternal,
144 GasConnectionError,
146 GasTimeout,
148 GasUnavailable,
150
151 InvalidRequest,
154
155 InvalidChainId,
158 InvalidInputTokens,
160 InvalidOutputTokens,
162 InvalidUserAddr,
164 BlockedUserAddr,
166 TooSlippery,
168 SameInputOutput,
170 MultiZapOutput,
172 InvalidTokenCount,
174 InvalidTokenAddr,
176 NonIntegerTokenAmount,
178 NegativeTokenAmount,
180 SameInputOutputTokens,
182 TokenBlacklisted,
184 InvalidTokenProportions,
186 TokenRoutingUnavailable,
188 InvalidReferralCode,
190 InvalidTokenAmount,
192 NonStringTokenAmount,
194
195 InvalidAssemblyRequest,
198 InvalidAssemblyUserAddr,
200 InvalidReceiverAddr,
202
203 InvalidSwapRequest,
206 UserAddrRequired,
208
209 InternalError,
212 SwapUnavailable,
214 PriceCheckFailure,
216 DefaultGasFailure,
218
219 Unknown(u16),
221}
222
223impl OdosErrorCode {
224 pub fn code(&self) -> u16 {
226 match self {
227 Self::ApiError => 1000,
228 Self::NoViablePath => 2000,
229 Self::AlgoValidationError => 2400,
230 Self::AlgoConnectionError => 2997,
231 Self::AlgoTimeout => 2998,
232 Self::AlgoInternal => 2999,
233 Self::InternalServiceError => 3000,
234 Self::ConfigInternal => 3100,
235 Self::ConfigConnectionError => 3101,
236 Self::ConfigTimeout => 3102,
237 Self::TxnAssemblyInternal => 3110,
238 Self::TxnAssemblyConnectionError => 3111,
239 Self::TxnAssemblyTimeout => 3112,
240 Self::ChainDataInternal => 3120,
241 Self::ChainDataConnectionError => 3121,
242 Self::ChainDataTimeout => 3122,
243 Self::PricingInternal => 3130,
244 Self::PricingConnectionError => 3131,
245 Self::PricingTimeout => 3132,
246 Self::GasInternal => 3140,
247 Self::GasConnectionError => 3141,
248 Self::GasTimeout => 3142,
249 Self::GasUnavailable => 3143,
250 Self::InvalidRequest => 4000,
251 Self::InvalidChainId => 4001,
252 Self::InvalidInputTokens => 4002,
253 Self::InvalidOutputTokens => 4003,
254 Self::InvalidUserAddr => 4004,
255 Self::BlockedUserAddr => 4005,
256 Self::TooSlippery => 4006,
257 Self::SameInputOutput => 4007,
258 Self::MultiZapOutput => 4008,
259 Self::InvalidTokenCount => 4009,
260 Self::InvalidTokenAddr => 4010,
261 Self::NonIntegerTokenAmount => 4011,
262 Self::NegativeTokenAmount => 4012,
263 Self::SameInputOutputTokens => 4013,
264 Self::TokenBlacklisted => 4014,
265 Self::InvalidTokenProportions => 4015,
266 Self::TokenRoutingUnavailable => 4016,
267 Self::InvalidReferralCode => 4017,
268 Self::InvalidTokenAmount => 4018,
269 Self::NonStringTokenAmount => 4019,
270 Self::InvalidAssemblyRequest => 4100,
271 Self::InvalidAssemblyUserAddr => 4101,
272 Self::InvalidReceiverAddr => 4102,
273 Self::InvalidSwapRequest => 4200,
274 Self::UserAddrRequired => 4201,
275 Self::InternalError => 5000,
276 Self::SwapUnavailable => 5001,
277 Self::PriceCheckFailure => 5002,
278 Self::DefaultGasFailure => 5003,
279 Self::Unknown(code) => *code,
280 }
281 }
282
283 pub fn category(&self) -> ErrorCategory {
285 let code = self.code();
286 match code {
287 1000..=1999 => ErrorCategory::General,
288 2000..=2999 => ErrorCategory::Algo,
289 3000..=3999 => ErrorCategory::InternalService,
290 4000..=4999 => ErrorCategory::Validation,
291 5000..=5999 => ErrorCategory::Internal,
292 _ => ErrorCategory::Unknown,
293 }
294 }
295
296 pub fn is_general_error(&self) -> bool {
298 matches!(self.category(), ErrorCategory::General)
299 }
300
301 pub fn is_algo_error(&self) -> bool {
303 matches!(self.category(), ErrorCategory::Algo)
304 }
305
306 pub fn is_internal_service_error(&self) -> bool {
308 matches!(self.category(), ErrorCategory::InternalService)
309 }
310
311 pub fn is_validation_error(&self) -> bool {
313 matches!(self.category(), ErrorCategory::Validation)
314 }
315
316 pub fn is_internal_error(&self) -> bool {
318 matches!(self.category(), ErrorCategory::Internal)
319 }
320
321 pub fn is_no_viable_path(&self) -> bool {
323 matches!(self, Self::NoViablePath)
324 }
325
326 pub fn is_invalid_chain_id(&self) -> bool {
328 matches!(self, Self::InvalidChainId)
329 }
330
331 pub fn is_blocked_user(&self) -> bool {
333 matches!(self, Self::BlockedUserAddr)
334 }
335
336 pub fn is_timeout(&self) -> bool {
338 matches!(
339 self,
340 Self::AlgoTimeout
341 | Self::ConfigTimeout
342 | Self::TxnAssemblyTimeout
343 | Self::ChainDataTimeout
344 | Self::PricingTimeout
345 | Self::GasTimeout
346 )
347 }
348
349 pub fn is_connection_error(&self) -> bool {
351 matches!(
352 self,
353 Self::AlgoConnectionError
354 | Self::ConfigConnectionError
355 | Self::TxnAssemblyConnectionError
356 | Self::ChainDataConnectionError
357 | Self::PricingConnectionError
358 | Self::GasConnectionError
359 )
360 }
361
362 pub fn is_retryable(&self) -> bool {
381 self.is_timeout()
382 || self.is_connection_error()
383 || matches!(
384 self,
385 Self::ConfigInternal
386 | Self::TxnAssemblyInternal
387 | Self::ChainDataInternal
388 | Self::PricingInternal
389 | Self::GasInternal
390 | Self::GasUnavailable
391 | Self::InternalServiceError
392 | Self::InternalError
393 )
394 }
395
396 pub fn is_unroutable_token(&self) -> bool {
407 matches!(
408 self,
409 Self::TokenRoutingUnavailable
410 | Self::TokenBlacklisted
411 | Self::InvalidInputTokens
412 | Self::InvalidOutputTokens
413 | Self::NoViablePath
414 )
415 }
416}
417
418impl From<u16> for OdosErrorCode {
419 fn from(code: u16) -> Self {
420 match code {
421 1000 => Self::ApiError,
422 2000 => Self::NoViablePath,
423 2400 => Self::AlgoValidationError,
424 2997 => Self::AlgoConnectionError,
425 2998 => Self::AlgoTimeout,
426 2999 => Self::AlgoInternal,
427 3000 => Self::InternalServiceError,
428 3100 => Self::ConfigInternal,
429 3101 => Self::ConfigConnectionError,
430 3102 => Self::ConfigTimeout,
431 3110 => Self::TxnAssemblyInternal,
432 3111 => Self::TxnAssemblyConnectionError,
433 3112 => Self::TxnAssemblyTimeout,
434 3120 => Self::ChainDataInternal,
435 3121 => Self::ChainDataConnectionError,
436 3122 => Self::ChainDataTimeout,
437 3130 => Self::PricingInternal,
438 3131 => Self::PricingConnectionError,
439 3132 => Self::PricingTimeout,
440 3140 => Self::GasInternal,
441 3141 => Self::GasConnectionError,
442 3142 => Self::GasTimeout,
443 3143 => Self::GasUnavailable,
444 4000 => Self::InvalidRequest,
445 4001 => Self::InvalidChainId,
446 4002 => Self::InvalidInputTokens,
447 4003 => Self::InvalidOutputTokens,
448 4004 => Self::InvalidUserAddr,
449 4005 => Self::BlockedUserAddr,
450 4006 => Self::TooSlippery,
451 4007 => Self::SameInputOutput,
452 4008 => Self::MultiZapOutput,
453 4009 => Self::InvalidTokenCount,
454 4010 => Self::InvalidTokenAddr,
455 4011 => Self::NonIntegerTokenAmount,
456 4012 => Self::NegativeTokenAmount,
457 4013 => Self::SameInputOutputTokens,
458 4014 => Self::TokenBlacklisted,
459 4015 => Self::InvalidTokenProportions,
460 4016 => Self::TokenRoutingUnavailable,
461 4017 => Self::InvalidReferralCode,
462 4018 => Self::InvalidTokenAmount,
463 4019 => Self::NonStringTokenAmount,
464 4100 => Self::InvalidAssemblyRequest,
465 4101 => Self::InvalidAssemblyUserAddr,
466 4102 => Self::InvalidReceiverAddr,
467 4200 => Self::InvalidSwapRequest,
468 4201 => Self::UserAddrRequired,
469 5000 => Self::InternalError,
470 5001 => Self::SwapUnavailable,
471 5002 => Self::PriceCheckFailure,
472 5003 => Self::DefaultGasFailure,
473 _ => Self::Unknown(code),
474 }
475 }
476}
477
478impl From<OdosErrorCode> for u16 {
479 fn from(error_code: OdosErrorCode) -> Self {
480 error_code.code()
481 }
482}
483
484impl fmt::Display for OdosErrorCode {
485 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
486 match self {
487 Self::ApiError => write!(f, "1000 (API_ERROR)"),
488 Self::NoViablePath => write!(f, "2000 (NO_VIABLE_PATH)"),
489 Self::AlgoValidationError => write!(f, "2400 (ALGO_VALIDATION_ERR)"),
490 Self::AlgoConnectionError => write!(f, "2997 (ALGO_CONN_ERR)"),
491 Self::AlgoTimeout => write!(f, "2998 (ALGO_TIMEOUT)"),
492 Self::AlgoInternal => write!(f, "2999 (ALGO_INTERNAL)"),
493 Self::InternalServiceError => write!(f, "3000 (INTERNAL_SERVICE_ERROR)"),
494 Self::ConfigInternal => write!(f, "3100 (CONFIG_INTERNAL)"),
495 Self::ConfigConnectionError => write!(f, "3101 (CONFIG_CONN_ERR)"),
496 Self::ConfigTimeout => write!(f, "3102 (CONFIG_TIMEOUT)"),
497 Self::TxnAssemblyInternal => write!(f, "3110 (TXN_ASSEMBLY_INTERNAL)"),
498 Self::TxnAssemblyConnectionError => write!(f, "3111 (TXN_ASSEMBLY_CONN_ERR)"),
499 Self::TxnAssemblyTimeout => write!(f, "3112 (TXN_ASSEMBLY_TIMEOUT)"),
500 Self::ChainDataInternal => write!(f, "3120 (CHAIN_DATA_INTERNAL)"),
501 Self::ChainDataConnectionError => write!(f, "3121 (CHAIN_DATA_CONN_ERR)"),
502 Self::ChainDataTimeout => write!(f, "3122 (CHAIN_DATA_TIMEOUT)"),
503 Self::PricingInternal => write!(f, "3130 (PRICING_INTERNAL)"),
504 Self::PricingConnectionError => write!(f, "3131 (PRICING_CONN_ERR)"),
505 Self::PricingTimeout => write!(f, "3132 (PRICING_TIMEOUT)"),
506 Self::GasInternal => write!(f, "3140 (GAS_INTERNAL)"),
507 Self::GasConnectionError => write!(f, "3141 (GAS_CONN_ERR)"),
508 Self::GasTimeout => write!(f, "3142 (GAS_TIMEOUT)"),
509 Self::GasUnavailable => write!(f, "3143 (GAS_UNAVAILABLE)"),
510 Self::InvalidRequest => write!(f, "4000 (INVALID_REQUEST)"),
511 Self::InvalidChainId => write!(f, "4001 (INVALID_CHAIN_ID)"),
512 Self::InvalidInputTokens => write!(f, "4002 (INVALID_INPUT_TOKENS)"),
513 Self::InvalidOutputTokens => write!(f, "4003 (INVALID_OUTPUT_TOKENS)"),
514 Self::InvalidUserAddr => write!(f, "4004 (INVALID_USER_ADDR)"),
515 Self::BlockedUserAddr => write!(f, "4005 (BLOCKED_USER_ADDR)"),
516 Self::TooSlippery => write!(f, "4006 (TOO_SLIPPERY)"),
517 Self::SameInputOutput => write!(f, "4007 (SAME_INPUT_OUTPUT)"),
518 Self::MultiZapOutput => write!(f, "4008 (MULTI_ZAP_OUTPUT)"),
519 Self::InvalidTokenCount => write!(f, "4009 (INVALID_TOKEN_COUNT)"),
520 Self::InvalidTokenAddr => write!(f, "4010 (INVALID_TOKEN_ADDR)"),
521 Self::NonIntegerTokenAmount => write!(f, "4011 (NON_INTEGER_TOKEN_AMOUNT)"),
522 Self::NegativeTokenAmount => write!(f, "4012 (NEGATIVE_TOKEN_AMOUNT)"),
523 Self::SameInputOutputTokens => write!(f, "4013 (SAME_INPUT_OUTPUT_TOKENS)"),
524 Self::TokenBlacklisted => write!(f, "4014 (TOKEN_BLACKLISTED)"),
525 Self::InvalidTokenProportions => write!(f, "4015 (INVALID_TOKEN_PROPORTIONS)"),
526 Self::TokenRoutingUnavailable => write!(f, "4016 (TOKEN_ROUTING_UNAVAILABLE)"),
527 Self::InvalidReferralCode => write!(f, "4017 (INVALID_REFERRAL_CODE)"),
528 Self::InvalidTokenAmount => write!(f, "4018 (INVALID_TOKEN_AMOUNT)"),
529 Self::NonStringTokenAmount => write!(f, "4019 (NON_STRING_TOKEN_AMOUNT)"),
530 Self::InvalidAssemblyRequest => write!(f, "4100 (INVALID_ASSEMBLY_REQUEST)"),
531 Self::InvalidAssemblyUserAddr => write!(f, "4101 (INVALID_USER_ADDR)"),
532 Self::InvalidReceiverAddr => write!(f, "4102 (INVALID_RECEIVER_ADDR)"),
533 Self::InvalidSwapRequest => write!(f, "4200 (INVALID_SWAP_REQUEST)"),
534 Self::UserAddrRequired => write!(f, "4201 (USER_ADDR_REQ)"),
535 Self::InternalError => write!(f, "5000 (INTERNAL_ERROR)"),
536 Self::SwapUnavailable => write!(f, "5001 (SWAP_UNAVAILABLE)"),
537 Self::PriceCheckFailure => write!(f, "5002 (PRICE_CHECK_FAILURE)"),
538 Self::DefaultGasFailure => write!(f, "5003 (DEFAULT_GAS_FAILURE)"),
539 Self::Unknown(code) => write!(f, "{code} (UNKNOWN)"),
540 }
541 }
542}
543
544#[cfg(test)]
545mod tests {
546 use super::*;
547
548 #[test]
549 fn test_trace_id_creation() {
550 let uuid = Uuid::parse_str("10becdc8-a021-4491-8201-a17b657204e0").unwrap();
551 let trace_id = TraceId::new(uuid);
552 assert_eq!(trace_id.as_uuid(), uuid);
553 assert_eq!(trace_id.to_string(), "10becdc8-a021-4491-8201-a17b657204e0");
554 }
555
556 #[test]
557 fn test_error_code_from_u16() {
558 assert_eq!(OdosErrorCode::from(2999), OdosErrorCode::AlgoInternal);
559 assert_eq!(OdosErrorCode::from(4001), OdosErrorCode::InvalidChainId);
560 assert_eq!(OdosErrorCode::from(3142), OdosErrorCode::GasTimeout);
561 assert_eq!(OdosErrorCode::from(9999), OdosErrorCode::Unknown(9999));
562 }
563
564 #[test]
565 fn test_error_code_to_u16() {
566 assert_eq!(OdosErrorCode::AlgoInternal.code(), 2999);
567 assert_eq!(OdosErrorCode::InvalidChainId.code(), 4001);
568 assert_eq!(OdosErrorCode::Unknown(9999).code(), 9999);
569 }
570
571 #[test]
572 fn test_error_categories() {
573 assert!(OdosErrorCode::ApiError.is_general_error());
574 assert!(OdosErrorCode::NoViablePath.is_algo_error());
575 assert!(OdosErrorCode::ConfigInternal.is_internal_service_error());
576 assert!(OdosErrorCode::InvalidChainId.is_validation_error());
577 assert!(OdosErrorCode::InternalError.is_internal_error());
578 }
579
580 #[test]
581 fn test_specific_error_checks() {
582 assert!(OdosErrorCode::NoViablePath.is_no_viable_path());
583 assert!(OdosErrorCode::InvalidChainId.is_invalid_chain_id());
584 assert!(OdosErrorCode::BlockedUserAddr.is_blocked_user());
585
586 assert!(!OdosErrorCode::ApiError.is_no_viable_path());
587 assert!(!OdosErrorCode::AlgoInternal.is_invalid_chain_id());
588 }
589
590 #[test]
591 fn test_timeout_detection() {
592 assert!(OdosErrorCode::AlgoTimeout.is_timeout());
593 assert!(OdosErrorCode::ConfigTimeout.is_timeout());
594 assert!(OdosErrorCode::TxnAssemblyTimeout.is_timeout());
595 assert!(OdosErrorCode::ChainDataTimeout.is_timeout());
596 assert!(OdosErrorCode::PricingTimeout.is_timeout());
597 assert!(OdosErrorCode::GasTimeout.is_timeout());
598
599 assert!(!OdosErrorCode::AlgoInternal.is_timeout());
600 assert!(!OdosErrorCode::InvalidChainId.is_timeout());
601 }
602
603 #[test]
604 fn test_connection_error_detection() {
605 assert!(OdosErrorCode::AlgoConnectionError.is_connection_error());
606 assert!(OdosErrorCode::ConfigConnectionError.is_connection_error());
607 assert!(OdosErrorCode::GasConnectionError.is_connection_error());
608
609 assert!(!OdosErrorCode::AlgoInternal.is_connection_error());
610 assert!(!OdosErrorCode::InvalidChainId.is_connection_error());
611 }
612
613 #[test]
614 fn test_retryability() {
615 assert!(OdosErrorCode::AlgoTimeout.is_retryable());
617 assert!(OdosErrorCode::GasTimeout.is_retryable());
618
619 assert!(OdosErrorCode::AlgoConnectionError.is_retryable());
621 assert!(OdosErrorCode::PricingConnectionError.is_retryable());
622
623 assert!(OdosErrorCode::InternalServiceError.is_retryable());
625 assert!(OdosErrorCode::GasUnavailable.is_retryable());
626
627 assert!(!OdosErrorCode::AlgoInternal.is_retryable());
630
631 assert!(!OdosErrorCode::InvalidChainId.is_retryable());
633 assert!(!OdosErrorCode::BlockedUserAddr.is_retryable());
634 assert!(!OdosErrorCode::InvalidTokenAmount.is_retryable());
635
636 assert!(!OdosErrorCode::NoViablePath.is_retryable());
638 }
639
640 #[test]
641 fn test_display_format() {
642 assert_eq!(
643 OdosErrorCode::AlgoInternal.to_string(),
644 "2999 (ALGO_INTERNAL)"
645 );
646 assert_eq!(
647 OdosErrorCode::InvalidChainId.to_string(),
648 "4001 (INVALID_CHAIN_ID)"
649 );
650 assert_eq!(OdosErrorCode::Unknown(9999).to_string(), "9999 (UNKNOWN)");
651 }
652
653 #[test]
654 fn test_unroutable_token_detection() {
655 assert!(OdosErrorCode::NoViablePath.is_unroutable_token());
657 assert!(OdosErrorCode::TokenRoutingUnavailable.is_unroutable_token());
658 assert!(OdosErrorCode::TokenBlacklisted.is_unroutable_token());
659 assert!(OdosErrorCode::InvalidInputTokens.is_unroutable_token());
660 assert!(OdosErrorCode::InvalidOutputTokens.is_unroutable_token());
661
662 assert!(!OdosErrorCode::AlgoTimeout.is_unroutable_token());
664 assert!(!OdosErrorCode::InternalError.is_unroutable_token());
665 assert!(!OdosErrorCode::InvalidChainId.is_unroutable_token());
666 assert!(!OdosErrorCode::BlockedUserAddr.is_unroutable_token());
667 }
668}