1use rust_decimal::Decimal;
4use thiserror::Error;
5use uuid::Uuid;
6
7#[derive(Error, Debug)]
8pub enum CoreError {
9 #[error("Database error: {0}")]
10 Database(String),
11
12 #[error("Validation error: {0}")]
13 Validation(String),
14
15 #[error("Not found: {0}")]
16 NotFound(String),
17
18 #[error("Insufficient balance: required {required}, available {available}")]
19 InsufficientBalance {
20 required: Decimal,
21 available: Decimal,
22 },
23
24 #[error("Token already exists: {0}")]
25 TokenExists(String),
26
27 #[error("Invalid bonding curve parameters")]
28 InvalidCurveParams,
29
30 #[error("Circuit breaker triggered")]
31 CircuitBreakerTriggered,
32
33 #[error("Slippage exceeded: expected {expected}, actual {actual}")]
34 SlippageExceeded { expected: Decimal, actual: Decimal },
35
36 #[error("Order expired")]
37 OrderExpired,
38
39 #[error("Token paused")]
40 TokenPaused,
41
42 #[error("Reputation too low: required {required}, current {current}")]
43 ReputationTooLow { required: Decimal, current: Decimal },
44
45 #[error("Max supply reached")]
46 MaxSupplyReached,
47
48 #[error("Unauthorized")]
49 Unauthorized,
50
51 #[error("Serialization error: {0}")]
52 Serialization(String),
53
54 #[error("Calculation error: {0}")]
55 Calculation(String),
56
57 #[error("Order not found: {0}")]
59 OrderNotFound(Uuid),
60
61 #[error("Invalid order quantity: {0}")]
62 InvalidOrderQuantity(String),
63
64 #[error("Invalid price: {0}")]
65 InvalidPrice(String),
66
67 #[error("Order already filled")]
68 OrderAlreadyFilled,
69
70 #[error("Order already cancelled")]
71 OrderAlreadyCancelled,
72
73 #[error("Insufficient liquidity: {0}")]
74 InsufficientLiquidity(String),
75
76 #[error("Price impact too high: {impact}% exceeds maximum {max_impact}%")]
77 PriceImpactTooHigh {
78 impact: Decimal,
79 max_impact: Decimal,
80 },
81
82 #[error("Market is closed")]
83 MarketClosed,
84
85 #[error("Trading halted for token {0}")]
86 TradingHalted(Uuid),
87
88 #[error("Position limit exceeded: current {current}, maximum {maximum}")]
90 PositionLimitExceeded { current: Decimal, maximum: Decimal },
91
92 #[error("Daily trading limit exceeded: traded {traded}, limit {limit}")]
93 DailyTradingLimitExceeded { traded: Decimal, limit: Decimal },
94
95 #[error("Leverage ratio too high: {ratio} exceeds maximum {max_ratio}")]
96 ExcessiveLeverage { ratio: Decimal, max_ratio: Decimal },
97
98 #[error("Margin call: equity {equity} below maintenance margin {maintenance}")]
99 MarginCall {
100 equity: Decimal,
101 maintenance: Decimal,
102 },
103
104 #[error("Payment not found: {0}")]
106 PaymentNotFound(Uuid),
107
108 #[error("Payment already confirmed")]
109 PaymentAlreadyConfirmed,
110
111 #[error("Payment expired")]
112 PaymentExpired,
113
114 #[error("Insufficient confirmations: required {required}, current {current}")]
115 InsufficientConfirmations { required: u32, current: u32 },
116
117 #[error("KYC verification required")]
119 KycRequired,
120
121 #[error("KYC verification pending")]
122 KycPending,
123
124 #[error("KYC verification rejected: {0}")]
125 KycRejected(String),
126
127 #[error("Commitment not found: {0}")]
128 CommitmentNotFound(Uuid),
129
130 #[error("Commitment already verified")]
131 CommitmentAlreadyVerified,
132
133 #[error("Commitment deadline passed")]
134 CommitmentDeadlinePassed,
135
136 #[error("Rate limit exceeded: retry after {retry_after} seconds")]
138 RateLimitExceeded { retry_after: u64 },
139
140 #[error("Too many requests from user {0}")]
141 TooManyRequests(Uuid),
142
143 #[error("Resource locked: {0}")]
145 ResourceLocked(String),
146
147 #[error("Optimistic lock failed: resource was modified")]
148 OptimisticLockFailed,
149
150 #[error("Deadlock detected")]
151 DeadlockDetected,
152
153 #[error("Configuration error: {0}")]
155 Configuration(String),
156
157 #[error("Feature not enabled: {0}")]
158 FeatureNotEnabled(String),
159
160 #[error("Invalid state: {0}")]
162 InvalidState(String),
163
164 #[error("Already exists: {0}")]
165 AlreadyExists(String),
166
167 #[error("Invalid amount")]
169 InvalidAmount,
170
171 #[error("Invalid bridge route")]
172 InvalidBridgeRoute,
173
174 #[error("Bridge not supported")]
175 BridgeNotSupported,
176}
177
178impl From<sqlx::Error> for CoreError {
179 fn from(err: sqlx::Error) -> Self {
180 CoreError::Database(err.to_string())
181 }
182}
183
184impl From<serde_json::Error> for CoreError {
185 fn from(err: serde_json::Error) -> Self {
186 CoreError::Serialization(err.to_string())
187 }
188}
189
190impl CoreError {
191 pub fn is_retryable(&self) -> bool {
193 matches!(
194 self,
195 CoreError::Database(_)
196 | CoreError::DeadlockDetected
197 | CoreError::ResourceLocked(_)
198 | CoreError::OptimisticLockFailed
199 )
200 }
201
202 pub fn is_client_error(&self) -> bool {
204 matches!(
205 self,
206 CoreError::Validation(_)
207 | CoreError::NotFound(_)
208 | CoreError::Unauthorized
209 | CoreError::OrderNotFound(_)
210 | CoreError::InvalidOrderQuantity(_)
211 | CoreError::InvalidPrice(_)
212 | CoreError::OrderExpired
213 | CoreError::OrderAlreadyFilled
214 | CoreError::OrderAlreadyCancelled
215 | CoreError::InsufficientBalance { .. }
216 | CoreError::TokenExists(_)
217 | CoreError::TokenPaused
218 | CoreError::ReputationTooLow { .. }
219 | CoreError::MaxSupplyReached
220 | CoreError::SlippageExceeded { .. }
221 | CoreError::InsufficientLiquidity { .. }
222 | CoreError::PriceImpactTooHigh { .. }
223 | CoreError::PositionLimitExceeded { .. }
224 | CoreError::DailyTradingLimitExceeded { .. }
225 | CoreError::ExcessiveLeverage { .. }
226 | CoreError::PaymentNotFound(_)
227 | CoreError::PaymentExpired
228 | CoreError::InsufficientConfirmations { .. }
229 | CoreError::KycRequired
230 | CoreError::KycPending
231 | CoreError::KycRejected(_)
232 | CoreError::CommitmentNotFound(_)
233 | CoreError::CommitmentDeadlinePassed
234 | CoreError::RateLimitExceeded { .. }
235 | CoreError::TooManyRequests(_)
236 | CoreError::FeatureNotEnabled(_)
237 | CoreError::InvalidState(_)
238 | CoreError::InvalidAmount
239 | CoreError::InvalidBridgeRoute
240 | CoreError::BridgeNotSupported
241 )
242 }
243
244 pub fn is_server_error(&self) -> bool {
246 matches!(
247 self,
248 CoreError::Database(_)
249 | CoreError::Serialization(_)
250 | CoreError::Calculation(_)
251 | CoreError::DeadlockDetected
252 | CoreError::ResourceLocked(_)
253 | CoreError::OptimisticLockFailed
254 | CoreError::Configuration(_)
255 )
256 }
257
258 pub fn status_code(&self) -> u16 {
260 match self {
261 CoreError::NotFound(_)
262 | CoreError::OrderNotFound(_)
263 | CoreError::PaymentNotFound(_)
264 | CoreError::CommitmentNotFound(_) => 404,
265
266 CoreError::Unauthorized => 401,
267
268 CoreError::Validation(_)
269 | CoreError::InvalidOrderQuantity(_)
270 | CoreError::InvalidPrice(_)
271 | CoreError::OrderExpired
272 | CoreError::OrderAlreadyFilled
273 | CoreError::OrderAlreadyCancelled
274 | CoreError::InsufficientBalance { .. }
275 | CoreError::TokenExists(_)
276 | CoreError::InvalidCurveParams
277 | CoreError::TokenPaused
278 | CoreError::ReputationTooLow { .. }
279 | CoreError::MaxSupplyReached
280 | CoreError::SlippageExceeded { .. }
281 | CoreError::InsufficientLiquidity(_)
282 | CoreError::PriceImpactTooHigh { .. }
283 | CoreError::MarketClosed
284 | CoreError::TradingHalted(_)
285 | CoreError::PositionLimitExceeded { .. }
286 | CoreError::DailyTradingLimitExceeded { .. }
287 | CoreError::ExcessiveLeverage { .. }
288 | CoreError::MarginCall { .. }
289 | CoreError::PaymentExpired
290 | CoreError::InsufficientConfirmations { .. }
291 | CoreError::KycRequired
292 | CoreError::KycRejected(_)
293 | CoreError::CommitmentDeadlinePassed
294 | CoreError::FeatureNotEnabled(_)
295 | CoreError::InvalidState(_)
296 | CoreError::InvalidAmount
297 | CoreError::InvalidBridgeRoute
298 | CoreError::BridgeNotSupported => 400,
299
300 CoreError::PaymentAlreadyConfirmed
301 | CoreError::CommitmentAlreadyVerified
302 | CoreError::KycPending
303 | CoreError::AlreadyExists(_) => 409,
304
305 CoreError::RateLimitExceeded { .. } | CoreError::TooManyRequests(_) => 429,
306
307 CoreError::CircuitBreakerTriggered => 503,
308
309 CoreError::Database(_)
310 | CoreError::Serialization(_)
311 | CoreError::Calculation(_)
312 | CoreError::ResourceLocked(_)
313 | CoreError::OptimisticLockFailed
314 | CoreError::DeadlockDetected
315 | CoreError::Configuration(_) => 500,
316 }
317 }
318
319 pub fn category(&self) -> &'static str {
321 match self {
322 CoreError::Database(_) => "database",
323 CoreError::Validation(_)
324 | CoreError::InvalidOrderQuantity(_)
325 | CoreError::InvalidPrice(_) => "validation",
326 CoreError::NotFound(_)
327 | CoreError::OrderNotFound(_)
328 | CoreError::PaymentNotFound(_)
329 | CoreError::CommitmentNotFound(_) => "not_found",
330 CoreError::Unauthorized => "authorization",
331 CoreError::InsufficientBalance { .. } | CoreError::InsufficientLiquidity(_) => {
332 "insufficient_funds"
333 }
334 CoreError::OrderExpired
335 | CoreError::OrderAlreadyFilled
336 | CoreError::OrderAlreadyCancelled => "order_state",
337 CoreError::TokenExists(_) | CoreError::TokenPaused | CoreError::TradingHalted(_) => {
338 "token_state"
339 }
340 CoreError::InvalidCurveParams => "bonding_curve",
341 CoreError::CircuitBreakerTriggered => "circuit_breaker",
342 CoreError::SlippageExceeded { .. } | CoreError::PriceImpactTooHigh { .. } => {
343 "price_protection"
344 }
345 CoreError::ReputationTooLow { .. } => "reputation",
346 CoreError::MaxSupplyReached => "supply_limit",
347 CoreError::MarketClosed => "market_hours",
348 CoreError::PositionLimitExceeded { .. }
349 | CoreError::DailyTradingLimitExceeded { .. }
350 | CoreError::ExcessiveLeverage { .. }
351 | CoreError::MarginCall { .. } => "risk_management",
352 CoreError::PaymentAlreadyConfirmed
353 | CoreError::PaymentExpired
354 | CoreError::InsufficientConfirmations { .. } => "payment",
355 CoreError::KycRequired | CoreError::KycPending | CoreError::KycRejected(_) => "kyc",
356 CoreError::CommitmentAlreadyVerified | CoreError::CommitmentDeadlinePassed => {
357 "commitment"
358 }
359 CoreError::RateLimitExceeded { .. } | CoreError::TooManyRequests(_) => "rate_limiting",
360 CoreError::ResourceLocked(_)
361 | CoreError::OptimisticLockFailed
362 | CoreError::DeadlockDetected => "concurrency",
363 CoreError::Configuration(_) | CoreError::FeatureNotEnabled(_) => "configuration",
364 CoreError::Serialization(_) => "serialization",
365 CoreError::Calculation(_) => "calculation",
366 CoreError::InvalidState(_) => "state",
367 CoreError::AlreadyExists(_) => "duplicate",
368 CoreError::InvalidAmount
369 | CoreError::InvalidBridgeRoute
370 | CoreError::BridgeNotSupported => "bridge",
371 }
372 }
373}
374
375pub type Result<T> = std::result::Result<T, CoreError>;
376
377#[cfg(test)]
378mod tests {
379 use super::*;
380 use rust_decimal_macros::dec;
381
382 #[test]
383 fn test_error_retryable() {
384 let retryable_error = CoreError::Database("Connection failed".to_string());
385 assert!(retryable_error.is_retryable());
386
387 let non_retryable_error = CoreError::Validation("Invalid input".to_string());
388 assert!(!non_retryable_error.is_retryable());
389 }
390
391 #[test]
392 fn test_error_categorization() {
393 let client_error = CoreError::Validation("Invalid input".to_string());
394 assert!(client_error.is_client_error());
395 assert!(!client_error.is_server_error());
396
397 let server_error = CoreError::Database("Connection failed".to_string());
398 assert!(!server_error.is_client_error());
399 assert!(server_error.is_server_error());
400 }
401
402 #[test]
403 fn test_error_status_codes() {
404 assert_eq!(
405 CoreError::NotFound("Resource".to_string()).status_code(),
406 404
407 );
408 assert_eq!(CoreError::Unauthorized.status_code(), 401);
409 assert_eq!(
410 CoreError::Validation("Invalid".to_string()).status_code(),
411 400
412 );
413 assert_eq!(CoreError::Database("Error".to_string()).status_code(), 500);
414 assert_eq!(
415 CoreError::RateLimitExceeded { retry_after: 60 }.status_code(),
416 429
417 );
418 }
419
420 #[test]
421 fn test_error_categories() {
422 assert_eq!(
423 CoreError::Database("Error".to_string()).category(),
424 "database"
425 );
426 assert_eq!(
427 CoreError::Validation("Error".to_string()).category(),
428 "validation"
429 );
430 assert_eq!(
431 CoreError::InsufficientBalance {
432 required: dec!(100),
433 available: dec!(50)
434 }
435 .category(),
436 "insufficient_funds"
437 );
438 assert_eq!(
439 CoreError::CircuitBreakerTriggered.category(),
440 "circuit_breaker"
441 );
442 }
443
444 #[test]
445 fn test_serde_json_error_conversion() {
446 let json_err = serde_json::from_str::<serde_json::Value>("invalid json");
447 assert!(json_err.is_err());
448
449 let core_err: CoreError = json_err.unwrap_err().into();
450 matches!(core_err, CoreError::Serialization(_));
451 }
452}