1use thiserror::Error;
7
8#[derive(Error, Debug)]
12pub enum SuiError {
13 #[error("RPC error: {0}")]
16 RpcError(String),
17
18 #[error("Object already used: {0}")]
21 ObjectUsed(String),
22
23 #[error("State proof verification failed: {0}")]
26 StateProofFailed(String),
27
28 #[error("Event proof verification failed: {0}")]
31 EventProofFailed(String),
32
33 #[error("Checkpoint certification failed: {0}")]
36 CheckpointFailed(String),
37
38 #[error("Transaction failed: {0}")]
41 TransactionFailed(String),
42
43 #[error("Serialization error: {0}")]
46 SerializationError(String),
47
48 #[error("Transaction confirmation timeout after {timeout_ms}ms: {tx_digest}")]
51 ConfirmationTimeout { tx_digest: String, timeout_ms: u64 },
52
53 #[error("Chain reorg detected at checkpoint {checkpoint}: anchor may be invalid")]
56 ReorgDetected { checkpoint: u64 },
57
58 #[error("Network mismatch: expected chain_id {expected}, got {actual}")]
61 NetworkMismatch { expected: String, actual: String },
62
63 #[error(transparent)]
65 CoreError(#[from] csv_adapter_core::AdapterError),
66}
67
68impl SuiError {
69 pub fn is_transient(&self) -> bool {
71 match self {
72 SuiError::RpcError(_)
73 | SuiError::ConfirmationTimeout { .. }
74 | SuiError::TransactionFailed(_) => true,
75 SuiError::ObjectUsed(_)
76 | SuiError::StateProofFailed(_)
77 | SuiError::EventProofFailed(_)
78 | SuiError::CheckpointFailed(_)
79 | SuiError::SerializationError(_)
80 | SuiError::ReorgDetected { .. }
81 | SuiError::NetworkMismatch { .. }
82 | SuiError::CoreError(_) => false,
83 }
84 }
85
86 pub fn timeout(tx_digest: &str, timeout_ms: u64) -> Self {
88 SuiError::ConfirmationTimeout {
89 tx_digest: tx_digest.to_string(),
90 timeout_ms,
91 }
92 }
93
94 pub fn reorg(checkpoint: u64) -> Self {
96 SuiError::ReorgDetected { checkpoint }
97 }
98}
99
100impl From<Box<dyn std::error::Error + Send + Sync>> for SuiError {
101 fn from(err: Box<dyn std::error::Error + Send + Sync>) -> Self {
102 SuiError::RpcError(err.to_string())
103 }
104}
105
106impl From<SuiError> for csv_adapter_core::AdapterError {
107 fn from(err: SuiError) -> Self {
108 match err {
109 SuiError::CoreError(e) => e,
110 SuiError::RpcError(msg) | SuiError::TransactionFailed(msg) => {
111 csv_adapter_core::AdapterError::NetworkError(msg)
112 }
113 SuiError::ObjectUsed(msg) => csv_adapter_core::AdapterError::InvalidSeal(msg),
114 SuiError::StateProofFailed(msg) | SuiError::EventProofFailed(msg) => {
115 csv_adapter_core::AdapterError::InclusionProofFailed(msg)
116 }
117 SuiError::CheckpointFailed(msg) => csv_adapter_core::AdapterError::NetworkError(msg),
118 SuiError::SerializationError(msg) => csv_adapter_core::AdapterError::InvalidSeal(msg),
119 SuiError::ConfirmationTimeout {
120 tx_digest,
121 timeout_ms,
122 } => csv_adapter_core::AdapterError::NetworkError(format!(
123 "Timeout waiting for tx {} after {}ms",
124 tx_digest, timeout_ms
125 )),
126 SuiError::ReorgDetected { checkpoint } => csv_adapter_core::AdapterError::ReorgInvalid(
127 format!("Reorg at checkpoint {}", checkpoint),
128 ),
129 sui_err => csv_adapter_core::AdapterError::NetworkError(format!("{}", sui_err)),
130 }
131 }
132}
133
134pub type SuiResult<T> = Result<T, SuiError>;
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn test_transient_errors() {
143 assert!(SuiError::RpcError("connection refused".to_string()).is_transient());
144 assert!(SuiError::ConfirmationTimeout {
145 tx_digest: "abc".to_string(),
146 timeout_ms: 30000
147 }
148 .is_transient());
149 assert!(SuiError::TransactionFailed("execution failed".to_string()).is_transient());
150 }
151
152 #[test]
153 fn test_non_transient_errors() {
154 assert!(!SuiError::ObjectUsed("object consumed".to_string()).is_transient());
155 assert!(!SuiError::StateProofFailed("invalid proof".to_string()).is_transient());
156 assert!(!SuiError::ReorgDetected { checkpoint: 100 }.is_transient());
157 }
158
159 #[test]
160 fn test_error_conversion() {
161 let sui_err = SuiError::StateProofFailed("bad proof".to_string());
162 let core_err: csv_adapter_core::AdapterError = sui_err.into();
163 assert!(matches!(
164 core_err,
165 csv_adapter_core::AdapterError::InclusionProofFailed(_)
166 ));
167 }
168}