use crate::types::{EventFilter, SubscriptionId};
use dashmap::DashMap;
use serde::{Deserialize, Serialize};
use std::sync::atomic::{AtomicU64, Ordering};
use thiserror::Error;
use tracing::{debug, warn};
#[derive(Debug, Error)]
pub enum SubscriptionError {
#[error("subscription limit reached: {current} active, max {max}")]
LimitReached { current: usize, max: usize },
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SubscriptionTransport {
WebSocket,
Grpc,
Webhook,
}
#[derive(Debug, Clone)]
pub struct SubscriptionManagerConfig {
pub max_total_subscriptions: usize,
}
impl Default for SubscriptionManagerConfig {
fn default() -> Self {
Self {
max_total_subscriptions: 10_000,
}
}
}
#[derive(Debug, Clone)]
struct SubscriptionEntry {
id: SubscriptionId,
filter: EventFilter,
transport: SubscriptionTransport,
}
pub struct SubscriptionManager {
config: SubscriptionManagerConfig,
subscriptions: DashMap<SubscriptionId, SubscriptionEntry>,
next_id: AtomicU64,
}
impl SubscriptionManager {
pub fn new(config: SubscriptionManagerConfig) -> Self {
Self {
config,
subscriptions: DashMap::new(),
next_id: AtomicU64::new(1),
}
}
pub fn config(&self) -> &SubscriptionManagerConfig {
&self.config
}
pub fn max_subscriptions(&self) -> usize {
self.config.max_total_subscriptions
}
pub fn create(
&self,
filter: EventFilter,
transport: SubscriptionTransport,
) -> Result<SubscriptionId, SubscriptionError> {
let current = self.subscriptions.len();
let max = self.config.max_total_subscriptions;
if current >= max {
warn!(
current,
max,
"subscription limit reached — rejecting new subscription"
);
return Err(SubscriptionError::LimitReached { current, max });
}
let id = self.next_id.fetch_add(1, Ordering::SeqCst);
self.subscriptions.insert(
id,
SubscriptionEntry {
id,
filter,
transport,
},
);
debug!(subscription_id = id, "subscription created");
Ok(id)
}
pub fn remove(&self, id: SubscriptionId) -> bool {
self.subscriptions.remove(&id).is_some()
}
pub fn count(&self) -> usize {
self.subscriptions.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_and_remove() {
let mgr = SubscriptionManager::new(SubscriptionManagerConfig::default());
let id = mgr
.create(EventFilter::all(), SubscriptionTransport::WebSocket)
.unwrap();
assert_eq!(mgr.count(), 1);
assert!(mgr.remove(id));
assert_eq!(mgr.count(), 0);
}
#[test]
fn test_subscription_limit_enforced() {
let cfg = SubscriptionManagerConfig {
max_total_subscriptions: 2,
};
let mgr = SubscriptionManager::new(cfg);
assert!(
mgr.create(EventFilter::all(), SubscriptionTransport::WebSocket)
.is_ok()
);
assert!(
mgr.create(EventFilter::all(), SubscriptionTransport::Webhook)
.is_ok()
);
let err = mgr
.create(EventFilter::all(), SubscriptionTransport::Grpc)
.unwrap_err();
match err {
SubscriptionError::LimitReached { current, max } => {
assert_eq!(current, 2);
assert_eq!(max, 2);
}
}
assert_eq!(mgr.count(), 2);
assert_eq!(mgr.config().max_total_subscriptions, 2);
assert_eq!(mgr.max_subscriptions(), 2);
}
}