kora_lib/
error.rs

1use jsonrpsee::{core::Error as RpcError, types::error::CallError};
2use serde::{Deserialize, Serialize};
3use solana_client::client_error::ClientError;
4use solana_program::program_error::ProgramError;
5use solana_sdk::signature::SignerError;
6use std::error::Error as StdError;
7use thiserror::Error;
8
9#[derive(Error, Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
10pub enum KoraError {
11    #[error("Account {0} not found")]
12    AccountNotFound(String),
13
14    #[error("RPC error: {0}")]
15    RpcError(String),
16
17    #[error("Signing error: {0}")]
18    SigningError(String),
19
20    #[error("Invalid transaction: {0}")]
21    InvalidTransaction(String),
22
23    #[error("Transaction execution failed: {0}")]
24    TransactionExecutionFailed(String),
25
26    #[error("Fee estimation failed: {0}")]
27    FeeEstimationFailed(String),
28
29    #[error("Token {0} is not supported for fee payment")]
30    UnsupportedFeeToken(String),
31
32    #[error("Insufficient funds: {0}")]
33    InsufficientFunds(String),
34
35    #[error("Internal error: {0}")]
36    InternalServerError(String),
37
38    #[error("Validation error: {0}")]
39    ValidationError(String),
40
41    #[error("Serialization error: {0}")]
42    SerializationError(String),
43
44    #[error("Swap error: {0}")]
45    SwapError(String),
46
47    #[error("Token operation failed: {0}")]
48    TokenOperationError(String),
49
50    #[error("Thread safety error: {0}")]
51    ThreadSafetyError(String),
52
53    #[error("Invalid request: {0}")]
54    InvalidRequest(String),
55
56    #[error("Unauthorized: {0}")]
57    Unauthorized(String),
58}
59
60impl From<ClientError> for KoraError {
61    fn from(e: ClientError) -> Self {
62        KoraError::RpcError(e.to_string())
63    }
64}
65
66impl From<SignerError> for KoraError {
67    fn from(e: SignerError) -> Self {
68        KoraError::SigningError(e.to_string())
69    }
70}
71
72impl From<bincode::Error> for KoraError {
73    fn from(e: bincode::Error) -> Self {
74        KoraError::SerializationError(e.to_string())
75    }
76}
77
78impl From<bs58::decode::Error> for KoraError {
79    fn from(e: bs58::decode::Error) -> Self {
80        KoraError::SerializationError(e.to_string())
81    }
82}
83
84impl From<bs58::encode::Error> for KoraError {
85    fn from(e: bs58::encode::Error) -> Self {
86        KoraError::SerializationError(e.to_string())
87    }
88}
89
90impl From<std::io::Error> for KoraError {
91    fn from(e: std::io::Error) -> Self {
92        KoraError::InternalServerError(e.to_string())
93    }
94}
95
96impl From<Box<dyn StdError>> for KoraError {
97    fn from(e: Box<dyn StdError>) -> Self {
98        KoraError::InternalServerError(e.to_string())
99    }
100}
101
102impl From<Box<dyn StdError + Send + Sync>> for KoraError {
103    fn from(e: Box<dyn StdError + Send + Sync>) -> Self {
104        KoraError::InternalServerError(e.to_string())
105    }
106}
107
108impl From<ProgramError> for KoraError {
109    fn from(err: ProgramError) -> Self {
110        KoraError::InvalidTransaction(err.to_string())
111    }
112}
113
114impl From<KoraError> for RpcError {
115    fn from(err: KoraError) -> Self {
116        match err {
117            KoraError::AccountNotFound(_)
118            | KoraError::InvalidTransaction(_)
119            | KoraError::ValidationError(_)
120            | KoraError::UnsupportedFeeToken(_)
121            | KoraError::InsufficientFunds(_) => invalid_request(err),
122
123            KoraError::InternalServerError(_) | KoraError::SerializationError(_) => {
124                internal_server_error(err)
125            }
126
127            _ => invalid_request(err),
128        }
129    }
130}
131
132pub fn invalid_request(e: KoraError) -> RpcError {
133    RpcError::Call(CallError::from_std_error(e))
134}
135
136pub fn internal_server_error(e: KoraError) -> RpcError {
137    RpcError::Call(CallError::from_std_error(e))
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct KoraResponse<T> {
142    pub data: Option<T>,
143    pub error: Option<KoraError>,
144}
145
146impl<T> KoraResponse<T> {
147    pub fn ok(data: T) -> Self {
148        Self { data: Some(data), error: None }
149    }
150
151    pub fn err(error: KoraError) -> Self {
152        Self { data: None, error: Some(error) }
153    }
154
155    pub fn from_result(result: Result<T, KoraError>) -> Self {
156        match result {
157            Ok(data) => Self::ok(data),
158            Err(error) => Self::err(error),
159        }
160    }
161}
162
163// Extension trait for Result<T, E> to convert to KoraResponse
164pub trait IntoKoraResponse<T> {
165    fn into_response(self) -> KoraResponse<T>;
166}
167
168impl<T, E: Into<KoraError>> IntoKoraResponse<T> for Result<T, E> {
169    fn into_response(self) -> KoraResponse<T> {
170        match self {
171            Ok(data) => KoraResponse::ok(data),
172            Err(e) => KoraResponse::err(e.into()),
173        }
174    }
175}
176
177impl From<anyhow::Error> for KoraError {
178    fn from(err: anyhow::Error) -> Self {
179        KoraError::SigningError(err.to_string())
180    }
181}
182
183impl From<kora_privy::PrivyError> for KoraError {
184    fn from(err: kora_privy::PrivyError) -> Self {
185        KoraError::SigningError(err.to_string())
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    #[test]
194    fn test_kora_response_ok() {
195        let response = KoraResponse::ok(42);
196        assert_eq!(response.data, Some(42));
197        assert_eq!(response.error, None);
198    }
199
200    #[test]
201    fn test_kora_response_err() {
202        let error = KoraError::AccountNotFound("test_account".to_string());
203        let response: KoraResponse<()> = KoraResponse::err(error.clone());
204        assert_eq!(response.data, None);
205        assert_eq!(response.error, Some(error));
206    }
207
208    #[test]
209    fn test_kora_response_from_result() {
210        let ok_response = KoraResponse::from_result(Ok(42));
211        assert_eq!(ok_response.data, Some(42));
212        assert_eq!(ok_response.error, None);
213
214        let error = KoraError::ValidationError("test error".to_string());
215        let err_response: KoraResponse<i32> = KoraResponse::from_result(Err(error.clone()));
216        assert_eq!(err_response.data, None);
217        assert_eq!(err_response.error, Some(error));
218    }
219
220    #[test]
221    fn test_into_kora_response() {
222        let result: Result<i32, KoraError> = Ok(42);
223        let response = result.into_response();
224        assert_eq!(response.data, Some(42));
225        assert_eq!(response.error, None);
226
227        let error = KoraError::SwapError("swap failed".to_string());
228        let result: Result<i32, KoraError> = Err(error.clone());
229        let response = result.into_response();
230        assert_eq!(response.data, None);
231        assert_eq!(response.error, Some(error));
232    }
233
234    #[test]
235    fn test_error_conversions() {
236        let client_error =
237            ClientError::from(std::io::Error::new(std::io::ErrorKind::Other, "test"));
238        let kora_error: KoraError = client_error.into();
239        assert!(matches!(kora_error, KoraError::RpcError(_)));
240
241        let signer_error = SignerError::Custom("test".to_string());
242        let kora_error: KoraError = signer_error.into();
243        assert!(matches!(kora_error, KoraError::SigningError(_)));
244
245        let io_error = std::io::Error::new(std::io::ErrorKind::Other, "test");
246        let kora_error: KoraError = io_error.into();
247        assert!(matches!(kora_error, KoraError::InternalServerError(_)));
248    }
249}