switchboard_common/
error.rs

1use anyhow::Error as AnyhowError;
2use serde_json::Error as SerdeJsonError;
3use std::error::Error as StdError;
4use std::fmt::Debug;
5use std::fmt::{self, Display, Formatter};
6use std::sync::Arc;
7
8type ParentError = Arc<dyn StdError + Send + Sync + 'static>;
9
10#[derive(Debug)]
11pub struct AnyhowWrapper {
12    inner: AnyhowError,
13}
14
15impl StdError for AnyhowWrapper {
16    fn source(&self) -> Option<&(dyn StdError + 'static)> {
17        self.inner.source()
18    }
19}
20
21impl From<AnyhowError> for AnyhowWrapper {
22    fn from(error: AnyhowError) -> Self {
23        AnyhowWrapper { inner: error }
24    }
25}
26
27impl Display for AnyhowWrapper {
28    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
29        write!(f, "{}", self.inner)?;
30        Ok(())
31    }
32}
33
34/// Switchboard Functions error suite
35#[derive(Clone, Debug)]
36pub enum SbError {
37    // Variant
38    AnyhowError(Arc<AnyhowWrapper>), // use anyhowWrapper to get around std::error::Error not being implemented for AnyhowError
39    // Generics
40    Generic,
41    Message(&'static str),
42    CustomMessage(String),
43    CustomError {
44        message: String,
45        source: ParentError,
46    },
47    Unexpected,
48    // Environment Errors
49    EnvVariableMissing(String),
50    InvalidKeypairFile,
51    KeyParseError,
52    CheckSizeError,
53
54    IoError(ParentError),
55
56    // SGX Errors
57    SgxError,
58    SgxWriteError,
59
60    //AMD Errors
61    AmdSevError,
62    AmdRandomnessError,
63
64    // Network Errors
65    NetworkError,
66
67    // Quote Errors
68    QuoteParseError,
69    InvalidQuoteError,
70
71    // QvnErrors
72    QvnError(Arc<String>),
73
74    // Docker/Container Errors
75    DockerError,
76    DockerFetchError,
77    FunctionImageTooBigError,
78    ContainerErrorMessage(String),
79    ContainerError(ParentError),
80    ContainerStartError(ParentError),
81    ContainerCreateError(ParentError),
82    ContainerNeedsUpdate,
83    // ContainerCreateError,
84    ContainerResultParseError,
85    AttachError,
86    ContainerTimeout,
87    ContainerActive,
88    ContainerBackoff(u64),
89    FunctionErrorCountExceeded(u32),
90
91    // Function Errors
92    FunctionResultParseError,
93    IllegalFunctionOutput,
94    FunctionVerifyFailure,
95    FunctionResultIllegalAccount,
96    FunctionResultAccountsMismatch,
97    FunctionResultInvalidData,
98    FunctionResultInvalidPid,
99    FunctionResultEmptyInstructions,
100
101    // Transaction Errors
102    TxFailure,
103    TxCompileErr,
104    TxDeserializationError,
105    QvnTxSendFailure,
106    InvalidInstructionError,
107
108    // Chain specific Errors
109    InvalidChain,
110    AnchorParse,
111    AnchorParseError,
112    EvmError,
113
114    // Misc
115    IpfsParseError,
116    IpfsNetworkError,
117    HeartbeatRoutineFailure,
118    EventListenerRoutineFailure,
119    DecryptError,
120    ParseError,
121    MrEnclaveMismatch,
122    FunctionResultIxIncorrectTargetChain,
123    InvalidSignature,
124
125    // Solana
126    /// Failed to fetch a network resource
127    SolanaFetchError(String),
128    /// Failed to fetch a blockhash from the cluster
129    SolanaBlockhashError,
130    /// Failed to fetch a blockhash from the cluster
131    SolanaBlockhashFetchError(ParentError),
132    /// THe provided payer does not match the payer of the transaction
133    /// Expected vs actual
134    SolanaPayerMismatch(String, String),
135    SolanaPayerSignerMissing(String),
136    /// A required Solana signer is missing
137    SolanaMissingSigner(String),
138    SolanaSignError(ParentError, String),
139    SolanaInstructionsEmpty,
140    SolanaInstructionOverflow,
141    FunctionResultIxMissingDiscriminator,
142    FunctionResultError(&'static str),
143    FunctionResultIxError(&'static str),
144    // An error which should fail to send the user generated transaction and should emit an error code
145    FunctionResultFailoverError(u8, ParentError),
146    // An error which should not be retried and should be dropped by the QVN.
147    FunctionResultNonRetryableError(ParentError),
148
149    AccountNotFound,
150    HttpError(String, u32, u32),
151}
152
153impl fmt::Display for SbError {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        match self {
156            SbError::AnyhowError(e) => write!(f, "Anyhow error: {}", e),
157            SbError::EnvVariableMissing(message) => {
158                write!(f, "Env variable missing: {}", message.as_str())
159            }
160            SbError::Message(message) => write!(f, "error: {}", message),
161            SbError::CustomMessage(message) => write!(f, "error: {}", message.as_str()),
162            SbError::CustomError {
163                message, source, ..
164            } => write!(f, "error: {} - {:?}", message.as_str(), source),
165            SbError::FunctionResultError(message) => {
166                write!(f, "error: FunctionResultError - {}", message)
167            }
168            SbError::FunctionResultIxError(message) => {
169                write!(f, "error: FunctionResultIxError - {}", message)
170            }
171            SbError::FunctionResultFailoverError(code, source) => {
172                write!(
173                    f,
174                    "error: FunctionResultFailoverError ({}) - {:?}",
175                    code, source
176                )
177            }
178            SbError::FunctionResultNonRetryableError(source) => {
179                write!(f, "error: FunctionResultNonRetryableError - {:?}", source)
180            }
181            SbError::SolanaPayerMismatch(expected, actual) => {
182                write!(
183                    f,
184                    "error: SolanaPayerMismatch - expected: {}, actual: {}",
185                    expected, actual
186                )
187            }
188            SbError::SolanaMissingSigner(missing_signer) => {
189                write!(f, "error: Missing required signer: {}", missing_signer)
190            }
191            SbError::SolanaInstructionsEmpty => write!(
192                f,
193                "error: The attempted action requires at least one instruction but none were provided"
194            ),
195            SbError::SolanaInstructionOverflow => write!(
196                f,
197                "error: The transaction exceeded the maximum number of instructions (10)"
198            ),
199            SbError::SolanaPayerSignerMissing(payer) => write!(
200                f,
201                "error: The payer keypair is missing from the provided signers: {}",
202                payer
203            ),
204            SbError::SolanaBlockhashFetchError(source) => write!(
205                f,
206                "error: Failed to fetch blockhash from the cluster. Please try again. - {:?}", source
207            ),
208            // Handle other error variants as needed
209            _ => write!(f, "{:#?}", self),
210        }
211    }
212}
213
214impl<T> From<SbError> for Result<T, Box<SbError>> {
215    fn from(err: SbError) -> Result<T, Box<SbError>> {
216        Err(Box::new(err))
217    }
218}
219
220impl From<&str> for SbError {
221    fn from(error: &str) -> Self {
222        SbError::CustomMessage(error.to_string())
223    }
224}
225impl From<String> for SbError {
226    fn from(error: String) -> Self {
227        SbError::CustomMessage(error)
228    }
229}
230
231impl From<hex::FromHexError> for SbError {
232    fn from(error: hex::FromHexError) -> Self {
233        SbError::CustomError {
234            message: "hex error".to_string(),
235            source: Arc::new(error),
236        }
237    }
238}
239
240impl std::error::Error for SbError {
241    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
242        match self {
243            SbError::AnyhowError(e) => Some(e.as_ref()),
244            SbError::CustomError { source, .. } => Some(source.as_ref()), // Handle other error variants as needed
245            SbError::ContainerError(source) => Some(source.as_ref()),
246            SbError::ContainerStartError(source) => Some(source.as_ref()),
247            SbError::SolanaSignError(source, ..) => Some(source.as_ref()),
248            SbError::FunctionResultFailoverError(_code, source, ..) => Some(source.as_ref()),
249            SbError::FunctionResultNonRetryableError(source, ..) => Some(source.as_ref()),
250            _ => None,
251        }
252    }
253}
254
255impl From<SerdeJsonError> for SbError {
256    fn from(error: SerdeJsonError) -> Self {
257        SbError::CustomError {
258            message: "serde_json error".to_string(),
259            source: Arc::new(error),
260        }
261    }
262}
263
264impl From<std::io::Error> for SbError {
265    fn from(val: std::io::Error) -> Self {
266        SbError::IoError(std::sync::Arc::new(val))
267    }
268}
269
270impl From<anyhow::Error> for SbError {
271    fn from(err: anyhow::Error) -> Self {
272        SbError::AnyhowError(Arc::new(AnyhowWrapper::from(err)))
273    }
274}
275
276impl<T> From<Result<T, anyhow::Error>> for SbError {
277    fn from(err: Result<T, anyhow::Error>) -> Self {
278        match err {
279            Ok(_) => SbError::Generic,
280            Err(e) => SbError::from(e),
281        }
282    }
283}
284
285#[cfg(test)]
286mod tests {
287    use super::*;
288    use anyhow::anyhow;
289
290    #[test]
291    fn display_generic() {
292        let error = SbError::Generic;
293        assert_eq!(format!("{}", error), "Generic");
294    }
295
296    #[test]
297    fn display_custom_message() {
298        let error = SbError::CustomMessage("my custom message".to_string());
299        assert_eq!(format!("{}", error), "error: my custom message");
300    }
301
302    #[test]
303    fn display_env_variable_missing() {
304        let error = SbError::EnvVariableMissing("MY_ENV_VAR".to_string());
305        assert_eq!(format!("{}", error), "Env variable missing: MY_ENV_VAR");
306    }
307
308    #[test]
309    fn from_str() {
310        let error: SbError = "my custom message".into();
311        assert_eq!(format!("{}", error), "error: my custom message");
312    }
313
314    #[test]
315    fn from_hex_error() {
316        let hex_error = hex::FromHexError::OddLength;
317        let error: SbError = hex_error.into();
318        assert_eq!(format!("{}", error), "error: hex error - OddLength");
319    }
320
321    #[test]
322    fn from_serde_json_error() {
323        let json = "\"";
324        let serde_json_error = serde_json::from_str::<serde_json::Value>(json).unwrap_err();
325        let error: SbError = serde_json_error.into();
326        assert!(format!("{}", error).starts_with("error: serde_json error - "));
327    }
328
329    // #[test]
330    // fn anyhow_error_integration_with_line_numbers() {
331    // Create the initial error
332    // let error = anyhow!("Initial error");
333
334    // // Add the first layer of context
335    // let error_with_context =
336    //     error.context(format!("First layer of context at {}:{}", file!(), line!()));
337    // println!("{:?}", error_with_context);
338    // // Add the second layer of context
339    // let error_with_context = error_with_context.context(format!(
340    //     "Second layer of context at {}:{}",
341    //     file!(),
342    //     line!() + 2
343    // ));
344    // println!("error_with_context: {:?}", error_with_context);
345    // let sb_error = SbError::AnyhowError(Arc::new(AnyhowWrapper::from(error_with_context)));
346    // println!("sb_error: {:?}", sb_error);
347    // let error_string = format!("{:?}", sb_error);
348    // println!("{}", error_string);
349
350    // // Pattern to match any line number in the format `error.rs:<line_number>`
351    // let line_number_pattern = Regex::new(r"error\.rs:\d+").unwrap();
352
353    // assert!(
354    //     error_string.contains("Initial error"),
355    //     "Should contain the initial error message"
356    // );
357    // assert!(
358    //     error_string.contains("First layer of context"),
359    //     "Should contain the first context"
360    // );
361    // assert!(
362    //     error_string.contains("Second layer of context"),
363    //     "Should contain the second context"
364    // );
365    // assert!(
366    //     error_string.contains("error.rs"),
367    //     "Should include the file name"
368    // );
369    // // assert!(
370    // //     line_number_pattern.is_match(&error_string),
371    // //     "Should include a line number"
372    // // );
373    // // assert!(
374    // //     error_string.contains("backtrace:"),
375    // //     "Should include a backtrace"
376    // // );
377    // }
378}
379
380impl From<SbError> for Box<dyn StdError + Send> {
381    fn from(err: SbError) -> Self {
382        Box::new(err) // Assuming SbError implements StdError and Send
383    }
384}