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
163pub 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}