openzeppelin_relayer/models/error/
transaction.rs1use crate::{
2 jobs::JobProducerError,
3 models::{SignerError, SignerFactoryError},
4 services::{ProviderError, SolanaProviderError},
5};
6
7use super::{ApiError, RepositoryError, StellarProviderError};
8use eyre::Report;
9use serde::Serialize;
10use soroban_rs::xdr;
11use thiserror::Error;
12
13#[derive(Error, Debug, Serialize)]
14pub enum TransactionError {
15 #[error("Transaction validation error: {0}")]
16 ValidationError(String),
17
18 #[error("Network configuration error: {0}")]
19 NetworkConfiguration(String),
20
21 #[error("Job producer error: {0}")]
22 JobProducerError(#[from] JobProducerError),
23
24 #[error("Invalid transaction type: {0}")]
25 InvalidType(String),
26
27 #[error("Underlying provider error: {0}")]
28 UnderlyingProvider(#[from] ProviderError),
29
30 #[error("Underlying Solana provider error: {0}")]
31 UnderlyingSolanaProvider(#[from] SolanaProviderError),
32
33 #[error("Unexpected error: {0}")]
34 UnexpectedError(String),
35
36 #[error("Not supported: {0}")]
37 NotSupported(String),
38
39 #[error("Signer error: {0}")]
40 SignerError(String),
41
42 #[error("Insufficient balance: {0}")]
43 InsufficientBalance(String),
44
45 #[error("Stellar transaction simulation failed: {0}")]
46 SimulationFailed(String),
47}
48
49impl From<TransactionError> for ApiError {
50 fn from(error: TransactionError) -> Self {
51 match error {
52 TransactionError::ValidationError(msg) => ApiError::BadRequest(msg),
53 TransactionError::NetworkConfiguration(msg) => ApiError::InternalError(msg),
54 TransactionError::JobProducerError(msg) => ApiError::InternalError(msg.to_string()),
55 TransactionError::InvalidType(msg) => ApiError::InternalError(msg),
56 TransactionError::UnderlyingProvider(err) => ApiError::InternalError(err.to_string()),
57 TransactionError::UnderlyingSolanaProvider(err) => {
58 ApiError::InternalError(err.to_string())
59 }
60 TransactionError::NotSupported(msg) => ApiError::BadRequest(msg),
61 TransactionError::UnexpectedError(msg) => ApiError::InternalError(msg),
62 TransactionError::SignerError(msg) => ApiError::InternalError(msg),
63 TransactionError::InsufficientBalance(msg) => ApiError::BadRequest(msg),
64 TransactionError::SimulationFailed(msg) => ApiError::BadRequest(msg),
65 }
66 }
67}
68
69impl From<RepositoryError> for TransactionError {
70 fn from(error: RepositoryError) -> Self {
71 TransactionError::ValidationError(error.to_string())
72 }
73}
74
75impl From<Report> for TransactionError {
76 fn from(err: Report) -> Self {
77 TransactionError::UnexpectedError(err.to_string())
78 }
79}
80
81impl From<SignerFactoryError> for TransactionError {
82 fn from(error: SignerFactoryError) -> Self {
83 TransactionError::SignerError(error.to_string())
84 }
85}
86
87impl From<SignerError> for TransactionError {
88 fn from(error: SignerError) -> Self {
89 TransactionError::SignerError(error.to_string())
90 }
91}
92
93impl From<StellarProviderError> for TransactionError {
94 fn from(error: StellarProviderError) -> Self {
95 match error {
96 StellarProviderError::SimulationFailed(msg) => TransactionError::SimulationFailed(msg),
97 StellarProviderError::InsufficientBalance(msg) => {
98 TransactionError::InsufficientBalance(msg)
99 }
100 StellarProviderError::BadSeq(msg) => TransactionError::ValidationError(msg),
101 StellarProviderError::RpcError(msg) | StellarProviderError::Unknown(msg) => {
102 TransactionError::UnderlyingProvider(ProviderError::NetworkConfiguration(msg))
103 }
104 }
105 }
106}
107
108impl From<xdr::Error> for TransactionError {
109 fn from(error: xdr::Error) -> Self {
110 TransactionError::ValidationError(format!("XDR error: {}", error))
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn test_transaction_error_display() {
120 let test_cases = vec![
121 (
122 TransactionError::ValidationError("invalid input".to_string()),
123 "Transaction validation error: invalid input",
124 ),
125 (
126 TransactionError::NetworkConfiguration("wrong network".to_string()),
127 "Network configuration error: wrong network",
128 ),
129 (
130 TransactionError::InvalidType("unknown type".to_string()),
131 "Invalid transaction type: unknown type",
132 ),
133 (
134 TransactionError::UnexpectedError("something went wrong".to_string()),
135 "Unexpected error: something went wrong",
136 ),
137 (
138 TransactionError::NotSupported("feature unavailable".to_string()),
139 "Not supported: feature unavailable",
140 ),
141 (
142 TransactionError::SignerError("key error".to_string()),
143 "Signer error: key error",
144 ),
145 (
146 TransactionError::InsufficientBalance("not enough funds".to_string()),
147 "Insufficient balance: not enough funds",
148 ),
149 (
150 TransactionError::SimulationFailed("sim failed".to_string()),
151 "Stellar transaction simulation failed: sim failed",
152 ),
153 ];
154
155 for (error, expected_message) in test_cases {
156 assert_eq!(error.to_string(), expected_message);
157 }
158 }
159
160 #[test]
161 fn test_transaction_error_to_api_error() {
162 let test_cases = vec![
163 (
164 TransactionError::ValidationError("invalid input".to_string()),
165 ApiError::BadRequest("invalid input".to_string()),
166 ),
167 (
168 TransactionError::NetworkConfiguration("wrong network".to_string()),
169 ApiError::InternalError("wrong network".to_string()),
170 ),
171 (
172 TransactionError::InvalidType("unknown type".to_string()),
173 ApiError::InternalError("unknown type".to_string()),
174 ),
175 (
176 TransactionError::UnexpectedError("something went wrong".to_string()),
177 ApiError::InternalError("something went wrong".to_string()),
178 ),
179 (
180 TransactionError::NotSupported("feature unavailable".to_string()),
181 ApiError::BadRequest("feature unavailable".to_string()),
182 ),
183 (
184 TransactionError::SignerError("key error".to_string()),
185 ApiError::InternalError("key error".to_string()),
186 ),
187 (
188 TransactionError::InsufficientBalance("not enough funds".to_string()),
189 ApiError::BadRequest("not enough funds".to_string()),
190 ),
191 (
192 TransactionError::SimulationFailed("boom".to_string()),
193 ApiError::BadRequest("boom".to_string()),
194 ),
195 ];
196
197 for (tx_error, expected_api_error) in test_cases {
198 let api_error = ApiError::from(tx_error);
199
200 match (&api_error, &expected_api_error) {
201 (ApiError::BadRequest(actual), ApiError::BadRequest(expected)) => {
202 assert_eq!(actual, expected);
203 }
204 (ApiError::InternalError(actual), ApiError::InternalError(expected)) => {
205 assert_eq!(actual, expected);
206 }
207 _ => panic!(
208 "Error types don't match: {:?} vs {:?}",
209 api_error, expected_api_error
210 ),
211 }
212 }
213 }
214
215 #[test]
216 fn test_repository_error_to_transaction_error() {
217 let repo_error = RepositoryError::NotFound("record not found".to_string());
218 let tx_error = TransactionError::from(repo_error);
219
220 match tx_error {
221 TransactionError::ValidationError(msg) => {
222 assert_eq!(msg, "Entity not found: record not found");
223 }
224 _ => panic!("Expected TransactionError::ValidationError"),
225 }
226 }
227
228 #[test]
229 fn test_report_to_transaction_error() {
230 let report = Report::msg("An unexpected error occurred");
231 let tx_error = TransactionError::from(report);
232
233 match tx_error {
234 TransactionError::UnexpectedError(msg) => {
235 assert!(msg.contains("An unexpected error occurred"));
236 }
237 _ => panic!("Expected TransactionError::UnexpectedError"),
238 }
239 }
240
241 #[test]
242 fn test_signer_factory_error_to_transaction_error() {
243 let factory_error = SignerFactoryError::InvalidConfig("missing key".to_string());
244 let tx_error = TransactionError::from(factory_error);
245
246 match tx_error {
247 TransactionError::SignerError(msg) => {
248 assert!(msg.contains("missing key"));
249 }
250 _ => panic!("Expected TransactionError::SignerError"),
251 }
252 }
253
254 #[test]
255 fn test_signer_error_to_transaction_error() {
256 let signer_error = SignerError::KeyError("invalid key format".to_string());
257 let tx_error = TransactionError::from(signer_error);
258
259 match tx_error {
260 TransactionError::SignerError(msg) => {
261 assert!(msg.contains("invalid key format"));
262 }
263 _ => panic!("Expected TransactionError::SignerError"),
264 }
265 }
266
267 #[test]
268 fn test_provider_error_conversion() {
269 let provider_error = ProviderError::NetworkConfiguration("timeout".to_string());
270 let tx_error = TransactionError::from(provider_error);
271
272 match tx_error {
273 TransactionError::UnderlyingProvider(err) => {
274 assert!(err.to_string().contains("timeout"));
275 }
276 _ => panic!("Expected TransactionError::UnderlyingProvider"),
277 }
278 }
279
280 #[test]
281 fn test_solana_provider_error_conversion() {
282 let solana_error = SolanaProviderError::RpcError("invalid response".to_string());
283 let tx_error = TransactionError::from(solana_error);
284
285 match tx_error {
286 TransactionError::UnderlyingSolanaProvider(err) => {
287 assert!(err.to_string().contains("invalid response"));
288 }
289 _ => panic!("Expected TransactionError::UnderlyingSolanaProvider"),
290 }
291 }
292
293 #[test]
294 fn test_job_producer_error_conversion() {
295 let job_error = JobProducerError::QueueError("queue full".to_string());
296 let tx_error = TransactionError::from(job_error);
297
298 match tx_error {
299 TransactionError::JobProducerError(err) => {
300 assert!(err.to_string().contains("queue full"));
301 }
302 _ => panic!("Expected TransactionError::JobProducerError"),
303 }
304 }
305
306 #[test]
307 fn test_xdr_error_conversion() {
308 use soroban_rs::xdr::{Limits, ReadXdr, TransactionEnvelope};
309
310 let xdr_error =
312 TransactionEnvelope::from_xdr_base64("invalid_base64", Limits::none()).unwrap_err();
313
314 let tx_error = TransactionError::from(xdr_error);
315
316 match tx_error {
317 TransactionError::ValidationError(msg) => {
318 assert!(msg.contains("XDR error:"));
319 }
320 _ => panic!("Expected TransactionError::ValidationError"),
321 }
322 }
323}