use std::{borrow::Cow, fmt};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub(crate) enum ErrorType {
Default,
InvalidPrice,
InvalidPriceOrQuantity,
InvalidQuantity,
OrderAlredyExists,
OrderNotFound,
OrderPostOnly,
OrderIOC,
OrderFOK,
InsufficientQuantity,
InvalidPriceLevel,
OrderBookEmpty,
}
impl ErrorType {
pub(crate) fn code(self) -> u32 {
match self {
ErrorType::Default => 1000,
ErrorType::InvalidQuantity => 1101,
ErrorType::InvalidPrice => 1102,
ErrorType::InvalidPriceOrQuantity => 1103,
ErrorType::OrderPostOnly => 1104,
ErrorType::OrderIOC => 1105,
ErrorType::OrderFOK => 1106,
ErrorType::OrderAlredyExists => 1109,
ErrorType::OrderNotFound => 1110,
ErrorType::OrderBookEmpty => 1200,
ErrorType::InsufficientQuantity => 1201,
ErrorType::InvalidPriceLevel => 1202,
}
}
pub(crate) const fn message(self) -> &'static str {
match self {
ErrorType::Default => "Something wrong",
ErrorType::InvalidQuantity => "Invalid order quantity",
ErrorType::InvalidPrice => "Invalid order price",
ErrorType::InvalidPriceOrQuantity => "Invalid order price or quantity",
ErrorType::OrderPostOnly => {
"Post Only order rejected: would execute immediately against existing orders"
}
ErrorType::OrderIOC => {
"IOC order rejected: no immediate liquidity available at requested price"
}
ErrorType::OrderFOK => "FOK order rejected: unable to fill entire quantity immediately",
ErrorType::OrderAlredyExists => "Order already exists",
ErrorType::OrderNotFound => "Order not found",
ErrorType::OrderBookEmpty => "Order book is empty",
ErrorType::InsufficientQuantity => "Insufficient quantity to calculate price",
ErrorType::InvalidPriceLevel => "Invalid order price level",
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct OrderBookError {
pub code: u32,
pub message: String,
}
impl OrderBookError {
#[inline]
pub(crate) fn new(code: u32, message: impl Into<String>) -> Self {
Self { code, message: message.into() }
}
#[inline]
pub(crate) fn from_code(code: u32) -> Self {
let msg = default_message_for_code(code);
Self::new(code, msg)
}
#[inline]
pub(crate) fn from_message(message: impl Into<String>) -> Self {
Self::new(ErrorType::Default.code(), message)
}
}
impl fmt::Display for OrderBookError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}] {}", self.code, self.message)
}
}
#[inline]
pub(crate) fn default_message_for_code(code: u32) -> Cow<'static, str> {
match code {
1000 => Cow::Borrowed(ErrorType::Default.message()),
1101 => Cow::Borrowed(ErrorType::InvalidQuantity.message()),
1102 => Cow::Borrowed(ErrorType::InvalidPrice.message()),
1103 => Cow::Borrowed(ErrorType::InvalidPriceOrQuantity.message()),
1104 => Cow::Borrowed(ErrorType::OrderPostOnly.message()),
1105 => Cow::Borrowed(ErrorType::OrderIOC.message()),
1106 => Cow::Borrowed(ErrorType::OrderFOK.message()),
1109 => Cow::Borrowed(ErrorType::OrderAlredyExists.message()),
1110 => Cow::Borrowed(ErrorType::OrderNotFound.message()),
1200 => Cow::Borrowed(ErrorType::InsufficientQuantity.message()),
1201 => Cow::Borrowed(ErrorType::InvalidPriceLevel.message()),
_ => Cow::Owned(format!("Unknown error ({code})")),
}
}
impl From<ErrorType> for OrderBookError {
#[inline]
fn from(t: ErrorType) -> Self {
Self::new(t.code(), t.message())
}
}
pub(crate) trait IntoOrderBookError {
fn into_error(self) -> OrderBookError;
}
impl IntoOrderBookError for ErrorType {
#[inline]
fn into_error(self) -> OrderBookError {
self.into()
}
}
impl IntoOrderBookError for u32 {
#[inline]
fn into_error(self) -> OrderBookError {
OrderBookError::from_code(self)
}
}
impl IntoOrderBookError for &str {
#[inline]
fn into_error(self) -> OrderBookError {
OrderBookError::from_message(self)
}
}
impl IntoOrderBookError for String {
#[inline]
fn into_error(self) -> OrderBookError {
OrderBookError::from_message(self)
}
}
#[inline]
pub(crate) fn make_error<E: IntoOrderBookError>(e: E) -> OrderBookError {
e.into_error()
}
pub type Result<T> = std::result::Result<T, OrderBookError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_type_codes_and_messages() {
let cases = vec![
(ErrorType::Default, 1000, "Something wrong"),
(ErrorType::InvalidQuantity, 1101, "Invalid order quantity"),
(ErrorType::InvalidPrice, 1102, "Invalid order price"),
(ErrorType::InvalidPriceOrQuantity, 1103, "Invalid order price or quantity"),
(
ErrorType::OrderPostOnly,
1104,
"Post Only order rejected: would execute immediately against existing orders",
),
(
ErrorType::OrderIOC,
1105,
"IOC order rejected: no immediate liquidity available at requested price",
),
(
ErrorType::OrderFOK,
1106,
"FOK order rejected: unable to fill entire quantity immediately",
),
(ErrorType::OrderAlredyExists, 1109, "Order already exists"),
(ErrorType::OrderNotFound, 1110, "Order not found"),
(ErrorType::OrderBookEmpty, 1200, "Order book is empty"),
(ErrorType::InsufficientQuantity, 1201, "Insufficient quantity to calculate price"),
(ErrorType::InvalidPriceLevel, 1202, "Invalid order price level"),
];
for (err_type, code, msg) in cases {
assert_eq!(err_type.code(), code);
assert_eq!(err_type.message(), msg);
}
}
#[test]
fn test_error_type_code_and_message() {
assert_eq!(ErrorType::Default.code(), 1000);
assert_eq!(ErrorType::InvalidPrice.message(), "Invalid order price");
assert_eq!(ErrorType::OrderBookEmpty.code(), 1200);
assert_eq!(ErrorType::InvalidPriceLevel.message(), "Invalid order price level");
}
#[test]
fn test_order_book_error_new() {
let err = OrderBookError::new(1234, "Custom error");
assert_eq!(err.code, 1234);
assert_eq!(err.message, "Custom error");
assert_eq!(err.to_string(), "[1234] Custom error");
}
#[test]
fn test_default_message_for_code() {
assert_eq!(default_message_for_code(1000), ErrorType::Default.message());
assert_eq!(default_message_for_code(1101), ErrorType::InvalidQuantity.message());
assert_eq!(default_message_for_code(1102), ErrorType::InvalidPrice.message());
assert_eq!(default_message_for_code(1103), ErrorType::InvalidPriceOrQuantity.message());
assert_eq!(default_message_for_code(1104), ErrorType::OrderPostOnly.message());
assert_eq!(default_message_for_code(1105), ErrorType::OrderIOC.message());
assert_eq!(default_message_for_code(1106), ErrorType::OrderFOK.message());
assert_eq!(default_message_for_code(1109), ErrorType::OrderAlredyExists.message());
assert_eq!(default_message_for_code(1110), ErrorType::OrderNotFound.message());
assert_eq!(default_message_for_code(1200), ErrorType::InsufficientQuantity.message());
assert_eq!(default_message_for_code(1201), ErrorType::InvalidPriceLevel.message());
}
#[test]
fn test_order_book_error_from_code_known() {
let err = OrderBookError::from_code(1102);
assert_eq!(err.code, 1102);
assert_eq!(err.message, "Invalid order price");
}
#[test]
fn test_order_book_error_from_code_unknown() {
let err = OrderBookError::from_code(9999);
assert_eq!(err.code, 9999);
assert_eq!(err.message, "Unknown error (9999)");
}
#[test]
fn test_order_book_error_from_message() {
let err = OrderBookError::from_message("Oops");
assert_eq!(err.code, 1000);
assert_eq!(err.message, "Oops");
}
#[test]
fn test_default_message_for_code_known() {
assert_eq!(default_message_for_code(1101), "Invalid order quantity");
}
#[test]
fn test_default_message_for_code_unknown() {
assert_eq!(default_message_for_code(4242), "Unknown error (4242)");
}
#[test]
fn test_into_order_book_error_from_error_type() {
let err: OrderBookError = ErrorType::OrderAlredyExists.into_error();
assert_eq!(err.code, 1109);
assert_eq!(err.message, "Order already exists");
}
#[test]
fn test_into_order_book_error_from_u32() {
let err: OrderBookError = 1110u32.into_error();
assert_eq!(err.code, 1110);
assert_eq!(err.message, "Order not found");
}
#[test]
fn test_into_order_book_error_from_str() {
let err: OrderBookError = "Something bad".into_error();
assert_eq!(err.code, 1000);
assert_eq!(err.message, "Something bad");
}
#[test]
fn test_into_order_book_error_from_string() {
let err: OrderBookError = String::from("Failure").into_error();
assert_eq!(err.code, 1000);
assert_eq!(err.message, "Failure");
}
#[test]
fn test_make_error_utility() {
let err1 = make_error(ErrorType::InvalidQuantity);
assert_eq!(err1.code, 1101);
let err2 = make_error(1103u32);
assert_eq!(err2.message, "Invalid order price or quantity");
let err3 = make_error("Oops");
assert_eq!(err3.message, "Oops");
let err4 = make_error(String::from("Boom"));
assert_eq!(err4.message, "Boom");
}
}