soroban_rs/
error.rs

1//! # Soroban Error Handling
2//!
3//! This module defines the error types used throughout the Soroban helpers library.
4//! It provides a unified error handling approach for all operations related to
5//! Soroban contract deployment, invocation, and transaction management.
6use std::{error::Error, fmt};
7
8/// Errors that can occur when using the Soroban helpers library.
9///
10/// This enum covers errors from various operations including transaction
11/// submission, signing, contract deployment, and network communication.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum SorobanHelperError {
14    /// Error when a transaction fails to execute successfully.
15    TransactionFailed(String),
16
17    /// Error when a transaction simulation fails.
18    TransactionSimulationFailed(String),
19
20    /// Error when attempting to upload contract code that already exists.
21    ContractCodeAlreadyExists,
22
23    /// Error when a network request to the Soroban RPC server fails.
24    NetworkRequestFailed(String),
25
26    /// Error when a signing operation fails.
27    SigningFailed(String),
28
29    /// Error when XDR encoding or decoding fails.
30    XdrEncodingFailed(String),
31
32    /// Error when an invalid argument is provided to a function.
33    InvalidArgument(String),
34
35    /// Error when building a transaction fails.
36    TransactionBuildFailed(String),
37
38    /// Error when an operation requires authorization that isn't present.
39    Unauthorized(String),
40
41    /// Error when attempting to invoke a contract without setting deployment configs.
42    ContractDeployedConfigsNotSet,
43
44    /// Error when a file operation fails.
45    FileReadError(String),
46
47    /// Error when a conversion fails.
48    ConversionError(String),
49
50    // Some client operations taht it's still not supported
51    NotSupported(String),
52}
53
54impl fmt::Display for SorobanHelperError {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        match self {
57            Self::TransactionFailed(msg) => write!(f, "Transaction failed: {}", msg),
58            Self::TransactionSimulationFailed(msg) => {
59                write!(f, "Transaction simulation failed: {}", msg)
60            }
61            Self::ContractCodeAlreadyExists => write!(f, "Contract code already exists"),
62            Self::NetworkRequestFailed(msg) => write!(f, "Network request failed: {}", msg),
63            Self::SigningFailed(msg) => write!(f, "Signing operation failed: {}", msg),
64            Self::XdrEncodingFailed(msg) => write!(f, "XDR encoding failed: {}", msg),
65            Self::InvalidArgument(msg) => write!(f, "Invalid argument: {}", msg),
66            Self::TransactionBuildFailed(msg) => write!(f, "Transaction build failed: {}", msg),
67            Self::Unauthorized(msg) => write!(f, "Unauthorized: {}", msg),
68            Self::ContractDeployedConfigsNotSet => write!(f, "Contract deployed configs not set"),
69            Self::FileReadError(msg) => write!(f, "File read error: {}", msg),
70            Self::ConversionError(msg) => write!(f, "Conversion error: {}", msg),
71            Self::NotSupported(msg) => write!(f, "Not supported: {}", msg),
72        }
73    }
74}
75
76impl Error for SorobanHelperError {}
77
78/// Convert XDR errors into SorobanHelperError
79impl From<stellar_xdr::curr::Error> for SorobanHelperError {
80    fn from(err: stellar_xdr::curr::Error) -> Self {
81        Self::XdrEncodingFailed(err.to_string())
82    }
83}
84
85/// Convert IO errors into SorobanHelperError
86impl From<std::io::Error> for SorobanHelperError {
87    fn from(err: std::io::Error) -> Self {
88        Self::InvalidArgument(format!("File operation failed: {}", err))
89    }
90}
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use std::io::{Error as IoError, ErrorKind};
95
96    #[test]
97    fn test_display_implementations() {
98        let cases = [
99            (
100                SorobanHelperError::TransactionFailed("timeout".to_string()),
101                "Transaction failed: timeout",
102            ),
103            (
104                SorobanHelperError::ContractCodeAlreadyExists,
105                "Contract code already exists",
106            ),
107            (
108                SorobanHelperError::NetworkRequestFailed("connection refused".to_string()),
109                "Network request failed: connection refused",
110            ),
111            (
112                SorobanHelperError::SigningFailed("invalid key".to_string()),
113                "Signing operation failed: invalid key",
114            ),
115            (
116                SorobanHelperError::XdrEncodingFailed("invalid format".to_string()),
117                "XDR encoding failed: invalid format",
118            ),
119            (
120                SorobanHelperError::InvalidArgument("wrong type".to_string()),
121                "Invalid argument: wrong type",
122            ),
123            (
124                SorobanHelperError::TransactionBuildFailed("missing field".to_string()),
125                "Transaction build failed: missing field",
126            ),
127            (
128                SorobanHelperError::Unauthorized("missing signature".to_string()),
129                "Unauthorized: missing signature",
130            ),
131            (
132                SorobanHelperError::ContractDeployedConfigsNotSet,
133                "Contract deployed configs not set",
134            ),
135            (
136                SorobanHelperError::FileReadError("file not found".to_string()),
137                "File read error: file not found",
138            ),
139            (
140                SorobanHelperError::ConversionError("invalid type conversion".to_string()),
141                "Conversion error: invalid type conversion",
142            ),
143            (
144                SorobanHelperError::TransactionSimulationFailed("bad input".to_string()),
145                "Transaction simulation failed: bad input",
146            ),
147            (
148                SorobanHelperError::NotSupported("feature not implemented".to_string()),
149                "Not supported: feature not implemented",
150            ),
151        ];
152
153        for (error, expected_msg) in cases {
154            assert_eq!(error.to_string(), expected_msg);
155        }
156    }
157
158    #[test]
159    fn test_from_io_error() {
160        let io_error = IoError::new(ErrorKind::NotFound, "file not found");
161        let helper_error = SorobanHelperError::from(io_error);
162
163        assert!(
164            matches!(helper_error, SorobanHelperError::InvalidArgument(_)),
165            "Expected InvalidArgument variant"
166        );
167
168        let error_string = helper_error.to_string();
169        assert!(error_string.contains("file not found"));
170        assert!(error_string.contains("File operation failed"));
171    }
172
173    #[test]
174    fn test_from_xdr_error() {
175        // Create a mock XDR error
176        let xdr_error = stellar_xdr::curr::Error::Invalid;
177        let helper_error = SorobanHelperError::from(xdr_error);
178
179        assert!(
180            matches!(helper_error, SorobanHelperError::XdrEncodingFailed(_)),
181            "Expected XdrEncodingFailed variant"
182        );
183
184        let error_string = helper_error.to_string();
185        assert!(error_string.contains("XDR encoding failed"));
186    }
187
188    #[test]
189    fn test_error_trait_implementation() {
190        // Test that SorobanHelperError implements the Error trait correctly
191        let error = SorobanHelperError::InvalidArgument("test error".to_string());
192        let _: &dyn Error = &error; // This will fail to compile if Error is not implemented
193
194        // Test Debug implementation
195        let debug_str = format!("{:?}", error);
196        assert!(debug_str.contains("InvalidArgument"));
197    }
198
199    #[test]
200    fn test_error_equality() {
201        // Test PartialEq implementation
202        let error1 = SorobanHelperError::InvalidArgument("test error".to_string());
203        let error2 = SorobanHelperError::InvalidArgument("test error".to_string());
204        let error3 = SorobanHelperError::InvalidArgument("different error".to_string());
205
206        assert_eq!(error1, error2);
207        assert_ne!(error1, error3);
208
209        let different_type = SorobanHelperError::NetworkRequestFailed("test error".to_string());
210        assert_ne!(error1, different_type);
211    }
212
213    #[test]
214    fn test_error_cloning() {
215        // Test Clone implementation
216        let original = SorobanHelperError::TransactionFailed("test failure".to_string());
217        let cloned = original.clone();
218
219        assert_eq!(original, cloned);
220    }
221}