use std::collections::{HashMap, HashSet};
use crate::websocket::types::SubscribeParams;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Subscription {
BookUpdate { orderbook_ids: Vec<String> },
Trades { orderbook_ids: Vec<String> },
User { user: String },
PriceHistory {
orderbook_id: String,
resolution: String,
include_ohlcv: bool,
},
Market { market_pubkey: String },
}
impl Subscription {
pub fn to_params(&self) -> SubscribeParams {
match self {
Self::BookUpdate { orderbook_ids } => SubscribeParams::book_update(orderbook_ids.clone()),
Self::Trades { orderbook_ids } => SubscribeParams::trades(orderbook_ids.clone()),
Self::User { user } => SubscribeParams::user(user.clone()),
Self::PriceHistory {
orderbook_id,
resolution,
include_ohlcv,
} => SubscribeParams::price_history(
orderbook_id.clone(),
resolution.clone(),
*include_ohlcv,
),
Self::Market { market_pubkey } => SubscribeParams::market(market_pubkey.clone()),
}
}
pub fn subscription_type(&self) -> &'static str {
match self {
Self::BookUpdate { .. } => "book_update",
Self::Trades { .. } => "trades",
Self::User { .. } => "user",
Self::PriceHistory { .. } => "price_history",
Self::Market { .. } => "market",
}
}
}
#[derive(Debug, Default)]
pub struct SubscriptionManager {
book_updates: HashSet<String>,
trades: HashSet<String>,
users: HashSet<String>,
price_history: HashMap<String, (String, String, bool)>, markets: HashSet<String>,
}
impl SubscriptionManager {
pub fn new() -> Self {
Self::default()
}
pub fn add_book_update(&mut self, orderbook_ids: Vec<String>) {
for id in orderbook_ids {
self.book_updates.insert(id);
}
}
pub fn remove_book_update(&mut self, orderbook_ids: &[String]) {
for id in orderbook_ids {
self.book_updates.remove(id);
}
}
pub fn is_subscribed_book_update(&self, orderbook_id: &str) -> bool {
self.book_updates.contains(orderbook_id)
}
pub fn add_trades(&mut self, orderbook_ids: Vec<String>) {
for id in orderbook_ids {
self.trades.insert(id);
}
}
pub fn remove_trades(&mut self, orderbook_ids: &[String]) {
for id in orderbook_ids {
self.trades.remove(id);
}
}
pub fn is_subscribed_trades(&self, orderbook_id: &str) -> bool {
self.trades.contains(orderbook_id)
}
pub fn add_user(&mut self, user: String) {
self.users.insert(user);
}
pub fn remove_user(&mut self, user: &str) {
self.users.remove(user);
}
pub fn is_subscribed_user(&self, user: &str) -> bool {
self.users.contains(user)
}
pub fn add_price_history(&mut self, orderbook_id: String, resolution: String, include_ohlcv: bool) {
let key = format!("{}:{}", orderbook_id, resolution);
self.price_history
.insert(key, (orderbook_id, resolution, include_ohlcv));
}
pub fn remove_price_history(&mut self, orderbook_id: &str, resolution: &str) {
let key = format!("{}:{}", orderbook_id, resolution);
self.price_history.remove(&key);
}
pub fn is_subscribed_price_history(&self, orderbook_id: &str, resolution: &str) -> bool {
let key = format!("{}:{}", orderbook_id, resolution);
self.price_history.contains_key(&key)
}
pub fn add_market(&mut self, market_pubkey: String) {
self.markets.insert(market_pubkey);
}
pub fn remove_market(&mut self, market_pubkey: &str) {
self.markets.remove(market_pubkey);
}
pub fn is_subscribed_market(&self, market_pubkey: &str) -> bool {
self.markets.contains(market_pubkey) || self.markets.contains("all")
}
pub fn get_all_subscriptions(&self) -> Vec<Subscription> {
let mut subs = Vec::new();
if !self.book_updates.is_empty() {
subs.push(Subscription::BookUpdate {
orderbook_ids: self.book_updates.iter().cloned().collect(),
});
}
if !self.trades.is_empty() {
subs.push(Subscription::Trades {
orderbook_ids: self.trades.iter().cloned().collect(),
});
}
for user in &self.users {
subs.push(Subscription::User { user: user.clone() });
}
for (orderbook_id, resolution, include_ohlcv) in self.price_history.values() {
subs.push(Subscription::PriceHistory {
orderbook_id: orderbook_id.clone(),
resolution: resolution.clone(),
include_ohlcv: *include_ohlcv,
});
}
for market_pubkey in &self.markets {
subs.push(Subscription::Market {
market_pubkey: market_pubkey.clone(),
});
}
subs
}
pub fn clear(&mut self) {
self.book_updates.clear();
self.trades.clear();
self.users.clear();
self.price_history.clear();
self.markets.clear();
}
pub fn has_subscriptions(&self) -> bool {
!self.book_updates.is_empty()
|| !self.trades.is_empty()
|| !self.users.is_empty()
|| !self.price_history.is_empty()
|| !self.markets.is_empty()
}
pub fn subscription_count(&self) -> usize {
self.book_updates.len()
+ self.trades.len()
+ self.users.len()
+ self.price_history.len()
+ self.markets.len()
}
pub fn book_update_orderbooks(&self) -> Vec<String> {
self.book_updates.iter().cloned().collect()
}
pub fn trade_orderbooks(&self) -> Vec<String> {
self.trades.iter().cloned().collect()
}
pub fn subscribed_users(&self) -> Vec<String> {
self.users.iter().cloned().collect()
}
pub fn subscribed_markets(&self) -> Vec<String> {
self.markets.iter().cloned().collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_book_update_subscriptions() {
let mut manager = SubscriptionManager::new();
manager.add_book_update(vec!["ob1".to_string(), "ob2".to_string()]);
assert!(manager.is_subscribed_book_update("ob1"));
assert!(manager.is_subscribed_book_update("ob2"));
assert!(!manager.is_subscribed_book_update("ob3"));
manager.remove_book_update(&["ob1".to_string()]);
assert!(!manager.is_subscribed_book_update("ob1"));
assert!(manager.is_subscribed_book_update("ob2"));
}
#[test]
fn test_user_subscriptions() {
let mut manager = SubscriptionManager::new();
manager.add_user("user1".to_string());
assert!(manager.is_subscribed_user("user1"));
assert!(!manager.is_subscribed_user("user2"));
manager.remove_user("user1");
assert!(!manager.is_subscribed_user("user1"));
}
#[test]
fn test_price_history_subscriptions() {
let mut manager = SubscriptionManager::new();
manager.add_price_history("ob1".to_string(), "1m".to_string(), true);
assert!(manager.is_subscribed_price_history("ob1", "1m"));
assert!(!manager.is_subscribed_price_history("ob1", "5m"));
manager.remove_price_history("ob1", "1m");
assert!(!manager.is_subscribed_price_history("ob1", "1m"));
}
#[test]
fn test_market_subscriptions() {
let mut manager = SubscriptionManager::new();
manager.add_market("market1".to_string());
assert!(manager.is_subscribed_market("market1"));
manager.add_market("all".to_string());
assert!(manager.is_subscribed_market("any_market"));
}
#[test]
fn test_get_all_subscriptions() {
let mut manager = SubscriptionManager::new();
manager.add_book_update(vec!["ob1".to_string()]);
manager.add_user("user1".to_string());
manager.add_price_history("ob1".to_string(), "1m".to_string(), true);
let subs = manager.get_all_subscriptions();
assert_eq!(subs.len(), 3);
}
#[test]
fn test_subscription_count() {
let mut manager = SubscriptionManager::new();
assert_eq!(manager.subscription_count(), 0);
assert!(!manager.has_subscriptions());
manager.add_book_update(vec!["ob1".to_string(), "ob2".to_string()]);
manager.add_user("user1".to_string());
assert_eq!(manager.subscription_count(), 3);
assert!(manager.has_subscriptions());
}
#[test]
fn test_clear() {
let mut manager = SubscriptionManager::new();
manager.add_book_update(vec!["ob1".to_string()]);
manager.add_user("user1".to_string());
manager.clear();
assert!(!manager.has_subscriptions());
assert_eq!(manager.subscription_count(), 0);
}
#[test]
fn test_subscription_to_params() {
let sub = Subscription::BookUpdate {
orderbook_ids: vec!["ob1".to_string()],
};
let params = sub.to_params();
let json = serde_json::to_string(¶ms).unwrap();
assert!(json.contains("book_update"));
assert!(json.contains("ob1"));
}
}