apex_sdk/
error.rs

1//! Error types for Apex SDK
2
3use thiserror::Error;
4
5/// Result type alias for Apex SDK operations
6pub type Result<T> = std::result::Result<T, Error>;
7
8/// Apex SDK error types
9#[derive(Error, Debug)]
10pub enum Error {
11    /// Configuration error
12    #[error("Configuration error: {0}\n\nTip: {1}")]
13    Config(String, String),
14
15    /// Connection error
16    #[error("Connection error: {0}\n\nTip: {1}")]
17    Connection(String, String),
18
19    /// Transaction error
20    #[error("Transaction error: {0}\n\nTip: {1}")]
21    Transaction(String, String),
22
23    /// Chain not supported
24    #[error("Chain not supported: {0}\n\nSupported chains: Polkadot, Kusama, Westend (Substrate) | Ethereum, BSC, Polygon (EVM)\nUse `apex chain list` to see all supported chains")]
25    UnsupportedChain(String),
26
27    /// Invalid address format
28    #[error("Invalid address format: {0}\n\nExpected formats:\n  -Substrate (SS58): 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\n  -Ethereum (Hex): 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb")]
29    InvalidAddress(String),
30
31    /// Substrate adapter error
32    #[error("Substrate adapter error: {0}")]
33    Substrate(#[from] apex_sdk_substrate::Error),
34
35    /// EVM adapter error
36    #[error("EVM adapter error: {0}")]
37    Evm(#[from] apex_sdk_evm::Error),
38
39    /// Serialization error
40    #[error("Serialization error: {0}")]
41    Serialization(String),
42
43    /// Generic error
44    #[error("{0}")]
45    Other(String),
46}
47
48impl Error {
49    /// Create a configuration error with a helpful tip
50    pub fn config(msg: impl Into<String>) -> Self {
51        let msg_str = msg.into();
52        let tip = if msg_str.contains("adapter") || msg_str.contains("configured") {
53            "Use .with_substrate() or .with_evm() when building the SDK:\n    \
54             ApexSDK::builder().with_substrate(Chain::Polkadot, \"wss://rpc.polkadot.io\").build()?"
55        } else {
56            "Check your configuration and ensure all required fields are set"
57        };
58        Error::Config(msg_str, tip.to_string())
59    }
60
61    /// Create a connection error with a helpful tip
62    pub fn connection(msg: impl Into<String>) -> Self {
63        let msg_str = msg.into();
64        let tip = if msg_str.contains("timeout") || msg_str.contains("timed out") {
65            "The endpoint may be slow or unreachable. Try:\n    \
66             1. Use a different RPC endpoint\n    \
67             2. Increase timeout with .with_timeout(Duration::from_secs(30))\n    \
68             3. Check your internet connection"
69        } else if msg_str.contains("refused") || msg_str.contains("failed to connect") {
70            "Cannot reach the endpoint. Verify:\n    \
71             1. The endpoint URL is correct\n    \
72             2. Your firewall allows outbound connections\n    \
73             3. The node is online and accessible"
74        } else {
75            "Check the endpoint URL and network connection"
76        };
77        Error::Connection(msg_str, tip.to_string())
78    }
79
80    /// Create a transaction error with a helpful tip
81    pub fn transaction(msg: impl Into<String>) -> Self {
82        let msg_str = msg.into();
83        let tip = if msg_str.contains("insufficient") || msg_str.contains("balance") {
84            "Your account doesn't have enough balance. Ensure:\n    \
85             1. The account has sufficient funds for transaction + fees\n    \
86             2. You're using the correct account\n    \
87             3. The chain is the right one for your tokens"
88        } else if msg_str.contains("nonce") {
89            "Transaction nonce error. Try:\n    \
90             1. Wait for pending transactions to complete\n    \
91             2. Use .with_nonce() to specify nonce manually\n    \
92             3. Check for stuck transactions"
93        } else {
94            "Review the transaction parameters and account state"
95        };
96        Error::Transaction(msg_str, tip.to_string())
97    }
98}
99
100impl From<anyhow::Error> for Error {
101    fn from(err: anyhow::Error) -> Self {
102        Error::Other(err.to_string())
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn test_config_error_display() {
112        let error = Error::config("test config error");
113        assert!(error
114            .to_string()
115            .contains("Configuration error: test config error"));
116        assert!(error.to_string().contains("Tip:"));
117    }
118
119    #[test]
120    fn test_connection_error_display() {
121        let error = Error::connection("failed to connect");
122        assert!(error
123            .to_string()
124            .contains("Connection error: failed to connect"));
125        assert!(error.to_string().contains("Tip:"));
126    }
127
128    #[test]
129    fn test_transaction_error_display() {
130        let error = Error::transaction("invalid transaction");
131        assert!(error
132            .to_string()
133            .contains("Transaction error: invalid transaction"));
134        assert!(error.to_string().contains("Tip:"));
135    }
136
137    #[test]
138    fn test_unsupported_chain_error_display() {
139        let error = Error::UnsupportedChain("Unknown".to_string());
140        assert!(error.to_string().contains("Chain not supported: Unknown"));
141        assert!(error.to_string().contains("Supported chains:"));
142    }
143
144    #[test]
145    fn test_invalid_address_error_display() {
146        let error = Error::InvalidAddress("0xinvalid".to_string());
147        assert!(error
148            .to_string()
149            .contains("Invalid address format: 0xinvalid"));
150        assert!(error.to_string().contains("Expected formats:"));
151    }
152
153    #[test]
154    fn test_serialization_error_display() {
155        let error = Error::Serialization("JSON parse error".to_string());
156        assert_eq!(error.to_string(), "Serialization error: JSON parse error");
157    }
158
159    #[test]
160    fn test_other_error_display() {
161        let error = Error::Other("generic error".to_string());
162        assert_eq!(error.to_string(), "generic error");
163    }
164
165    #[test]
166    fn test_from_anyhow_error() {
167        let anyhow_err = anyhow::anyhow!("test error");
168        let error: Error = anyhow_err.into();
169        assert!(matches!(error, Error::Other(_)));
170        assert_eq!(error.to_string(), "test error");
171    }
172
173    #[test]
174    fn test_error_is_send_sync() {
175        fn assert_send<T: Send>() {}
176        fn assert_sync<T: Sync>() {}
177        assert_send::<Error>();
178        assert_sync::<Error>();
179    }
180}