gmsol_sdk/
error.rs

1pub(crate) use gmsol_programs::anchor_lang::prelude::Error as AnchorLangError;
2
3/// SDK Error.
4#[derive(Debug, thiserror::Error)]
5pub enum Error {
6    /// Error from [`gmsol-solana-utils`].
7    #[error("solana-utils: {0}")]
8    SolanaUtils(gmsol_solana_utils::Error),
9    /// Anchor Lang Error.
10    #[error("anchor-lang: {0}")]
11    AnchorLang(Box<AnchorLangError>),
12    /// Anchor Error.
13    #[error("anchor: {0:#?}")]
14    Anchor(AnchorError),
15    /// Model Error.
16    #[error("model: {0}")]
17    Model(#[from] gmsol_model::Error),
18    /// Base64 decode error.
19    #[error("base64-decode: {0}")]
20    Base64Decode(#[from] base64::DecodeError),
21    /// Bincode error.
22    #[cfg(feature = "bincode")]
23    #[error("bincode: {0}")]
24    Bincode(#[from] bincode::Error),
25    /// Custom error.
26    #[error("custom: {0}")]
27    Custom(String),
28    /// Transport error.
29    #[error("transport: {0}")]
30    Transport(String),
31    /// Market Graph Errors
32    #[cfg(feature = "market-graph")]
33    #[error("market-graph: {0}")]
34    MarketGraph(#[from] crate::market_graph::error::MarketGraphError),
35    /// Parse Pubkey Error.
36    #[error("parse pubkey error: {0}")]
37    ParsePubkey(#[from] solana_sdk::pubkey::ParsePubkeyError),
38    /// Pubsub client closed.
39    #[cfg(feature = "client")]
40    #[error("pubsub: closed")]
41    PubsubClosed,
42    /// Not found error.
43    #[error("not found")]
44    NotFound,
45    /// Decode error.
46    #[cfg(feature = "decode")]
47    #[error("decode: {0}")]
48    Decode(#[from] gmsol_decode::DecodeError),
49    /// Error from [`gmsol_programs`].
50    #[error("programs: {0}")]
51    Programs(#[from] gmsol_programs::Error),
52    /// Reqwest error.
53    #[cfg(feature = "reqwest")]
54    #[error("reqwest: {0}")]
55    Reqwest(#[from] reqwest::Error),
56    /// Json error.
57    #[cfg(feature = "serde_json")]
58    #[error("json: {0}")]
59    Json(#[from] serde_json::Error),
60    /// Market closed error.
61    #[error("market closed: {0}")]
62    MarketClosed(String),
63}
64
65impl Error {
66    /// Create a custom error.
67    pub fn custom(msg: impl ToString) -> Self {
68        Self::Custom(msg.to_string())
69    }
70
71    /// Create a transport error.
72    pub fn transport(msg: impl ToString) -> Self {
73        Self::Transport(msg.to_string())
74    }
75
76    /// Anchor Error Code.
77    pub fn anchor_error_code(&self) -> Option<u32> {
78        let Self::Anchor(error) = self else {
79            return None;
80        };
81        Some(error.error_code_number)
82    }
83
84    /// Create a [`MarketClosed`](Error::MarketClosed) error.
85    pub fn market_closed(msg: impl ToString) -> Self {
86        Self::MarketClosed(msg.to_string())
87    }
88}
89
90impl From<AnchorLangError> for Error {
91    fn from(value: AnchorLangError) -> Self {
92        Self::AnchorLang(Box::new(value))
93    }
94}
95
96#[cfg(feature = "wasm-bindgen")]
97impl From<Error> for wasm_bindgen::JsValue {
98    fn from(value: Error) -> Self {
99        Self::from_str(&value.to_string())
100    }
101}
102
103/// Anchor Error with owned source.
104#[derive(Debug, Clone, Default)]
105pub struct AnchorError {
106    /// Error name.
107    pub error_name: String,
108    /// Error code.
109    pub error_code_number: u32,
110    /// Error message.
111    pub error_msg: String,
112    /// Error origin.
113    pub error_origin: Option<ErrorOrigin>,
114    /// Logs.
115    pub logs: Vec<String>,
116}
117
118/// Error origin with owned source.
119#[derive(Debug, Clone)]
120pub enum ErrorOrigin {
121    /// Source.
122    Source(String, u32),
123    /// Account.
124    AccountName(String),
125}
126
127#[cfg(feature = "solana-client")]
128fn handle_solana_client_error(error: &solana_client::client_error::ClientError) -> Option<Error> {
129    use solana_client::{
130        client_error::ClientErrorKind,
131        rpc_request::{RpcError, RpcResponseErrorData},
132    };
133
134    let ClientErrorKind::RpcError(rpc_error) = error.kind() else {
135        return None;
136    };
137
138    let RpcError::RpcResponseError { data, .. } = rpc_error else {
139        return None;
140    };
141
142    let RpcResponseErrorData::SendTransactionPreflightFailure(simulation) = data else {
143        return None;
144    };
145
146    let Some(logs) = &simulation.logs else {
147        return None;
148    };
149
150    for log in logs {
151        if log.starts_with("Program log: AnchorError") {
152            let log = log.trim_start_matches("Program log: AnchorError ");
153            let Some((origin, rest)) = log.split_once("Error Code:") else {
154                break;
155            };
156            let Some((name, rest)) = rest.split_once("Error Number:") else {
157                break;
158            };
159            let Some((number, message)) = rest.split_once("Error Message:") else {
160                break;
161            };
162            let number = number.trim().trim_end_matches('.');
163            let Ok(number) = number.parse() else {
164                break;
165            };
166
167            let origin = origin.trim().trim_end_matches('.');
168
169            let origin = if origin.starts_with("thrown in") {
170                let source = origin.trim_start_matches("thrown in ");
171                if let Some((filename, line)) = source.split_once(':') {
172                    Some(ErrorOrigin::Source(
173                        filename.to_string(),
174                        line.parse().ok().unwrap_or(0),
175                    ))
176                } else {
177                    None
178                }
179            } else if origin.starts_with("caused by account:") {
180                let account = origin.trim_start_matches("caused by account: ");
181                Some(ErrorOrigin::AccountName(account.to_string()))
182            } else {
183                None
184            };
185
186            let error = AnchorError {
187                error_name: name.trim().trim_end_matches('.').to_string(),
188                error_code_number: number,
189                error_msg: message.trim().to_string(),
190                error_origin: origin,
191                logs: logs.clone(),
192            };
193
194            return Some(Error::Anchor(error));
195        }
196    }
197
198    None
199}
200
201impl From<gmsol_solana_utils::Error> for Error {
202    fn from(value: gmsol_solana_utils::Error) -> Self {
203        match value {
204            #[cfg(feature = "solana-client")]
205            gmsol_solana_utils::Error::Client(err) => match handle_solana_client_error(&err) {
206                Some(err) => err,
207                None => Self::SolanaUtils(err.into()),
208            },
209            err => Self::SolanaUtils(err),
210        }
211    }
212}
213
214impl<T> From<(T, gmsol_solana_utils::Error)> for Error {
215    fn from((_, err): (T, gmsol_solana_utils::Error)) -> Self {
216        Self::from(err)
217    }
218}