opendeviationbar_core/
errors.rs1#[cfg(feature = "python")]
6use pyo3::prelude::*;
7use thiserror::Error;
8
9#[derive(Error, Debug)]
11pub enum ProcessingError {
12 #[error(
13 "Trades not sorted at index {index}: prev=({prev_time}, {prev_id}), curr=({curr_time}, {curr_id})"
14 )]
15 UnsortedTrades {
16 index: usize,
17 prev_time: i64,
18 prev_id: i64,
19 curr_time: i64,
20 curr_id: i64,
21 },
22
23 #[error("Empty trade data")]
24 EmptyData,
25
26 #[error(
27 "Invalid threshold: {threshold_decimal_bps} dbps. Valid range: 1-100,000 dbps (0.001%-100%)"
28 )]
29 InvalidThreshold { threshold_decimal_bps: u32 },
30}
31
32#[cfg(feature = "python")]
33impl From<ProcessingError> for PyErr {
34 fn from(err: ProcessingError) -> PyErr {
35 match err {
36 ProcessingError::UnsortedTrades {
37 index,
38 prev_time,
39 prev_id,
40 curr_time,
41 curr_id,
42 } => pyo3::exceptions::PyValueError::new_err(format!(
43 "Trades not sorted at index {}: prev=({}, {}), curr=({}, {})",
44 index, prev_time, prev_id, curr_time, curr_id
45 )),
46 ProcessingError::EmptyData => {
47 pyo3::exceptions::PyValueError::new_err("Empty trade data")
48 }
49 ProcessingError::InvalidThreshold {
50 threshold_decimal_bps,
51 } => pyo3::exceptions::PyValueError::new_err(format!(
52 "Invalid threshold: {} dbps. Valid range: 1-100,000 dbps (0.001%-100%)",
53 threshold_decimal_bps
54 )),
55 }
56 }
57}
58
59#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn test_unsorted_trades_display() {
66 let err = ProcessingError::UnsortedTrades {
67 index: 42,
68 prev_time: 1000,
69 prev_id: 100,
70 curr_time: 999,
71 curr_id: 101,
72 };
73 let msg = err.to_string();
74 assert!(msg.contains("index 42"));
75 assert!(msg.contains("prev=(1000, 100)"));
76 assert!(msg.contains("curr=(999, 101)"));
77 }
78
79 #[test]
80 fn test_empty_data_display() {
81 let err = ProcessingError::EmptyData;
82 assert_eq!(err.to_string(), "Empty trade data");
83 }
84
85 #[test]
86 fn test_invalid_threshold_display() {
87 let err = ProcessingError::InvalidThreshold {
88 threshold_decimal_bps: 0,
89 };
90 let msg = err.to_string();
91 assert!(msg.contains("0 dbps"));
92 assert!(msg.contains("Valid range"));
93 }
94
95 #[test]
96 fn test_invalid_threshold_large_value() {
97 let err = ProcessingError::InvalidThreshold {
98 threshold_decimal_bps: 999_999,
99 };
100 assert!(err.to_string().contains("999999 dbps"));
101 }
102
103 #[test]
104 fn test_error_is_send_sync() {
105 fn assert_send<T: Send>() {}
106 fn assert_sync<T: Sync>() {}
107 assert_send::<ProcessingError>();
109 assert_sync::<ProcessingError>();
110 }
111
112 #[test]
113 fn test_error_debug_impl() {
114 let err = ProcessingError::EmptyData;
115 let debug = format!("{:?}", err);
116 assert!(debug.contains("EmptyData"));
117 }
118
119 #[test]
120 fn test_unsorted_trades_boundary_values() {
121 let err = ProcessingError::UnsortedTrades {
122 index: usize::MAX,
123 prev_time: i64::MIN,
124 prev_id: i64::MAX,
125 curr_time: 0,
126 curr_id: 0,
127 };
128 let msg = err.to_string();
130 assert!(!msg.is_empty());
131 }
132}