1use pricelevel::{PriceLevelError, Side};
4use std::fmt;
5
6#[derive(Debug)]
8#[non_exhaustive]
9pub enum OrderBookError {
10 PriceLevelError(PriceLevelError),
12
13 OrderNotFound(String),
15
16 InvalidPriceLevel(u128),
18
19 PriceCrossing {
21 price: u128,
23 side: Side,
25 opposite_price: u128,
27 },
28
29 InsufficientLiquidity {
31 side: Side,
33 requested: u64,
35 available: u64,
37 },
38
39 InvalidOperation {
41 message: String,
43 },
44
45 SerializationError {
47 message: String,
49 },
50
51 DeserializationError {
53 message: String,
55 },
56
57 ChecksumMismatch {
59 expected: String,
61 actual: String,
63 },
64
65 InvalidTickSize {
67 price: u128,
69 tick_size: u128,
71 },
72
73 InvalidLotSize {
75 quantity: u64,
77 lot_size: u64,
79 },
80
81 OrderSizeOutOfRange {
83 quantity: u64,
85 min: Option<u64>,
87 max: Option<u64>,
89 },
90
91 MissingUserId {
95 order_id: pricelevel::Id,
97 },
98
99 SelfTradePrevented {
102 mode: crate::orderbook::stp::STPMode,
104 taker_order_id: pricelevel::Id,
106 user_id: pricelevel::Hash32,
108 },
109
110 #[cfg(feature = "nats")]
112 NatsPublishError {
113 message: String,
115 },
116
117 #[cfg(feature = "nats")]
119 NatsSerializationError {
120 message: String,
122 },
123}
124
125impl fmt::Display for OrderBookError {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 match self {
128 OrderBookError::PriceLevelError(err) => write!(f, "Price level error: {err}"),
129 OrderBookError::OrderNotFound(id) => write!(f, "Order not found: {id}"),
130 OrderBookError::InvalidPriceLevel(price) => write!(f, "Invalid price level: {price}"),
131 OrderBookError::PriceCrossing {
132 price,
133 side,
134 opposite_price,
135 } => {
136 write!(
137 f,
138 "Price crossing: {side} {price} would cross opposite at {opposite_price}"
139 )
140 }
141 OrderBookError::InsufficientLiquidity {
142 side,
143 requested,
144 available,
145 } => {
146 write!(
147 f,
148 "Insufficient liquidity for {side} order: requested {requested}, available {available}"
149 )
150 }
151 OrderBookError::InvalidOperation { message } => {
152 write!(f, "Invalid operation: {message}")
153 }
154 OrderBookError::SerializationError { message } => {
155 write!(f, "Serialization error: {message}")
156 }
157 OrderBookError::DeserializationError { message } => {
158 write!(f, "Deserialization error: {message}")
159 }
160 OrderBookError::ChecksumMismatch { expected, actual } => {
161 write!(
162 f,
163 "Checksum mismatch: expected {expected}, but computed {actual}"
164 )
165 }
166 OrderBookError::InvalidTickSize { price, tick_size } => {
167 write!(
168 f,
169 "invalid tick size: price {price} is not a multiple of tick size {tick_size}"
170 )
171 }
172 OrderBookError::InvalidLotSize { quantity, lot_size } => {
173 write!(
174 f,
175 "invalid lot size: quantity {quantity} is not a multiple of lot size {lot_size}"
176 )
177 }
178 OrderBookError::OrderSizeOutOfRange { quantity, min, max } => {
179 write!(
180 f,
181 "order size out of range: quantity {quantity}, min {min:?}, max {max:?}"
182 )
183 }
184 OrderBookError::MissingUserId { order_id } => {
185 write!(
186 f,
187 "missing user_id: order {order_id} rejected because STP is enabled and user_id is zero"
188 )
189 }
190 OrderBookError::SelfTradePrevented {
191 mode,
192 taker_order_id,
193 user_id,
194 } => {
195 write!(
196 f,
197 "self-trade prevented ({mode}): taker {taker_order_id}, user {user_id}"
198 )
199 }
200 #[cfg(feature = "nats")]
201 OrderBookError::NatsPublishError { message } => {
202 write!(f, "nats publish error: {message}")
203 }
204 #[cfg(feature = "nats")]
205 OrderBookError::NatsSerializationError { message } => {
206 write!(f, "nats serialization error: {message}")
207 }
208 }
209 }
210}
211
212impl std::error::Error for OrderBookError {}
213
214impl From<PriceLevelError> for OrderBookError {
215 fn from(err: PriceLevelError) -> Self {
216 OrderBookError::PriceLevelError(err)
217 }
218}
219
220impl Clone for OrderBookError {
221 fn clone(&self) -> Self {
222 match self {
223 OrderBookError::PriceLevelError(err) => {
224 let cloned_err = match err {
226 PriceLevelError::ParseError { message } => PriceLevelError::ParseError {
227 message: message.clone(),
228 },
229 PriceLevelError::InvalidFormat => PriceLevelError::InvalidFormat,
230 PriceLevelError::UnknownOrderType(s) => {
231 PriceLevelError::UnknownOrderType(s.clone())
232 }
233 PriceLevelError::MissingField(s) => PriceLevelError::MissingField(s.clone()),
234 PriceLevelError::InvalidFieldValue { field, value } => {
235 PriceLevelError::InvalidFieldValue {
236 field: field.clone(),
237 value: value.clone(),
238 }
239 }
240 PriceLevelError::InvalidOperation { message } => {
241 PriceLevelError::InvalidOperation {
242 message: message.clone(),
243 }
244 }
245 PriceLevelError::SerializationError { message } => {
246 PriceLevelError::SerializationError {
247 message: message.clone(),
248 }
249 }
250 PriceLevelError::DeserializationError { message } => {
251 PriceLevelError::DeserializationError {
252 message: message.clone(),
253 }
254 }
255 PriceLevelError::ChecksumMismatch { expected, actual } => {
256 PriceLevelError::ChecksumMismatch {
257 expected: expected.clone(),
258 actual: actual.clone(),
259 }
260 }
261 };
262 OrderBookError::PriceLevelError(cloned_err)
263 }
264 OrderBookError::OrderNotFound(s) => OrderBookError::OrderNotFound(s.clone()),
265 OrderBookError::InvalidPriceLevel(p) => OrderBookError::InvalidPriceLevel(*p),
266 OrderBookError::PriceCrossing {
267 price,
268 side,
269 opposite_price,
270 } => OrderBookError::PriceCrossing {
271 price: *price,
272 side: *side,
273 opposite_price: *opposite_price,
274 },
275 OrderBookError::InsufficientLiquidity {
276 side,
277 requested,
278 available,
279 } => OrderBookError::InsufficientLiquidity {
280 side: *side,
281 requested: *requested,
282 available: *available,
283 },
284 OrderBookError::InvalidOperation { message } => OrderBookError::InvalidOperation {
285 message: message.clone(),
286 },
287 OrderBookError::SerializationError { message } => OrderBookError::SerializationError {
288 message: message.clone(),
289 },
290 OrderBookError::DeserializationError { message } => {
291 OrderBookError::DeserializationError {
292 message: message.clone(),
293 }
294 }
295 OrderBookError::ChecksumMismatch { expected, actual } => {
296 OrderBookError::ChecksumMismatch {
297 expected: expected.clone(),
298 actual: actual.clone(),
299 }
300 }
301 OrderBookError::InvalidTickSize { price, tick_size } => {
302 OrderBookError::InvalidTickSize {
303 price: *price,
304 tick_size: *tick_size,
305 }
306 }
307 OrderBookError::InvalidLotSize { quantity, lot_size } => {
308 OrderBookError::InvalidLotSize {
309 quantity: *quantity,
310 lot_size: *lot_size,
311 }
312 }
313 OrderBookError::OrderSizeOutOfRange { quantity, min, max } => {
314 OrderBookError::OrderSizeOutOfRange {
315 quantity: *quantity,
316 min: *min,
317 max: *max,
318 }
319 }
320 OrderBookError::MissingUserId { order_id } => OrderBookError::MissingUserId {
321 order_id: *order_id,
322 },
323 OrderBookError::SelfTradePrevented {
324 mode,
325 taker_order_id,
326 user_id,
327 } => OrderBookError::SelfTradePrevented {
328 mode: *mode,
329 taker_order_id: *taker_order_id,
330 user_id: *user_id,
331 },
332 #[cfg(feature = "nats")]
333 OrderBookError::NatsPublishError { message } => OrderBookError::NatsPublishError {
334 message: message.clone(),
335 },
336 #[cfg(feature = "nats")]
337 OrderBookError::NatsSerializationError { message } => {
338 OrderBookError::NatsSerializationError {
339 message: message.clone(),
340 }
341 }
342 }
343 }
344}
345
346#[derive(Debug, Clone)]
348#[non_exhaustive]
349pub enum ManagerError {
350 ProcessorAlreadyStarted,
352}
353
354impl fmt::Display for ManagerError {
355 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
356 match self {
357 ManagerError::ProcessorAlreadyStarted => {
358 write!(f, "trade processor already started")
359 }
360 }
361 }
362}
363
364impl std::error::Error for ManagerError {}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369 use pricelevel::{Hash32, Id};
370
371 #[test]
372 fn test_clone_order_not_found() {
373 let error = OrderBookError::OrderNotFound("order123".to_string());
374 let cloned = error.clone();
375 assert!(matches!(cloned, OrderBookError::OrderNotFound(ref s) if s == "order123"));
376 }
377
378 #[test]
379 fn test_clone_invalid_price_level() {
380 let error = OrderBookError::InvalidPriceLevel(12345);
381 let cloned = error.clone();
382 assert!(matches!(cloned, OrderBookError::InvalidPriceLevel(12345)));
383 }
384
385 #[test]
386 fn test_clone_price_crossing() {
387 let error = OrderBookError::PriceCrossing {
388 price: 100,
389 side: Side::Buy,
390 opposite_price: 99,
391 };
392 let cloned = error.clone();
393 assert!(matches!(
394 cloned,
395 OrderBookError::PriceCrossing {
396 price: 100,
397 side: Side::Buy,
398 opposite_price: 99
399 }
400 ));
401 }
402
403 #[test]
404 fn test_clone_insufficient_liquidity() {
405 let error = OrderBookError::InsufficientLiquidity {
406 side: Side::Sell,
407 requested: 1000,
408 available: 500,
409 };
410 let cloned = error.clone();
411 assert!(matches!(
412 cloned,
413 OrderBookError::InsufficientLiquidity {
414 side: Side::Sell,
415 requested: 1000,
416 available: 500
417 }
418 ));
419 }
420
421 #[test]
422 fn test_clone_invalid_operation() {
423 let error = OrderBookError::InvalidOperation {
424 message: "Cannot cancel filled order".to_string(),
425 };
426 let cloned = error.clone();
427 assert!(matches!(
428 cloned,
429 OrderBookError::InvalidOperation { ref message } if message == "Cannot cancel filled order"
430 ));
431 }
432
433 #[test]
434 fn test_clone_serialization_error() {
435 let error = OrderBookError::SerializationError {
436 message: "Failed to serialize".to_string(),
437 };
438 let cloned = error.clone();
439 assert!(matches!(
440 cloned,
441 OrderBookError::SerializationError { ref message } if message == "Failed to serialize"
442 ));
443 }
444
445 #[test]
446 fn test_clone_checksum_mismatch() {
447 let error = OrderBookError::ChecksumMismatch {
448 expected: "abc123".to_string(),
449 actual: "def456".to_string(),
450 };
451 let cloned = error.clone();
452 assert!(matches!(
453 cloned,
454 OrderBookError::ChecksumMismatch { ref expected, ref actual }
455 if expected == "abc123" && actual == "def456"
456 ));
457 }
458
459 #[test]
460 fn test_clone_invalid_tick_size() {
461 let error = OrderBookError::InvalidTickSize {
462 price: 10050,
463 tick_size: 100,
464 };
465 let cloned = error.clone();
466 assert!(matches!(
467 cloned,
468 OrderBookError::InvalidTickSize {
469 price: 10050,
470 tick_size: 100
471 }
472 ));
473 }
474
475 #[test]
476 fn test_clone_invalid_lot_size() {
477 let error = OrderBookError::InvalidLotSize {
478 quantity: 75,
479 lot_size: 10,
480 };
481 let cloned = error.clone();
482 assert!(matches!(
483 cloned,
484 OrderBookError::InvalidLotSize {
485 quantity: 75,
486 lot_size: 10
487 }
488 ));
489 }
490
491 #[test]
492 fn test_clone_order_size_out_of_range() {
493 let error = OrderBookError::OrderSizeOutOfRange {
494 quantity: 5,
495 min: Some(10),
496 max: Some(1000),
497 };
498 let cloned = error.clone();
499 assert!(matches!(
500 cloned,
501 OrderBookError::OrderSizeOutOfRange {
502 quantity: 5,
503 min: Some(10),
504 max: Some(1000)
505 }
506 ));
507 }
508
509 #[test]
510 fn test_clone_missing_user_id() {
511 let order_id = Id::new_uuid();
512 let error = OrderBookError::MissingUserId { order_id };
513 let cloned = error.clone();
514 assert!(matches!(
515 cloned,
516 OrderBookError::MissingUserId { order_id: id } if id == order_id
517 ));
518 }
519
520 #[test]
521 fn test_clone_self_trade_prevented() {
522 let taker_id = Id::new_uuid();
523 let user_id = Hash32::from([1u8; 32]);
524 let error = OrderBookError::SelfTradePrevented {
525 mode: crate::orderbook::stp::STPMode::CancelMaker,
526 taker_order_id: taker_id,
527 user_id,
528 };
529 let cloned = error.clone();
530 assert!(matches!(
531 cloned,
532 OrderBookError::SelfTradePrevented {
533 mode: crate::orderbook::stp::STPMode::CancelMaker,
534 taker_order_id: id,
535 user_id: uid
536 } if id == taker_id && uid == user_id
537 ));
538 }
539
540 #[test]
541 fn test_clone_price_level_error_parse_error() {
542 let price_level_err = PriceLevelError::ParseError {
543 message: "Parse failed".to_string(),
544 };
545 let error = OrderBookError::PriceLevelError(price_level_err);
546 let cloned = error.clone();
547 assert!(matches!(
548 cloned,
549 OrderBookError::PriceLevelError(PriceLevelError::ParseError { ref message })
550 if message == "Parse failed"
551 ));
552 }
553
554 #[test]
555 fn test_clone_price_level_error_invalid_format() {
556 let price_level_err = PriceLevelError::InvalidFormat;
557 let error = OrderBookError::PriceLevelError(price_level_err);
558 let cloned = error.clone();
559 assert!(matches!(
560 cloned,
561 OrderBookError::PriceLevelError(PriceLevelError::InvalidFormat)
562 ));
563 }
564
565 #[test]
566 fn test_clone_price_level_error_unknown_order_type() {
567 let price_level_err = PriceLevelError::UnknownOrderType("CUSTOM".to_string());
568 let error = OrderBookError::PriceLevelError(price_level_err);
569 let cloned = error.clone();
570 assert!(matches!(
571 cloned,
572 OrderBookError::PriceLevelError(PriceLevelError::UnknownOrderType(ref s))
573 if s == "CUSTOM"
574 ));
575 }
576
577 #[test]
578 fn test_clone_price_level_error_checksum_mismatch() {
579 let price_level_err = PriceLevelError::ChecksumMismatch {
580 expected: "hash1".to_string(),
581 actual: "hash2".to_string(),
582 };
583 let error = OrderBookError::PriceLevelError(price_level_err);
584 let cloned = error.clone();
585 assert!(matches!(
586 cloned,
587 OrderBookError::PriceLevelError(PriceLevelError::ChecksumMismatch {
588 ref expected,
589 ref actual
590 }) if expected == "hash1" && actual == "hash2"
591 ));
592 }
593}