use std::borrow::Cow;
use polyoxide_venue::{KeyError, KeySpace, MarketKey, ProductClass, ProductId, RawKey, VenueId};
use crate::spot::types::Symbol;
pub const VENUE: VenueId = VenueId::from_static("binance");
pub const PRODUCT: ProductId = ProductId::from_static("spot");
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SpotKeys;
impl KeySpace for SpotKeys {
const VENUE: VenueId = VENUE;
const PRODUCT: ProductId = PRODUCT;
const CLASS: ProductClass = ProductClass::Spot;
type Id = Symbol;
fn parse_id(text: &str) -> Result<Symbol, String> {
Symbol::new(text).map_err(|err| err.to_string())
}
fn id_text(id: &Symbol) -> Cow<'_, str> {
Cow::Borrowed(id.as_str())
}
}
pub fn key(symbol: &Symbol) -> MarketKey {
SpotKeys::key(symbol)
}
pub fn parse(raw: &RawKey) -> Result<MarketKey, KeyError> {
SpotKeys::parse(raw)
}
pub fn symbol(key: &MarketKey) -> Result<Symbol, KeyError> {
SpotKeys::id(key)
}
#[cfg(test)]
mod tests {
use super::*;
fn raw(text: &str) -> RawKey {
RawKey::parse(text).unwrap()
}
#[test]
fn a_lowercase_symbol_names_the_same_key() {
let lower = parse(&raw("binance.spot:btcusdt")).unwrap();
let upper = parse(&raw("binance.spot:BTCUSDT")).unwrap();
assert_eq!(lower, upper);
assert_eq!(lower.to_string(), "binance.spot:BTCUSDT");
assert_eq!(lower, key(&Symbol::new("BTCUSDT").unwrap()));
assert_eq!(
parse(&raw("binance.spot:1000satsusdt")).unwrap().id(),
"1000SATSUSDT"
);
}
#[test]
fn a_key_gives_back_its_symbol() {
let btc = Symbol::new("BTCUSDT").unwrap();
assert_eq!(symbol(&key(&btc)), Ok(btc));
}
#[test]
fn a_symbol_binance_cannot_carry_and_another_product_are_refused() {
assert!(matches!(
parse(&raw("binance.spot:BTC/USDT")),
Err(KeyError::InvalidId { .. })
));
for other in ["binance.usdm:BTCUSDT", "binance.margin:BTCUSDT"] {
assert!(
matches!(parse(&raw(other)), Err(KeyError::WrongProduct { .. })),
"{other}"
);
}
}
#[test]
fn the_ids_are_the_ones_the_crate_registers() {
let declared = polyoxide_test_support::registration!();
assert_eq!(declared.venue.as_deref(), Some(VENUE.as_str()));
assert!(
declared
.products
.iter()
.any(|product| product == PRODUCT.as_str()),
"{declared:?}"
);
assert_eq!(
declared.classes.get(PRODUCT.as_str()).map(String::as_str),
Some(SpotKeys::CLASS.as_str()),
"{declared:?}"
);
assert_eq!(SpotKeys::CLASS.as_str(), "spot");
}
}