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