1use rust_decimal::Decimal;
4use thiserror::Error;
5use uuid::Uuid;
6
7#[derive(Error, Debug)]
9pub enum CoreError {
10 #[error("Database error: {0}")]
12 Database(String),
13
14 #[error("Validation error: {0}")]
16 Validation(String),
17
18 #[error("Not found: {0}")]
20 NotFound(String),
21
22 #[error("Insufficient balance: required {required}, available {available}")]
24 InsufficientBalance {
25 required: Decimal,
27 available: Decimal,
29 },
30
31 #[error("Token already exists: {0}")]
33 TokenExists(String),
34
35 #[error("Invalid bonding curve parameters")]
37 InvalidCurveParams,
38
39 #[error("Circuit breaker triggered")]
41 CircuitBreakerTriggered,
42
43 #[error("Slippage exceeded: expected {expected}, actual {actual}")]
45 SlippageExceeded {
46 expected: Decimal,
48 actual: Decimal,
50 },
51
52 #[error("Order expired")]
54 OrderExpired,
55
56 #[error("Token paused")]
58 TokenPaused,
59
60 #[error("Reputation too low: required {required}, current {current}")]
62 ReputationTooLow {
63 required: Decimal,
65 current: Decimal,
67 },
68
69 #[error("Max supply reached")]
71 MaxSupplyReached,
72
73 #[error("Unauthorized")]
75 Unauthorized,
76
77 #[error("Serialization error: {0}")]
79 Serialization(String),
80
81 #[error("Calculation error: {0}")]
83 Calculation(String),
84
85 #[error("Order not found: {0}")]
88 OrderNotFound(Uuid),
89
90 #[error("Invalid order quantity: {0}")]
92 InvalidOrderQuantity(String),
93
94 #[error("Invalid price: {0}")]
96 InvalidPrice(String),
97
98 #[error("Order already filled")]
100 OrderAlreadyFilled,
101
102 #[error("Order already cancelled")]
104 OrderAlreadyCancelled,
105
106 #[error("Insufficient liquidity: {0}")]
108 InsufficientLiquidity(String),
109
110 #[error("Price impact too high: {impact}% exceeds maximum {max_impact}%")]
112 PriceImpactTooHigh {
113 impact: Decimal,
115 max_impact: Decimal,
117 },
118
119 #[error("Market is closed")]
121 MarketClosed,
122
123 #[error("Trading halted for token {0}")]
125 TradingHalted(Uuid),
126
127 #[error("Position limit exceeded: current {current}, maximum {maximum}")]
130 PositionLimitExceeded {
131 current: Decimal,
133 maximum: Decimal,
135 },
136
137 #[error("Daily trading limit exceeded: traded {traded}, limit {limit}")]
139 DailyTradingLimitExceeded {
140 traded: Decimal,
142 limit: Decimal,
144 },
145
146 #[error("Leverage ratio too high: {ratio} exceeds maximum {max_ratio}")]
148 ExcessiveLeverage {
149 ratio: Decimal,
151 max_ratio: Decimal,
153 },
154
155 #[error("Margin call: equity {equity} below maintenance margin {maintenance}")]
157 MarginCall {
158 equity: Decimal,
160 maintenance: Decimal,
162 },
163
164 #[error("Payment not found: {0}")]
167 PaymentNotFound(Uuid),
168
169 #[error("Payment already confirmed")]
171 PaymentAlreadyConfirmed,
172
173 #[error("Payment expired")]
175 PaymentExpired,
176
177 #[error("Insufficient confirmations: required {required}, current {current}")]
179 InsufficientConfirmations {
180 required: u32,
182 current: u32,
184 },
185
186 #[error("KYC verification required")]
189 KycRequired,
190
191 #[error("KYC verification pending")]
193 KycPending,
194
195 #[error("KYC verification rejected: {0}")]
197 KycRejected(String),
198
199 #[error("Commitment not found: {0}")]
201 CommitmentNotFound(Uuid),
202
203 #[error("Commitment already verified")]
205 CommitmentAlreadyVerified,
206
207 #[error("Commitment deadline passed")]
209 CommitmentDeadlinePassed,
210
211 #[error("Rate limit exceeded: retry after {retry_after} seconds")]
214 RateLimitExceeded {
215 retry_after: u64,
217 },
218
219 #[error("Too many requests from user {0}")]
221 TooManyRequests(Uuid),
222
223 #[error("Resource locked: {0}")]
226 ResourceLocked(String),
227
228 #[error("Optimistic lock failed: resource was modified")]
230 OptimisticLockFailed,
231
232 #[error("Deadlock detected")]
234 DeadlockDetected,
235
236 #[error("Configuration error: {0}")]
239 Configuration(String),
240
241 #[error("Feature not enabled: {0}")]
243 FeatureNotEnabled(String),
244
245 #[error("Invalid state: {0}")]
248 InvalidState(String),
249
250 #[error("Already exists: {0}")]
252 AlreadyExists(String),
253
254 #[error("Invalid amount")]
257 InvalidAmount,
258
259 #[error("Invalid bridge route")]
261 InvalidBridgeRoute,
262
263 #[error("Bridge not supported")]
265 BridgeNotSupported,
266}
267
268impl From<sqlx::Error> for CoreError {
269 fn from(err: sqlx::Error) -> Self {
270 CoreError::Database(err.to_string())
271 }
272}
273
274impl From<serde_json::Error> for CoreError {
275 fn from(err: serde_json::Error) -> Self {
276 CoreError::Serialization(err.to_string())
277 }
278}
279
280impl CoreError {
281 pub fn is_retryable(&self) -> bool {
283 matches!(
284 self,
285 CoreError::Database(_)
286 | CoreError::DeadlockDetected
287 | CoreError::ResourceLocked(_)
288 | CoreError::OptimisticLockFailed
289 )
290 }
291
292 pub fn is_client_error(&self) -> bool {
294 matches!(
295 self,
296 CoreError::Validation(_)
297 | CoreError::NotFound(_)
298 | CoreError::Unauthorized
299 | CoreError::OrderNotFound(_)
300 | CoreError::InvalidOrderQuantity(_)
301 | CoreError::InvalidPrice(_)
302 | CoreError::OrderExpired
303 | CoreError::OrderAlreadyFilled
304 | CoreError::OrderAlreadyCancelled
305 | CoreError::InsufficientBalance { .. }
306 | CoreError::TokenExists(_)
307 | CoreError::TokenPaused
308 | CoreError::ReputationTooLow { .. }
309 | CoreError::MaxSupplyReached
310 | CoreError::SlippageExceeded { .. }
311 | CoreError::InsufficientLiquidity { .. }
312 | CoreError::PriceImpactTooHigh { .. }
313 | CoreError::PositionLimitExceeded { .. }
314 | CoreError::DailyTradingLimitExceeded { .. }
315 | CoreError::ExcessiveLeverage { .. }
316 | CoreError::PaymentNotFound(_)
317 | CoreError::PaymentExpired
318 | CoreError::InsufficientConfirmations { .. }
319 | CoreError::KycRequired
320 | CoreError::KycPending
321 | CoreError::KycRejected(_)
322 | CoreError::CommitmentNotFound(_)
323 | CoreError::CommitmentDeadlinePassed
324 | CoreError::RateLimitExceeded { .. }
325 | CoreError::TooManyRequests(_)
326 | CoreError::FeatureNotEnabled(_)
327 | CoreError::InvalidState(_)
328 | CoreError::InvalidAmount
329 | CoreError::InvalidBridgeRoute
330 | CoreError::BridgeNotSupported
331 )
332 }
333
334 pub fn is_server_error(&self) -> bool {
336 matches!(
337 self,
338 CoreError::Database(_)
339 | CoreError::Serialization(_)
340 | CoreError::Calculation(_)
341 | CoreError::DeadlockDetected
342 | CoreError::ResourceLocked(_)
343 | CoreError::OptimisticLockFailed
344 | CoreError::Configuration(_)
345 )
346 }
347
348 pub fn status_code(&self) -> u16 {
350 match self {
351 CoreError::NotFound(_)
352 | CoreError::OrderNotFound(_)
353 | CoreError::PaymentNotFound(_)
354 | CoreError::CommitmentNotFound(_) => 404,
355
356 CoreError::Unauthorized => 401,
357
358 CoreError::Validation(_)
359 | CoreError::InvalidOrderQuantity(_)
360 | CoreError::InvalidPrice(_)
361 | CoreError::OrderExpired
362 | CoreError::OrderAlreadyFilled
363 | CoreError::OrderAlreadyCancelled
364 | CoreError::InsufficientBalance { .. }
365 | CoreError::TokenExists(_)
366 | CoreError::InvalidCurveParams
367 | CoreError::TokenPaused
368 | CoreError::ReputationTooLow { .. }
369 | CoreError::MaxSupplyReached
370 | CoreError::SlippageExceeded { .. }
371 | CoreError::InsufficientLiquidity(_)
372 | CoreError::PriceImpactTooHigh { .. }
373 | CoreError::MarketClosed
374 | CoreError::TradingHalted(_)
375 | CoreError::PositionLimitExceeded { .. }
376 | CoreError::DailyTradingLimitExceeded { .. }
377 | CoreError::ExcessiveLeverage { .. }
378 | CoreError::MarginCall { .. }
379 | CoreError::PaymentExpired
380 | CoreError::InsufficientConfirmations { .. }
381 | CoreError::KycRequired
382 | CoreError::KycRejected(_)
383 | CoreError::CommitmentDeadlinePassed
384 | CoreError::FeatureNotEnabled(_)
385 | CoreError::InvalidState(_)
386 | CoreError::InvalidAmount
387 | CoreError::InvalidBridgeRoute
388 | CoreError::BridgeNotSupported => 400,
389
390 CoreError::PaymentAlreadyConfirmed
391 | CoreError::CommitmentAlreadyVerified
392 | CoreError::KycPending
393 | CoreError::AlreadyExists(_) => 409,
394
395 CoreError::RateLimitExceeded { .. } | CoreError::TooManyRequests(_) => 429,
396
397 CoreError::CircuitBreakerTriggered => 503,
398
399 CoreError::Database(_)
400 | CoreError::Serialization(_)
401 | CoreError::Calculation(_)
402 | CoreError::ResourceLocked(_)
403 | CoreError::OptimisticLockFailed
404 | CoreError::DeadlockDetected
405 | CoreError::Configuration(_) => 500,
406 }
407 }
408
409 pub fn category(&self) -> &'static str {
411 match self {
412 CoreError::Database(_) => "database",
413 CoreError::Validation(_)
414 | CoreError::InvalidOrderQuantity(_)
415 | CoreError::InvalidPrice(_) => "validation",
416 CoreError::NotFound(_)
417 | CoreError::OrderNotFound(_)
418 | CoreError::PaymentNotFound(_)
419 | CoreError::CommitmentNotFound(_) => "not_found",
420 CoreError::Unauthorized => "authorization",
421 CoreError::InsufficientBalance { .. } | CoreError::InsufficientLiquidity(_) => {
422 "insufficient_funds"
423 }
424 CoreError::OrderExpired
425 | CoreError::OrderAlreadyFilled
426 | CoreError::OrderAlreadyCancelled => "order_state",
427 CoreError::TokenExists(_) | CoreError::TokenPaused | CoreError::TradingHalted(_) => {
428 "token_state"
429 }
430 CoreError::InvalidCurveParams => "bonding_curve",
431 CoreError::CircuitBreakerTriggered => "circuit_breaker",
432 CoreError::SlippageExceeded { .. } | CoreError::PriceImpactTooHigh { .. } => {
433 "price_protection"
434 }
435 CoreError::ReputationTooLow { .. } => "reputation",
436 CoreError::MaxSupplyReached => "supply_limit",
437 CoreError::MarketClosed => "market_hours",
438 CoreError::PositionLimitExceeded { .. }
439 | CoreError::DailyTradingLimitExceeded { .. }
440 | CoreError::ExcessiveLeverage { .. }
441 | CoreError::MarginCall { .. } => "risk_management",
442 CoreError::PaymentAlreadyConfirmed
443 | CoreError::PaymentExpired
444 | CoreError::InsufficientConfirmations { .. } => "payment",
445 CoreError::KycRequired | CoreError::KycPending | CoreError::KycRejected(_) => "kyc",
446 CoreError::CommitmentAlreadyVerified | CoreError::CommitmentDeadlinePassed => {
447 "commitment"
448 }
449 CoreError::RateLimitExceeded { .. } | CoreError::TooManyRequests(_) => "rate_limiting",
450 CoreError::ResourceLocked(_)
451 | CoreError::OptimisticLockFailed
452 | CoreError::DeadlockDetected => "concurrency",
453 CoreError::Configuration(_) | CoreError::FeatureNotEnabled(_) => "configuration",
454 CoreError::Serialization(_) => "serialization",
455 CoreError::Calculation(_) => "calculation",
456 CoreError::InvalidState(_) => "state",
457 CoreError::AlreadyExists(_) => "duplicate",
458 CoreError::InvalidAmount
459 | CoreError::InvalidBridgeRoute
460 | CoreError::BridgeNotSupported => "bridge",
461 }
462 }
463}
464
465pub type Result<T> = std::result::Result<T, CoreError>;
467
468#[cfg(test)]
469mod tests {
470 use super::*;
471 use rust_decimal_macros::dec;
472
473 #[test]
474 fn test_error_retryable() {
475 let retryable_error = CoreError::Database("Connection failed".to_string());
476 assert!(retryable_error.is_retryable());
477
478 let non_retryable_error = CoreError::Validation("Invalid input".to_string());
479 assert!(!non_retryable_error.is_retryable());
480 }
481
482 #[test]
483 fn test_error_categorization() {
484 let client_error = CoreError::Validation("Invalid input".to_string());
485 assert!(client_error.is_client_error());
486 assert!(!client_error.is_server_error());
487
488 let server_error = CoreError::Database("Connection failed".to_string());
489 assert!(!server_error.is_client_error());
490 assert!(server_error.is_server_error());
491 }
492
493 #[test]
494 fn test_error_status_codes() {
495 assert_eq!(
496 CoreError::NotFound("Resource".to_string()).status_code(),
497 404
498 );
499 assert_eq!(CoreError::Unauthorized.status_code(), 401);
500 assert_eq!(
501 CoreError::Validation("Invalid".to_string()).status_code(),
502 400
503 );
504 assert_eq!(CoreError::Database("Error".to_string()).status_code(), 500);
505 assert_eq!(
506 CoreError::RateLimitExceeded { retry_after: 60 }.status_code(),
507 429
508 );
509 }
510
511 #[test]
512 fn test_error_categories() {
513 assert_eq!(
514 CoreError::Database("Error".to_string()).category(),
515 "database"
516 );
517 assert_eq!(
518 CoreError::Validation("Error".to_string()).category(),
519 "validation"
520 );
521 assert_eq!(
522 CoreError::InsufficientBalance {
523 required: dec!(100),
524 available: dec!(50)
525 }
526 .category(),
527 "insufficient_funds"
528 );
529 assert_eq!(
530 CoreError::CircuitBreakerTriggered.category(),
531 "circuit_breaker"
532 );
533 }
534
535 #[test]
536 fn test_serde_json_error_conversion() {
537 let json_err = serde_json::from_str::<serde_json::Value>("invalid json");
538 assert!(json_err.is_err());
539
540 let core_err: CoreError = json_err.unwrap_err().into();
541 matches!(core_err, CoreError::Serialization(_));
542 }
543}