use serde::{Deserialize, Serialize};
use std::fmt;
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct TraceId(pub Uuid);
impl TraceId {
pub fn new(uuid: Uuid) -> Self {
Self(uuid)
}
pub fn as_uuid(&self) -> Uuid {
self.0
}
}
impl fmt::Display for TraceId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<Uuid> for TraceId {
fn from(uuid: Uuid) -> Self {
Self(uuid)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorCategory {
General,
Algo,
InternalService,
Validation,
Internal,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OdosErrorCode {
ApiError,
NoViablePath,
AlgoValidationError,
AlgoConnectionError,
AlgoTimeout,
AlgoInternal,
InternalServiceError,
ConfigInternal,
ConfigConnectionError,
ConfigTimeout,
TxnAssemblyInternal,
TxnAssemblyConnectionError,
TxnAssemblyTimeout,
ChainDataInternal,
ChainDataConnectionError,
ChainDataTimeout,
PricingInternal,
PricingConnectionError,
PricingTimeout,
GasInternal,
GasConnectionError,
GasTimeout,
GasUnavailable,
InvalidRequest,
InvalidChainId,
InvalidInputTokens,
InvalidOutputTokens,
InvalidUserAddr,
BlockedUserAddr,
TooSlippery,
SameInputOutput,
MultiZapOutput,
InvalidTokenCount,
InvalidTokenAddr,
NonIntegerTokenAmount,
NegativeTokenAmount,
SameInputOutputTokens,
TokenBlacklisted,
InvalidTokenProportions,
TokenRoutingUnavailable,
InvalidReferralCode,
InvalidTokenAmount,
NonStringTokenAmount,
InvalidAssemblyRequest,
InvalidAssemblyUserAddr,
InvalidReceiverAddr,
InvalidSwapRequest,
UserAddrRequired,
InternalError,
SwapUnavailable,
PriceCheckFailure,
DefaultGasFailure,
Unknown(u16),
}
impl OdosErrorCode {
pub fn code(&self) -> u16 {
match self {
Self::ApiError => 1000,
Self::NoViablePath => 2000,
Self::AlgoValidationError => 2400,
Self::AlgoConnectionError => 2997,
Self::AlgoTimeout => 2998,
Self::AlgoInternal => 2999,
Self::InternalServiceError => 3000,
Self::ConfigInternal => 3100,
Self::ConfigConnectionError => 3101,
Self::ConfigTimeout => 3102,
Self::TxnAssemblyInternal => 3110,
Self::TxnAssemblyConnectionError => 3111,
Self::TxnAssemblyTimeout => 3112,
Self::ChainDataInternal => 3120,
Self::ChainDataConnectionError => 3121,
Self::ChainDataTimeout => 3122,
Self::PricingInternal => 3130,
Self::PricingConnectionError => 3131,
Self::PricingTimeout => 3132,
Self::GasInternal => 3140,
Self::GasConnectionError => 3141,
Self::GasTimeout => 3142,
Self::GasUnavailable => 3143,
Self::InvalidRequest => 4000,
Self::InvalidChainId => 4001,
Self::InvalidInputTokens => 4002,
Self::InvalidOutputTokens => 4003,
Self::InvalidUserAddr => 4004,
Self::BlockedUserAddr => 4005,
Self::TooSlippery => 4006,
Self::SameInputOutput => 4007,
Self::MultiZapOutput => 4008,
Self::InvalidTokenCount => 4009,
Self::InvalidTokenAddr => 4010,
Self::NonIntegerTokenAmount => 4011,
Self::NegativeTokenAmount => 4012,
Self::SameInputOutputTokens => 4013,
Self::TokenBlacklisted => 4014,
Self::InvalidTokenProportions => 4015,
Self::TokenRoutingUnavailable => 4016,
Self::InvalidReferralCode => 4017,
Self::InvalidTokenAmount => 4018,
Self::NonStringTokenAmount => 4019,
Self::InvalidAssemblyRequest => 4100,
Self::InvalidAssemblyUserAddr => 4101,
Self::InvalidReceiverAddr => 4102,
Self::InvalidSwapRequest => 4200,
Self::UserAddrRequired => 4201,
Self::InternalError => 5000,
Self::SwapUnavailable => 5001,
Self::PriceCheckFailure => 5002,
Self::DefaultGasFailure => 5003,
Self::Unknown(code) => *code,
}
}
pub fn category(&self) -> ErrorCategory {
let code = self.code();
match code {
1000..=1999 => ErrorCategory::General,
2000..=2999 => ErrorCategory::Algo,
3000..=3999 => ErrorCategory::InternalService,
4000..=4999 => ErrorCategory::Validation,
5000..=5999 => ErrorCategory::Internal,
_ => ErrorCategory::Unknown,
}
}
pub fn is_general_error(&self) -> bool {
matches!(self.category(), ErrorCategory::General)
}
pub fn is_algo_error(&self) -> bool {
matches!(self.category(), ErrorCategory::Algo)
}
pub fn is_internal_service_error(&self) -> bool {
matches!(self.category(), ErrorCategory::InternalService)
}
pub fn is_validation_error(&self) -> bool {
matches!(self.category(), ErrorCategory::Validation)
}
pub fn is_internal_error(&self) -> bool {
matches!(self.category(), ErrorCategory::Internal)
}
pub fn is_no_viable_path(&self) -> bool {
matches!(self, Self::NoViablePath)
}
pub fn is_invalid_chain_id(&self) -> bool {
matches!(self, Self::InvalidChainId)
}
pub fn is_blocked_user(&self) -> bool {
matches!(self, Self::BlockedUserAddr)
}
pub fn is_timeout(&self) -> bool {
matches!(
self,
Self::AlgoTimeout
| Self::ConfigTimeout
| Self::TxnAssemblyTimeout
| Self::ChainDataTimeout
| Self::PricingTimeout
| Self::GasTimeout
)
}
pub fn is_connection_error(&self) -> bool {
matches!(
self,
Self::AlgoConnectionError
| Self::ConfigConnectionError
| Self::TxnAssemblyConnectionError
| Self::ChainDataConnectionError
| Self::PricingConnectionError
| Self::GasConnectionError
)
}
pub fn is_retryable(&self) -> bool {
self.is_timeout()
|| self.is_connection_error()
|| matches!(
self,
Self::AlgoInternal
| Self::ConfigInternal
| Self::TxnAssemblyInternal
| Self::ChainDataInternal
| Self::PricingInternal
| Self::GasInternal
| Self::GasUnavailable
| Self::InternalServiceError
| Self::InternalError
)
}
pub fn is_unroutable_token(&self) -> bool {
matches!(
self,
Self::TokenRoutingUnavailable
| Self::TokenBlacklisted
| Self::InvalidInputTokens
| Self::InvalidOutputTokens
| Self::NoViablePath
)
}
}
impl From<u16> for OdosErrorCode {
fn from(code: u16) -> Self {
match code {
1000 => Self::ApiError,
2000 => Self::NoViablePath,
2400 => Self::AlgoValidationError,
2997 => Self::AlgoConnectionError,
2998 => Self::AlgoTimeout,
2999 => Self::AlgoInternal,
3000 => Self::InternalServiceError,
3100 => Self::ConfigInternal,
3101 => Self::ConfigConnectionError,
3102 => Self::ConfigTimeout,
3110 => Self::TxnAssemblyInternal,
3111 => Self::TxnAssemblyConnectionError,
3112 => Self::TxnAssemblyTimeout,
3120 => Self::ChainDataInternal,
3121 => Self::ChainDataConnectionError,
3122 => Self::ChainDataTimeout,
3130 => Self::PricingInternal,
3131 => Self::PricingConnectionError,
3132 => Self::PricingTimeout,
3140 => Self::GasInternal,
3141 => Self::GasConnectionError,
3142 => Self::GasTimeout,
3143 => Self::GasUnavailable,
4000 => Self::InvalidRequest,
4001 => Self::InvalidChainId,
4002 => Self::InvalidInputTokens,
4003 => Self::InvalidOutputTokens,
4004 => Self::InvalidUserAddr,
4005 => Self::BlockedUserAddr,
4006 => Self::TooSlippery,
4007 => Self::SameInputOutput,
4008 => Self::MultiZapOutput,
4009 => Self::InvalidTokenCount,
4010 => Self::InvalidTokenAddr,
4011 => Self::NonIntegerTokenAmount,
4012 => Self::NegativeTokenAmount,
4013 => Self::SameInputOutputTokens,
4014 => Self::TokenBlacklisted,
4015 => Self::InvalidTokenProportions,
4016 => Self::TokenRoutingUnavailable,
4017 => Self::InvalidReferralCode,
4018 => Self::InvalidTokenAmount,
4019 => Self::NonStringTokenAmount,
4100 => Self::InvalidAssemblyRequest,
4101 => Self::InvalidAssemblyUserAddr,
4102 => Self::InvalidReceiverAddr,
4200 => Self::InvalidSwapRequest,
4201 => Self::UserAddrRequired,
5000 => Self::InternalError,
5001 => Self::SwapUnavailable,
5002 => Self::PriceCheckFailure,
5003 => Self::DefaultGasFailure,
_ => Self::Unknown(code),
}
}
}
impl From<OdosErrorCode> for u16 {
fn from(error_code: OdosErrorCode) -> Self {
error_code.code()
}
}
impl fmt::Display for OdosErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ApiError => write!(f, "1000 (API_ERROR)"),
Self::NoViablePath => write!(f, "2000 (NO_VIABLE_PATH)"),
Self::AlgoValidationError => write!(f, "2400 (ALGO_VALIDATION_ERR)"),
Self::AlgoConnectionError => write!(f, "2997 (ALGO_CONN_ERR)"),
Self::AlgoTimeout => write!(f, "2998 (ALGO_TIMEOUT)"),
Self::AlgoInternal => write!(f, "2999 (ALGO_INTERNAL)"),
Self::InternalServiceError => write!(f, "3000 (INTERNAL_SERVICE_ERROR)"),
Self::ConfigInternal => write!(f, "3100 (CONFIG_INTERNAL)"),
Self::ConfigConnectionError => write!(f, "3101 (CONFIG_CONN_ERR)"),
Self::ConfigTimeout => write!(f, "3102 (CONFIG_TIMEOUT)"),
Self::TxnAssemblyInternal => write!(f, "3110 (TXN_ASSEMBLY_INTERNAL)"),
Self::TxnAssemblyConnectionError => write!(f, "3111 (TXN_ASSEMBLY_CONN_ERR)"),
Self::TxnAssemblyTimeout => write!(f, "3112 (TXN_ASSEMBLY_TIMEOUT)"),
Self::ChainDataInternal => write!(f, "3120 (CHAIN_DATA_INTERNAL)"),
Self::ChainDataConnectionError => write!(f, "3121 (CHAIN_DATA_CONN_ERR)"),
Self::ChainDataTimeout => write!(f, "3122 (CHAIN_DATA_TIMEOUT)"),
Self::PricingInternal => write!(f, "3130 (PRICING_INTERNAL)"),
Self::PricingConnectionError => write!(f, "3131 (PRICING_CONN_ERR)"),
Self::PricingTimeout => write!(f, "3132 (PRICING_TIMEOUT)"),
Self::GasInternal => write!(f, "3140 (GAS_INTERNAL)"),
Self::GasConnectionError => write!(f, "3141 (GAS_CONN_ERR)"),
Self::GasTimeout => write!(f, "3142 (GAS_TIMEOUT)"),
Self::GasUnavailable => write!(f, "3143 (GAS_UNAVAILABLE)"),
Self::InvalidRequest => write!(f, "4000 (INVALID_REQUEST)"),
Self::InvalidChainId => write!(f, "4001 (INVALID_CHAIN_ID)"),
Self::InvalidInputTokens => write!(f, "4002 (INVALID_INPUT_TOKENS)"),
Self::InvalidOutputTokens => write!(f, "4003 (INVALID_OUTPUT_TOKENS)"),
Self::InvalidUserAddr => write!(f, "4004 (INVALID_USER_ADDR)"),
Self::BlockedUserAddr => write!(f, "4005 (BLOCKED_USER_ADDR)"),
Self::TooSlippery => write!(f, "4006 (TOO_SLIPPERY)"),
Self::SameInputOutput => write!(f, "4007 (SAME_INPUT_OUTPUT)"),
Self::MultiZapOutput => write!(f, "4008 (MULTI_ZAP_OUTPUT)"),
Self::InvalidTokenCount => write!(f, "4009 (INVALID_TOKEN_COUNT)"),
Self::InvalidTokenAddr => write!(f, "4010 (INVALID_TOKEN_ADDR)"),
Self::NonIntegerTokenAmount => write!(f, "4011 (NON_INTEGER_TOKEN_AMOUNT)"),
Self::NegativeTokenAmount => write!(f, "4012 (NEGATIVE_TOKEN_AMOUNT)"),
Self::SameInputOutputTokens => write!(f, "4013 (SAME_INPUT_OUTPUT_TOKENS)"),
Self::TokenBlacklisted => write!(f, "4014 (TOKEN_BLACKLISTED)"),
Self::InvalidTokenProportions => write!(f, "4015 (INVALID_TOKEN_PROPORTIONS)"),
Self::TokenRoutingUnavailable => write!(f, "4016 (TOKEN_ROUTING_UNAVAILABLE)"),
Self::InvalidReferralCode => write!(f, "4017 (INVALID_REFERRAL_CODE)"),
Self::InvalidTokenAmount => write!(f, "4018 (INVALID_TOKEN_AMOUNT)"),
Self::NonStringTokenAmount => write!(f, "4019 (NON_STRING_TOKEN_AMOUNT)"),
Self::InvalidAssemblyRequest => write!(f, "4100 (INVALID_ASSEMBLY_REQUEST)"),
Self::InvalidAssemblyUserAddr => write!(f, "4101 (INVALID_USER_ADDR)"),
Self::InvalidReceiverAddr => write!(f, "4102 (INVALID_RECEIVER_ADDR)"),
Self::InvalidSwapRequest => write!(f, "4200 (INVALID_SWAP_REQUEST)"),
Self::UserAddrRequired => write!(f, "4201 (USER_ADDR_REQ)"),
Self::InternalError => write!(f, "5000 (INTERNAL_ERROR)"),
Self::SwapUnavailable => write!(f, "5001 (SWAP_UNAVAILABLE)"),
Self::PriceCheckFailure => write!(f, "5002 (PRICE_CHECK_FAILURE)"),
Self::DefaultGasFailure => write!(f, "5003 (DEFAULT_GAS_FAILURE)"),
Self::Unknown(code) => write!(f, "{code} (UNKNOWN)"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_trace_id_creation() {
let uuid = Uuid::parse_str("10becdc8-a021-4491-8201-a17b657204e0").unwrap();
let trace_id = TraceId::new(uuid);
assert_eq!(trace_id.as_uuid(), uuid);
assert_eq!(trace_id.to_string(), "10becdc8-a021-4491-8201-a17b657204e0");
}
#[test]
fn test_error_code_from_u16() {
assert_eq!(OdosErrorCode::from(2999), OdosErrorCode::AlgoInternal);
assert_eq!(OdosErrorCode::from(4001), OdosErrorCode::InvalidChainId);
assert_eq!(OdosErrorCode::from(3142), OdosErrorCode::GasTimeout);
assert_eq!(OdosErrorCode::from(9999), OdosErrorCode::Unknown(9999));
}
#[test]
fn test_error_code_to_u16() {
assert_eq!(OdosErrorCode::AlgoInternal.code(), 2999);
assert_eq!(OdosErrorCode::InvalidChainId.code(), 4001);
assert_eq!(OdosErrorCode::Unknown(9999).code(), 9999);
}
#[test]
fn test_error_categories() {
assert!(OdosErrorCode::ApiError.is_general_error());
assert!(OdosErrorCode::NoViablePath.is_algo_error());
assert!(OdosErrorCode::ConfigInternal.is_internal_service_error());
assert!(OdosErrorCode::InvalidChainId.is_validation_error());
assert!(OdosErrorCode::InternalError.is_internal_error());
}
#[test]
fn test_specific_error_checks() {
assert!(OdosErrorCode::NoViablePath.is_no_viable_path());
assert!(OdosErrorCode::InvalidChainId.is_invalid_chain_id());
assert!(OdosErrorCode::BlockedUserAddr.is_blocked_user());
assert!(!OdosErrorCode::ApiError.is_no_viable_path());
assert!(!OdosErrorCode::AlgoInternal.is_invalid_chain_id());
}
#[test]
fn test_timeout_detection() {
assert!(OdosErrorCode::AlgoTimeout.is_timeout());
assert!(OdosErrorCode::ConfigTimeout.is_timeout());
assert!(OdosErrorCode::TxnAssemblyTimeout.is_timeout());
assert!(OdosErrorCode::ChainDataTimeout.is_timeout());
assert!(OdosErrorCode::PricingTimeout.is_timeout());
assert!(OdosErrorCode::GasTimeout.is_timeout());
assert!(!OdosErrorCode::AlgoInternal.is_timeout());
assert!(!OdosErrorCode::InvalidChainId.is_timeout());
}
#[test]
fn test_connection_error_detection() {
assert!(OdosErrorCode::AlgoConnectionError.is_connection_error());
assert!(OdosErrorCode::ConfigConnectionError.is_connection_error());
assert!(OdosErrorCode::GasConnectionError.is_connection_error());
assert!(!OdosErrorCode::AlgoInternal.is_connection_error());
assert!(!OdosErrorCode::InvalidChainId.is_connection_error());
}
#[test]
fn test_retryability() {
assert!(OdosErrorCode::AlgoTimeout.is_retryable());
assert!(OdosErrorCode::GasTimeout.is_retryable());
assert!(OdosErrorCode::AlgoConnectionError.is_retryable());
assert!(OdosErrorCode::PricingConnectionError.is_retryable());
assert!(OdosErrorCode::AlgoInternal.is_retryable());
assert!(OdosErrorCode::InternalServiceError.is_retryable());
assert!(OdosErrorCode::GasUnavailable.is_retryable());
assert!(!OdosErrorCode::InvalidChainId.is_retryable());
assert!(!OdosErrorCode::BlockedUserAddr.is_retryable());
assert!(!OdosErrorCode::InvalidTokenAmount.is_retryable());
assert!(!OdosErrorCode::NoViablePath.is_retryable());
}
#[test]
fn test_display_format() {
assert_eq!(
OdosErrorCode::AlgoInternal.to_string(),
"2999 (ALGO_INTERNAL)"
);
assert_eq!(
OdosErrorCode::InvalidChainId.to_string(),
"4001 (INVALID_CHAIN_ID)"
);
assert_eq!(OdosErrorCode::Unknown(9999).to_string(), "9999 (UNKNOWN)");
}
#[test]
fn test_unroutable_token_detection() {
assert!(OdosErrorCode::NoViablePath.is_unroutable_token());
assert!(OdosErrorCode::TokenRoutingUnavailable.is_unroutable_token());
assert!(OdosErrorCode::TokenBlacklisted.is_unroutable_token());
assert!(OdosErrorCode::InvalidInputTokens.is_unroutable_token());
assert!(OdosErrorCode::InvalidOutputTokens.is_unroutable_token());
assert!(!OdosErrorCode::AlgoTimeout.is_unroutable_token());
assert!(!OdosErrorCode::InternalError.is_unroutable_token());
assert!(!OdosErrorCode::InvalidChainId.is_unroutable_token());
assert!(!OdosErrorCode::BlockedUserAddr.is_unroutable_token());
}
}