crypto-msg-type 1.0.12

Cryptocurrenty message type
Documentation
use std::collections::HashMap;

use crate::MessageType;

fn msg_type_symbol_to_topic(
    msg_type: MessageType,
    symbol: &str,
    configs: Option<&HashMap<String, String>>,
) -> String {
    let is_spot = symbol.to_lowercase() == *symbol;
    let channel = match msg_type {
        MessageType::Trade => "trade.detail",
        MessageType::L2Event => {
            if is_spot {
                "mbp.20"
            } else {
                "depth.size_20.high_freq"
            }
        }
        MessageType::L2TopK => {
            if is_spot {
                "depth.step1"
            } else {
                "depth.step7"
            }
        }
        MessageType::BBO => "bbo",
        MessageType::Ticker => "detail",
        MessageType::Candlestick => "kline",
        _ => panic!("Unknown message type {msg_type}"),
    };
    if msg_type == MessageType::Candlestick {
        format!("market.{}.kline.{}", symbol, configs.unwrap().get("interval").unwrap())
    } else {
        format!("market.{symbol}.{channel}")
    }
}

fn topic_to_command(topic: &str, subscribe: bool) -> String {
    if topic.ends_with("depth.size_20.high_freq") {
        format!(
            r#"{{"{}": "{}","data_type":"incremental","id": "crypto-ws-client"}}"#,
            if subscribe { "sub" } else { "unsub" },
            topic,
        )
    } else {
        format!(
            r#"{{"{}":"{}","id":"crypto-ws-client"}}"#,
            if subscribe { "sub" } else { "unsub" },
            topic,
        )
    }
}

pub(crate) fn get_ws_commands(
    msg_types: &[MessageType],
    symbols: &[String],
    subscribe: bool,
    configs: Option<&HashMap<String, String>>,
) -> Vec<String> {
    msg_types
        .iter()
        .flat_map(|msg_type| {
            symbols.iter().map(|symbol| msg_type_symbol_to_topic(*msg_type, symbol, configs))
        })
        .map(|topic| topic_to_command(&topic, subscribe))
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn single_msg_type_multiple_symbols() {
        let commands = get_ws_commands(
            &[MessageType::Trade],
            &["BTC-USD".to_string(), "ETH-USD".to_string()],
            true,
            None,
        );
        assert_eq!(commands.len(), 2);
        assert_eq!(r#"{"sub":"market.BTC-USD.trade.detail","id":"crypto-ws-client"}"#, commands[0]);
        assert_eq!(r#"{"sub":"market.ETH-USD.trade.detail","id":"crypto-ws-client"}"#, commands[1]);
    }

    #[test]
    fn multiple_msg_types_single_symbol() {
        let commands = get_ws_commands(
            &[MessageType::Trade, MessageType::L2Event],
            &["BTC-USD".to_string()],
            true,
            None,
        );
        assert_eq!(commands.len(), 2);
        assert_eq!(r#"{"sub":"market.BTC-USD.trade.detail","id":"crypto-ws-client"}"#, commands[0]);
        assert_eq!(
            r#"{"sub": "market.BTC-USD.depth.size_20.high_freq","data_type":"incremental","id": "crypto-ws-client"}"#,
            commands[1]
        );
    }

    #[test]
    fn candlestick() {
        let mut configs = HashMap::new();
        configs.insert("interval".to_string(), "1m".to_string());
        let commands = get_ws_commands(
            &[MessageType::Candlestick],
            &["BTC-USD".to_string(), "ETH-USD".to_string()],
            true,
            Some(&configs),
        );
        assert_eq!(commands.len(), 2);
        assert_eq!(r#"{"sub":"market.BTC-USD.kline.1m","id":"crypto-ws-client"}"#, commands[0]);
        assert_eq!(r#"{"sub":"market.ETH-USD.kline.1m","id":"crypto-ws-client"}"#, commands[1]);
    }
}