rust_order_book/
error.rs

1use std::{borrow::Cow, fmt};
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
6#[non_exhaustive]
7pub(crate) enum ErrorType {
8    // 10xx General issues
9    Default,
10
11    // 11xx Request issues
12    InvalidPrice,
13    InvalidPriceOrQuantity,
14    InvalidQuantity,
15    OrderAlredyExists,
16    OrderNotFound,
17    OrderPostOnly,
18    OrderIOC,
19    OrderFOK,
20
21    // 12xx Internal error
22    InsufficientQuantity,
23    InvalidPriceLevel,
24    OrderBookEmpty,
25}
26
27impl ErrorType {
28    /// Numeric code for the error type.
29    pub(crate) fn code(self) -> u32 {
30        match self {
31            // 10xx General issues
32            ErrorType::Default => 1000,
33
34            // 11xx Request issues
35            ErrorType::InvalidQuantity => 1101,
36            ErrorType::InvalidPrice => 1102,
37            ErrorType::InvalidPriceOrQuantity => 1103,
38            ErrorType::OrderPostOnly => 1104,
39            ErrorType::OrderIOC => 1105,
40            ErrorType::OrderFOK => 1106,
41            ErrorType::OrderAlredyExists => 1109,
42            ErrorType::OrderNotFound => 1110,
43
44            // 12xx Internal error
45            ErrorType::OrderBookEmpty => 1200,
46            ErrorType::InsufficientQuantity => 1201,
47            ErrorType::InvalidPriceLevel => 1202,
48        }
49    }
50
51    /// Default human message for the error type.
52    pub(crate) const fn message(self) -> &'static str {
53        match self {
54            // 10xx General issues
55            ErrorType::Default => "Something wrong",
56
57            // 11xx Request issues
58            ErrorType::InvalidQuantity => "Invalid order quantity",
59            ErrorType::InvalidPrice => "Invalid order price",
60            ErrorType::InvalidPriceOrQuantity => "Invalid order price or quantity",
61            ErrorType::OrderPostOnly => {
62                "Post Only order rejected: would execute immediately against existing orders"
63            }
64            ErrorType::OrderIOC => {
65                "IOC order rejected: no immediate liquidity available at requested price"
66            }
67            ErrorType::OrderFOK => "FOK order rejected: unable to fill entire quantity immediately",
68            ErrorType::OrderAlredyExists => "Order already exists",
69            ErrorType::OrderNotFound => "Order not found",
70
71            // 12xx Internal error
72            ErrorType::OrderBookEmpty => "Order book is empty",
73            ErrorType::InsufficientQuantity => "Insufficient quantity to calculate price",
74            ErrorType::InvalidPriceLevel => "Invalid order price level",
75        }
76    }
77}
78
79/// Concrete error type carrying both code and message.
80///
81/// `Display` renders as `"[{code}] {message}"`.
82#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
83#[non_exhaustive]
84pub struct OrderBookError {
85    pub code: u32,
86    pub message: String,
87}
88
89impl OrderBookError {
90    /// Create from explicit code and message.
91    #[inline]
92    pub(crate) fn new(code: u32, message: impl Into<String>) -> Self {
93        Self { code, message: message.into() }
94    }
95
96    /// Create from a known numeric code, using the standard message if known.
97    #[inline]
98    pub(crate) fn from_code(code: u32) -> Self {
99        let msg = default_message_for_code(code);
100        Self::new(code, msg)
101    }
102
103    /// Create from a free-form message, using the default code (1000).
104    #[inline]
105    pub(crate) fn from_message(message: impl Into<String>) -> Self {
106        Self::new(ErrorType::Default.code(), message)
107    }
108}
109
110impl fmt::Display for OrderBookError {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        write!(f, "[{}] {}", self.code, self.message)
113    }
114}
115
116/// Map known numeric codes to their default messages.
117/// Unknown codes get `"Unknown error ({code})"`.
118#[inline]
119pub(crate) fn default_message_for_code(code: u32) -> Cow<'static, str> {
120    match code {
121        // 10xx General issues
122        1000 => Cow::Borrowed(ErrorType::Default.message()),
123
124        // 11xx Request issues
125        1101 => Cow::Borrowed(ErrorType::InvalidQuantity.message()),
126        1102 => Cow::Borrowed(ErrorType::InvalidPrice.message()),
127        1103 => Cow::Borrowed(ErrorType::InvalidPriceOrQuantity.message()),
128        1104 => Cow::Borrowed(ErrorType::OrderPostOnly.message()),
129        1105 => Cow::Borrowed(ErrorType::OrderIOC.message()),
130        1106 => Cow::Borrowed(ErrorType::OrderFOK.message()),
131        1109 => Cow::Borrowed(ErrorType::OrderAlredyExists.message()),
132        1110 => Cow::Borrowed(ErrorType::OrderNotFound.message()),
133
134        // 12xx Internal error
135        1200 => Cow::Borrowed(ErrorType::InsufficientQuantity.message()),
136        1201 => Cow::Borrowed(ErrorType::InvalidPriceLevel.message()),
137
138        _ => Cow::Owned(format!("Unknown error ({code})")),
139    }
140}
141
142/* ---------- Conversions & utilities ---------- */
143impl From<ErrorType> for OrderBookError {
144    #[inline]
145    fn from(t: ErrorType) -> Self {
146        Self::new(t.code(), t.message())
147    }
148}
149
150/// Trait to create a `OrderBookError` from different inputs (code, message or type).
151pub(crate) trait IntoOrderBookError {
152    fn into_error(self) -> OrderBookError;
153}
154
155impl IntoOrderBookError for ErrorType {
156    #[inline]
157    fn into_error(self) -> OrderBookError {
158        self.into()
159    }
160}
161
162impl IntoOrderBookError for u32 {
163    #[inline]
164    fn into_error(self) -> OrderBookError {
165        OrderBookError::from_code(self)
166    }
167}
168
169impl IntoOrderBookError for &str {
170    #[inline]
171    fn into_error(self) -> OrderBookError {
172        OrderBookError::from_message(self)
173    }
174}
175
176impl IntoOrderBookError for String {
177    #[inline]
178    fn into_error(self) -> OrderBookError {
179        OrderBookError::from_message(self)
180    }
181}
182
183/// One-stop utility: accepts either a code (`u32`), a message (`&str`/`String`) or an `ErrorType`.
184#[inline]
185pub(crate) fn make_error<E: IntoOrderBookError>(e: E) -> OrderBookError {
186    e.into_error()
187}
188
189/// Result alias for the library.
190pub type Result<T> = std::result::Result<T, OrderBookError>;
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195
196    #[test]
197    fn test_error_type_codes_and_messages() {
198        let cases = vec![
199            (ErrorType::Default, 1000, "Something wrong"),
200            (ErrorType::InvalidQuantity, 1101, "Invalid order quantity"),
201            (ErrorType::InvalidPrice, 1102, "Invalid order price"),
202            (ErrorType::InvalidPriceOrQuantity, 1103, "Invalid order price or quantity"),
203            (
204                ErrorType::OrderPostOnly,
205                1104,
206                "Post Only order rejected: would execute immediately against existing orders",
207            ),
208            (
209                ErrorType::OrderIOC,
210                1105,
211                "IOC order rejected: no immediate liquidity available at requested price",
212            ),
213            (
214                ErrorType::OrderFOK,
215                1106,
216                "FOK order rejected: unable to fill entire quantity immediately",
217            ),
218            (ErrorType::OrderAlredyExists, 1109, "Order already exists"),
219            (ErrorType::OrderNotFound, 1110, "Order not found"),
220            (ErrorType::OrderBookEmpty, 1200, "Order book is empty"),
221            (ErrorType::InsufficientQuantity, 1201, "Insufficient quantity to calculate price"),
222            (ErrorType::InvalidPriceLevel, 1202, "Invalid order price level"),
223        ];
224
225        for (err_type, code, msg) in cases {
226            assert_eq!(err_type.code(), code);
227            assert_eq!(err_type.message(), msg);
228        }
229    }
230
231    #[test]
232    fn test_error_type_code_and_message() {
233        assert_eq!(ErrorType::Default.code(), 1000);
234        assert_eq!(ErrorType::InvalidPrice.message(), "Invalid order price");
235        assert_eq!(ErrorType::OrderBookEmpty.code(), 1200);
236        assert_eq!(ErrorType::InvalidPriceLevel.message(), "Invalid order price level");
237    }
238
239    #[test]
240    fn test_order_book_error_new() {
241        let err = OrderBookError::new(1234, "Custom error");
242        assert_eq!(err.code, 1234);
243        assert_eq!(err.message, "Custom error");
244        assert_eq!(err.to_string(), "[1234] Custom error");
245    }
246
247    #[test]
248    fn test_default_message_for_code() {
249        assert_eq!(default_message_for_code(1000), ErrorType::Default.message());
250        assert_eq!(default_message_for_code(1101), ErrorType::InvalidQuantity.message());
251        assert_eq!(default_message_for_code(1102), ErrorType::InvalidPrice.message());
252        assert_eq!(default_message_for_code(1103), ErrorType::InvalidPriceOrQuantity.message());
253        assert_eq!(default_message_for_code(1104), ErrorType::OrderPostOnly.message());
254        assert_eq!(default_message_for_code(1105), ErrorType::OrderIOC.message());
255        assert_eq!(default_message_for_code(1106), ErrorType::OrderFOK.message());
256        assert_eq!(default_message_for_code(1109), ErrorType::OrderAlredyExists.message());
257        assert_eq!(default_message_for_code(1110), ErrorType::OrderNotFound.message());
258        assert_eq!(default_message_for_code(1200), ErrorType::InsufficientQuantity.message());
259        assert_eq!(default_message_for_code(1201), ErrorType::InvalidPriceLevel.message());
260    }
261
262    #[test]
263    fn test_order_book_error_from_code_known() {
264        let err = OrderBookError::from_code(1102);
265        assert_eq!(err.code, 1102);
266        assert_eq!(err.message, "Invalid order price");
267    }
268
269    #[test]
270    fn test_order_book_error_from_code_unknown() {
271        let err = OrderBookError::from_code(9999);
272        assert_eq!(err.code, 9999);
273        assert_eq!(err.message, "Unknown error (9999)");
274    }
275
276    #[test]
277    fn test_order_book_error_from_message() {
278        let err = OrderBookError::from_message("Oops");
279        assert_eq!(err.code, 1000);
280        assert_eq!(err.message, "Oops");
281    }
282
283    #[test]
284    fn test_default_message_for_code_known() {
285        assert_eq!(default_message_for_code(1101), "Invalid order quantity");
286    }
287
288    #[test]
289    fn test_default_message_for_code_unknown() {
290        assert_eq!(default_message_for_code(4242), "Unknown error (4242)");
291    }
292
293    #[test]
294    fn test_into_order_book_error_from_error_type() {
295        let err: OrderBookError = ErrorType::OrderAlredyExists.into_error();
296        assert_eq!(err.code, 1109);
297        assert_eq!(err.message, "Order already exists");
298    }
299
300    #[test]
301    fn test_into_order_book_error_from_u32() {
302        let err: OrderBookError = 1110u32.into_error();
303        assert_eq!(err.code, 1110);
304        assert_eq!(err.message, "Order not found");
305    }
306
307    #[test]
308    fn test_into_order_book_error_from_str() {
309        let err: OrderBookError = "Something bad".into_error();
310        assert_eq!(err.code, 1000);
311        assert_eq!(err.message, "Something bad");
312    }
313
314    #[test]
315    fn test_into_order_book_error_from_string() {
316        let err: OrderBookError = String::from("Failure").into_error();
317        assert_eq!(err.code, 1000);
318        assert_eq!(err.message, "Failure");
319    }
320
321    #[test]
322    fn test_make_error_utility() {
323        let err1 = make_error(ErrorType::InvalidQuantity);
324        assert_eq!(err1.code, 1101);
325
326        let err2 = make_error(1103u32);
327        assert_eq!(err2.message, "Invalid order price or quantity");
328
329        let err3 = make_error("Oops");
330        assert_eq!(err3.message, "Oops");
331
332        let err4 = make_error(String::from("Boom"));
333        assert_eq!(err4.message, "Boom");
334    }
335}