use std::{fmt, str::FromStr};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
pub const WIDTH_48: u64 = (1 << 48) - 1;
pub const NO_MARKET: u64 = 255;
pub const MAX_ACCOUNT_INDEX: u64 = (1 << 48) - 2;
pub const MIN_SUB_ACCOUNT_INDEX: u64 = 1 << 47;
pub const MAX_API_KEY_INDEX: u8 = 254;
pub const MIN_ORDER_INDEX: u64 = 1 << 48;
pub const MAX_ORDER_INDEX: u64 = (1 << 60) - 1;
pub const MAX_ASSET_INDEX: u16 = (1 << 6) - 2;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum BadId {
#[error("{0} is not a market: an index is below 2^48 and never 255")]
Market(u64),
#[error("{0} is not an account index: it is at most 2^48 - 2")]
Account(u64),
#[error("{0} is not an API key slot: they run from 0 to 254")]
ApiKey(u64),
#[error("{0} is not an asset: assets run from 1 to 62")]
Asset(u64),
#[error("{0} is not a client order index: it runs from 1 to 2^48 - 1")]
ClientOrder(u64),
#[error("{0} is not an order index: one runs from 2^48 to 2^60 - 1")]
Order(u64),
#[error("`{0}` is not a whole number")]
NotANumber(String),
}
macro_rules! id {
($(#[$doc:meta])* $name:ident($inner:ty), $check:expr) => {
$(#[$doc])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct $name($inner);
impl $name {
pub fn new(value: $inner) -> Result<$name, BadId> {
let check: fn($inner) -> Result<(), BadId> = $check;
check(value).map(|()| $name(value))
}
#[must_use]
pub const fn get(self) -> $inner {
self.0
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for $name {
type Err = BadId;
fn from_str(text: &str) -> Result<Self, Self::Err> {
let text = text.trim();
let value = text.parse::<$inner>().map_err(|_| BadId::NotANumber(text.to_owned()))?;
$name::new(value)
}
}
impl Serialize for $name {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for $name {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value = whole(deserializer)?;
let value = <$inner>::try_from(value).map_err(|_| de::Error::custom(format!("{value} is out of range")))?;
$name::new(value).map_err(de::Error::custom)
}
}
};
}
id!(
MarketId(u64),
|value| if value > WIDTH_48 || value == NO_MARKET { Err(BadId::Market(value)) } else { Ok(()) }
);
id!(
AccountIndex(u64),
|value| if value > MAX_ACCOUNT_INDEX { Err(BadId::Account(value)) } else { Ok(()) }
);
id!(
ApiKeyIndex(u8),
|value| if value > MAX_API_KEY_INDEX { Err(BadId::ApiKey(u64::from(value))) } else { Ok(()) }
);
id!(
AssetId(u16),
|value| if value == 0 || value > MAX_ASSET_INDEX { Err(BadId::Asset(u64::from(value))) } else { Ok(()) }
);
id!(
ClientOrderIndex(u64),
|value| if value == 0 || value > WIDTH_48 { Err(BadId::ClientOrder(value)) } else { Ok(()) }
);
id!(
OrderIndex(u64),
|value| if !(MIN_ORDER_INDEX..=MAX_ORDER_INDEX).contains(&value) { Err(BadId::Order(value)) } else { Ok(()) }
);
impl MarketId {
#[must_use]
pub const fn of<const ID: u64>() -> MarketId {
const { assert!(ID <= WIDTH_48 && ID != NO_MARKET, "not a market") };
MarketId(ID)
}
}
impl AccountIndex {
pub const LLP: AccountIndex = AccountIndex(MAX_ACCOUNT_INDEX);
#[must_use]
pub const fn of<const INDEX: u64>() -> AccountIndex {
const { assert!(INDEX <= MAX_ACCOUNT_INDEX, "not an account") };
AccountIndex(INDEX)
}
#[must_use]
pub const fn is_master(self) -> bool {
self.0 < MIN_SUB_ACCOUNT_INDEX
}
}
impl ApiKeyIndex {
#[must_use]
pub const fn of<const SLOT: u8>() -> ApiKeyIndex {
const { assert!(SLOT <= MAX_API_KEY_INDEX, "not an API key slot") };
ApiKeyIndex(SLOT)
}
}
impl AssetId {
pub const ETH: AssetId = AssetId(1);
pub const LIT: AssetId = AssetId(2);
pub const COLLATERAL: AssetId = AssetId(3);
#[must_use]
pub const fn of<const ID: u16>() -> AssetId {
const { assert!(ID != 0 && ID <= MAX_ASSET_INDEX, "not an asset") };
AssetId(ID)
}
}
impl ClientOrderIndex {
#[must_use]
pub const fn of<const INDEX: u64>() -> ClientOrderIndex {
const { assert!(INDEX != 0 && INDEX <= WIDTH_48, "not a client order index") };
ClientOrderIndex(INDEX)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum OrderRef {
Client(ClientOrderIndex),
Venue(OrderIndex),
}
impl OrderRef {
#[must_use]
pub const fn index(self) -> u64 {
match self {
OrderRef::Client(client) => client.get(),
OrderRef::Venue(order) => order.get(),
}
}
pub fn from_index(index: u64) -> Result<OrderRef, BadId> {
match index {
0..MIN_ORDER_INDEX => ClientOrderIndex::new(index).map(OrderRef::Client),
_ => OrderIndex::new(index).map(OrderRef::Venue),
}
}
}
impl From<ClientOrderIndex> for OrderRef {
fn from(client: ClientOrderIndex) -> OrderRef {
OrderRef::Client(client)
}
}
impl From<OrderIndex> for OrderRef {
fn from(order: OrderIndex) -> OrderRef {
OrderRef::Venue(order)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Route {
#[default]
Perps,
Spot,
}
impl Route {
#[must_use]
pub const fn code(self) -> u8 {
match self {
Route::Perps => 0,
Route::Spot => 1,
}
}
}
pub(crate) fn whole<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u64, D::Error> {
struct Visitor;
impl de::Visitor<'_> for Visitor {
type Value = u64;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a whole number or its digits")
}
fn visit_u64<E: de::Error>(self, value: u64) -> Result<u64, E> {
Ok(value)
}
fn visit_i64<E: de::Error>(self, value: i64) -> Result<u64, E> {
u64::try_from(value).map_err(|_| E::custom(format!("{value} is negative")))
}
fn visit_str<E: de::Error>(self, text: &str) -> Result<u64, E> {
text.trim().parse().map_err(|_| E::custom(format!("`{text}` is not a whole number")))
}
}
deserializer.deserialize_any(Visitor)
}
#[cfg(test)]
#[path = "tests/ids.rs"]
mod tests;