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 pub fn is_unroutable_token(&self) -> bool {
385 matches!(
386 self,
387 Self::TokenRoutingUnavailable
388 | Self::TokenBlacklisted
389 | Self::InvalidInputTokens
390 | Self::InvalidOutputTokens
391 | Self::NoViablePath
392 )
393 }
394}
395
396impl From<u16> for OdosErrorCode {
397 fn from(code: u16) -> Self {
398 match code {
399 1000 => Self::ApiError,
400 2000 => Self::NoViablePath,
401 2400 => Self::AlgoValidationError,
402 2997 => Self::AlgoConnectionError,
403 2998 => Self::AlgoTimeout,
404 2999 => Self::AlgoInternal,
405 3000 => Self::InternalServiceError,
406 3100 => Self::ConfigInternal,
407 3101 => Self::ConfigConnectionError,
408 3102 => Self::ConfigTimeout,
409 3110 => Self::TxnAssemblyInternal,
410 3111 => Self::TxnAssemblyConnectionError,
411 3112 => Self::TxnAssemblyTimeout,
412 3120 => Self::ChainDataInternal,
413 3121 => Self::ChainDataConnectionError,
414 3122 => Self::ChainDataTimeout,
415 3130 => Self::PricingInternal,
416 3131 => Self::PricingConnectionError,
417 3132 => Self::PricingTimeout,
418 3140 => Self::GasInternal,
419 3141 => Self::GasConnectionError,
420 3142 => Self::GasTimeout,
421 3143 => Self::GasUnavailable,
422 4000 => Self::InvalidRequest,
423 4001 => Self::InvalidChainId,
424 4002 => Self::InvalidInputTokens,
425 4003 => Self::InvalidOutputTokens,
426 4004 => Self::InvalidUserAddr,
427 4005 => Self::BlockedUserAddr,
428 4006 => Self::TooSlippery,
429 4007 => Self::SameInputOutput,
430 4008 => Self::MultiZapOutput,
431 4009 => Self::InvalidTokenCount,
432 4010 => Self::InvalidTokenAddr,
433 4011 => Self::NonIntegerTokenAmount,
434 4012 => Self::NegativeTokenAmount,
435 4013 => Self::SameInputOutputTokens,
436 4014 => Self::TokenBlacklisted,
437 4015 => Self::InvalidTokenProportions,
438 4016 => Self::TokenRoutingUnavailable,
439 4017 => Self::InvalidReferralCode,
440 4018 => Self::InvalidTokenAmount,
441 4019 => Self::NonStringTokenAmount,
442 4100 => Self::InvalidAssemblyRequest,
443 4101 => Self::InvalidAssemblyUserAddr,
444 4102 => Self::InvalidReceiverAddr,
445 4200 => Self::InvalidSwapRequest,
446 4201 => Self::UserAddrRequired,
447 5000 => Self::InternalError,
448 5001 => Self::SwapUnavailable,
449 5002 => Self::PriceCheckFailure,
450 5003 => Self::DefaultGasFailure,
451 _ => Self::Unknown(code),
452 }
453 }
454}
455
456impl From<OdosErrorCode> for u16 {
457 fn from(error_code: OdosErrorCode) -> Self {
458 error_code.code()
459 }
460}
461
462impl fmt::Display for OdosErrorCode {
463 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
464 match self {
465 Self::ApiError => write!(f, "1000 (API_ERROR)"),
466 Self::NoViablePath => write!(f, "2000 (NO_VIABLE_PATH)"),
467 Self::AlgoValidationError => write!(f, "2400 (ALGO_VALIDATION_ERR)"),
468 Self::AlgoConnectionError => write!(f, "2997 (ALGO_CONN_ERR)"),
469 Self::AlgoTimeout => write!(f, "2998 (ALGO_TIMEOUT)"),
470 Self::AlgoInternal => write!(f, "2999 (ALGO_INTERNAL)"),
471 Self::InternalServiceError => write!(f, "3000 (INTERNAL_SERVICE_ERROR)"),
472 Self::ConfigInternal => write!(f, "3100 (CONFIG_INTERNAL)"),
473 Self::ConfigConnectionError => write!(f, "3101 (CONFIG_CONN_ERR)"),
474 Self::ConfigTimeout => write!(f, "3102 (CONFIG_TIMEOUT)"),
475 Self::TxnAssemblyInternal => write!(f, "3110 (TXN_ASSEMBLY_INTERNAL)"),
476 Self::TxnAssemblyConnectionError => write!(f, "3111 (TXN_ASSEMBLY_CONN_ERR)"),
477 Self::TxnAssemblyTimeout => write!(f, "3112 (TXN_ASSEMBLY_TIMEOUT)"),
478 Self::ChainDataInternal => write!(f, "3120 (CHAIN_DATA_INTERNAL)"),
479 Self::ChainDataConnectionError => write!(f, "3121 (CHAIN_DATA_CONN_ERR)"),
480 Self::ChainDataTimeout => write!(f, "3122 (CHAIN_DATA_TIMEOUT)"),
481 Self::PricingInternal => write!(f, "3130 (PRICING_INTERNAL)"),
482 Self::PricingConnectionError => write!(f, "3131 (PRICING_CONN_ERR)"),
483 Self::PricingTimeout => write!(f, "3132 (PRICING_TIMEOUT)"),
484 Self::GasInternal => write!(f, "3140 (GAS_INTERNAL)"),
485 Self::GasConnectionError => write!(f, "3141 (GAS_CONN_ERR)"),
486 Self::GasTimeout => write!(f, "3142 (GAS_TIMEOUT)"),
487 Self::GasUnavailable => write!(f, "3143 (GAS_UNAVAILABLE)"),
488 Self::InvalidRequest => write!(f, "4000 (INVALID_REQUEST)"),
489 Self::InvalidChainId => write!(f, "4001 (INVALID_CHAIN_ID)"),
490 Self::InvalidInputTokens => write!(f, "4002 (INVALID_INPUT_TOKENS)"),
491 Self::InvalidOutputTokens => write!(f, "4003 (INVALID_OUTPUT_TOKENS)"),
492 Self::InvalidUserAddr => write!(f, "4004 (INVALID_USER_ADDR)"),
493 Self::BlockedUserAddr => write!(f, "4005 (BLOCKED_USER_ADDR)"),
494 Self::TooSlippery => write!(f, "4006 (TOO_SLIPPERY)"),
495 Self::SameInputOutput => write!(f, "4007 (SAME_INPUT_OUTPUT)"),
496 Self::MultiZapOutput => write!(f, "4008 (MULTI_ZAP_OUTPUT)"),
497 Self::InvalidTokenCount => write!(f, "4009 (INVALID_TOKEN_COUNT)"),
498 Self::InvalidTokenAddr => write!(f, "4010 (INVALID_TOKEN_ADDR)"),
499 Self::NonIntegerTokenAmount => write!(f, "4011 (NON_INTEGER_TOKEN_AMOUNT)"),
500 Self::NegativeTokenAmount => write!(f, "4012 (NEGATIVE_TOKEN_AMOUNT)"),
501 Self::SameInputOutputTokens => write!(f, "4013 (SAME_INPUT_OUTPUT_TOKENS)"),
502 Self::TokenBlacklisted => write!(f, "4014 (TOKEN_BLACKLISTED)"),
503 Self::InvalidTokenProportions => write!(f, "4015 (INVALID_TOKEN_PROPORTIONS)"),
504 Self::TokenRoutingUnavailable => write!(f, "4016 (TOKEN_ROUTING_UNAVAILABLE)"),
505 Self::InvalidReferralCode => write!(f, "4017 (INVALID_REFERRAL_CODE)"),
506 Self::InvalidTokenAmount => write!(f, "4018 (INVALID_TOKEN_AMOUNT)"),
507 Self::NonStringTokenAmount => write!(f, "4019 (NON_STRING_TOKEN_AMOUNT)"),
508 Self::InvalidAssemblyRequest => write!(f, "4100 (INVALID_ASSEMBLY_REQUEST)"),
509 Self::InvalidAssemblyUserAddr => write!(f, "4101 (INVALID_USER_ADDR)"),
510 Self::InvalidReceiverAddr => write!(f, "4102 (INVALID_RECEIVER_ADDR)"),
511 Self::InvalidSwapRequest => write!(f, "4200 (INVALID_SWAP_REQUEST)"),
512 Self::UserAddrRequired => write!(f, "4201 (USER_ADDR_REQ)"),
513 Self::InternalError => write!(f, "5000 (INTERNAL_ERROR)"),
514 Self::SwapUnavailable => write!(f, "5001 (SWAP_UNAVAILABLE)"),
515 Self::PriceCheckFailure => write!(f, "5002 (PRICE_CHECK_FAILURE)"),
516 Self::DefaultGasFailure => write!(f, "5003 (DEFAULT_GAS_FAILURE)"),
517 Self::Unknown(code) => write!(f, "{code} (UNKNOWN)"),
518 }
519 }
520}
521
522#[cfg(test)]
523mod tests {
524 use super::*;
525
526 #[test]
527 fn test_trace_id_creation() {
528 let uuid = Uuid::parse_str("10becdc8-a021-4491-8201-a17b657204e0").unwrap();
529 let trace_id = TraceId::new(uuid);
530 assert_eq!(trace_id.as_uuid(), uuid);
531 assert_eq!(trace_id.to_string(), "10becdc8-a021-4491-8201-a17b657204e0");
532 }
533
534 #[test]
535 fn test_error_code_from_u16() {
536 assert_eq!(OdosErrorCode::from(2999), OdosErrorCode::AlgoInternal);
537 assert_eq!(OdosErrorCode::from(4001), OdosErrorCode::InvalidChainId);
538 assert_eq!(OdosErrorCode::from(3142), OdosErrorCode::GasTimeout);
539 assert_eq!(OdosErrorCode::from(9999), OdosErrorCode::Unknown(9999));
540 }
541
542 #[test]
543 fn test_error_code_to_u16() {
544 assert_eq!(OdosErrorCode::AlgoInternal.code(), 2999);
545 assert_eq!(OdosErrorCode::InvalidChainId.code(), 4001);
546 assert_eq!(OdosErrorCode::Unknown(9999).code(), 9999);
547 }
548
549 #[test]
550 fn test_error_categories() {
551 assert!(OdosErrorCode::ApiError.is_general_error());
552 assert!(OdosErrorCode::NoViablePath.is_algo_error());
553 assert!(OdosErrorCode::ConfigInternal.is_internal_service_error());
554 assert!(OdosErrorCode::InvalidChainId.is_validation_error());
555 assert!(OdosErrorCode::InternalError.is_internal_error());
556 }
557
558 #[test]
559 fn test_specific_error_checks() {
560 assert!(OdosErrorCode::NoViablePath.is_no_viable_path());
561 assert!(OdosErrorCode::InvalidChainId.is_invalid_chain_id());
562 assert!(OdosErrorCode::BlockedUserAddr.is_blocked_user());
563
564 assert!(!OdosErrorCode::ApiError.is_no_viable_path());
565 assert!(!OdosErrorCode::AlgoInternal.is_invalid_chain_id());
566 }
567
568 #[test]
569 fn test_timeout_detection() {
570 assert!(OdosErrorCode::AlgoTimeout.is_timeout());
571 assert!(OdosErrorCode::ConfigTimeout.is_timeout());
572 assert!(OdosErrorCode::TxnAssemblyTimeout.is_timeout());
573 assert!(OdosErrorCode::ChainDataTimeout.is_timeout());
574 assert!(OdosErrorCode::PricingTimeout.is_timeout());
575 assert!(OdosErrorCode::GasTimeout.is_timeout());
576
577 assert!(!OdosErrorCode::AlgoInternal.is_timeout());
578 assert!(!OdosErrorCode::InvalidChainId.is_timeout());
579 }
580
581 #[test]
582 fn test_connection_error_detection() {
583 assert!(OdosErrorCode::AlgoConnectionError.is_connection_error());
584 assert!(OdosErrorCode::ConfigConnectionError.is_connection_error());
585 assert!(OdosErrorCode::GasConnectionError.is_connection_error());
586
587 assert!(!OdosErrorCode::AlgoInternal.is_connection_error());
588 assert!(!OdosErrorCode::InvalidChainId.is_connection_error());
589 }
590
591 #[test]
592 fn test_retryability() {
593 assert!(OdosErrorCode::AlgoTimeout.is_retryable());
595 assert!(OdosErrorCode::GasTimeout.is_retryable());
596
597 assert!(OdosErrorCode::AlgoConnectionError.is_retryable());
599 assert!(OdosErrorCode::PricingConnectionError.is_retryable());
600
601 assert!(OdosErrorCode::AlgoInternal.is_retryable());
603 assert!(OdosErrorCode::InternalServiceError.is_retryable());
604 assert!(OdosErrorCode::GasUnavailable.is_retryable());
605
606 assert!(!OdosErrorCode::InvalidChainId.is_retryable());
608 assert!(!OdosErrorCode::BlockedUserAddr.is_retryable());
609 assert!(!OdosErrorCode::InvalidTokenAmount.is_retryable());
610
611 assert!(!OdosErrorCode::NoViablePath.is_retryable());
613 }
614
615 #[test]
616 fn test_display_format() {
617 assert_eq!(
618 OdosErrorCode::AlgoInternal.to_string(),
619 "2999 (ALGO_INTERNAL)"
620 );
621 assert_eq!(
622 OdosErrorCode::InvalidChainId.to_string(),
623 "4001 (INVALID_CHAIN_ID)"
624 );
625 assert_eq!(OdosErrorCode::Unknown(9999).to_string(), "9999 (UNKNOWN)");
626 }
627
628 #[test]
629 fn test_unroutable_token_detection() {
630 assert!(OdosErrorCode::NoViablePath.is_unroutable_token());
632 assert!(OdosErrorCode::TokenRoutingUnavailable.is_unroutable_token());
633 assert!(OdosErrorCode::TokenBlacklisted.is_unroutable_token());
634 assert!(OdosErrorCode::InvalidInputTokens.is_unroutable_token());
635 assert!(OdosErrorCode::InvalidOutputTokens.is_unroutable_token());
636
637 assert!(!OdosErrorCode::AlgoTimeout.is_unroutable_token());
639 assert!(!OdosErrorCode::InternalError.is_unroutable_token());
640 assert!(!OdosErrorCode::InvalidChainId.is_unroutable_token());
641 assert!(!OdosErrorCode::BlockedUserAddr.is_unroutable_token());
642 }
643}