use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use thiserror::Error;
use tokio::sync::{mpsc, RwLock};
use tokio::time::interval;
use super::DataPoint;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct LiveDataSource {
pub source_id: String,
pub name: String,
pub source_type: DataSourceType,
pub url: Option<String>,
pub update_interval: Duration,
pub is_active: bool,
pub last_update: Option<u64>,
pub config: DataSourceConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum DataSourceType {
WebSocket,
ServerSentEvents,
Polling,
Database,
Api,
File,
Custom,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DataSourceConfig {
pub buffer_size: usize,
pub max_retries: u32,
pub timeout: Duration,
pub compression: bool,
pub authentication: Option<AuthConfig>,
pub filters: Vec<DataFilter>,
pub transform: Option<DataTransform>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AuthConfig {
pub auth_type: AuthType,
pub credentials: HashMap<String, String>,
pub headers: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum AuthType {
None,
Basic,
Bearer,
ApiKey,
OAuth2,
Custom,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DataFilter {
pub field: String,
pub operator: FilterOperator,
pub value: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum FilterOperator {
Equals,
NotEquals,
GreaterThan,
LessThan,
GreaterThanOrEqual,
LessThanOrEqual,
Contains,
StartsWith,
EndsWith,
In,
NotIn,
Regex,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DataTransform {
pub transform_type: TransformType,
pub parameters: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TransformType {
None,
Aggregate,
Filter,
Sort,
Group,
Pivot,
Calculate,
Custom,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct LiveUpdate {
pub update_id: String,
pub source_id: String,
pub timestamp: u64,
pub update_type: UpdateType,
pub data: Vec<DataPoint>,
pub metadata: HashMap<String, serde_json::Value>,
pub priority: UpdatePriority,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum UpdateType {
FullRefresh,
Incremental,
Delta,
Append,
Replace,
Delete,
Custom,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum UpdatePriority {
Low,
Normal,
High,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct UpdateSubscription {
pub subscription_id: String,
pub chart_id: String,
pub source_id: String,
pub filters: Vec<DataFilter>,
pub callback_url: Option<String>,
pub is_active: bool,
pub created_at: u64,
pub last_update: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LiveUpdateStats {
pub total_updates: u64,
pub updates_per_second: f64,
pub active_sources: usize,
pub active_subscriptions: usize,
pub average_latency: Duration,
pub error_rate: f64,
pub last_update_time: Option<u64>,
}
#[derive(Debug, Error)]
pub enum LiveUpdateError {
#[error("Data source not found: {source_id}")]
DataSourceNotFound { source_id: String },
#[error("Subscription not found: {subscription_id}")]
SubscriptionNotFound { subscription_id: String },
#[error("Data source connection failed: {source_id}")]
ConnectionFailed { source_id: String },
#[error("Data parsing error: {reason}")]
DataParsingError { reason: String },
#[error("Update timeout: {source_id}")]
UpdateTimeout { source_id: String },
#[error("Rate limit exceeded: {source_id}")]
RateLimitExceeded { source_id: String },
#[error("Authentication failed: {source_id}")]
AuthenticationFailed { source_id: String },
#[error("Invalid configuration: {reason}")]
InvalidConfiguration { reason: String },
#[error("Serialization error: {0}")]
Serialization(#[from] serde_json::Error),
#[error("WebSocket error: {0}")]
WebSocket(#[from] super::WebSocketError),
}
pub struct LiveUpdateManager {
data_sources: Arc<RwLock<HashMap<String, LiveDataSource>>>,
subscriptions: Arc<RwLock<HashMap<String, UpdateSubscription>>>,
update_sender: mpsc::UnboundedSender<LiveUpdate>,
update_receiver: Arc<RwLock<Option<mpsc::UnboundedReceiver<LiveUpdate>>>>,
stats: Arc<RwLock<LiveUpdateStats>>,
update_tasks: Arc<RwLock<HashMap<String, tokio::task::JoinHandle<()>>>>,
}
impl LiveUpdateManager {
pub fn new() -> Self {
let (update_sender, update_receiver) = mpsc::unbounded_channel();
Self {
data_sources: Arc::new(RwLock::new(HashMap::new())),
subscriptions: Arc::new(RwLock::new(HashMap::new())),
update_sender,
update_receiver: Arc::new(RwLock::new(Some(update_receiver))),
stats: Arc::new(RwLock::new(LiveUpdateStats {
total_updates: 0,
updates_per_second: 0.0,
active_sources: 0,
active_subscriptions: 0,
average_latency: Duration::from_millis(0),
error_rate: 0.0,
last_update_time: None,
})),
update_tasks: Arc::new(RwLock::new(HashMap::new())),
}
}
pub async fn register_data_source(
&self,
data_source: LiveDataSource,
) -> Result<(), LiveUpdateError> {
let mut sources = self.data_sources.write().await;
let source_id = data_source.source_id.clone();
if sources.contains_key(&source_id) {
return Err(LiveUpdateError::InvalidConfiguration {
reason: "Data source already exists".to_string(),
});
}
sources.insert(source_id.clone(), data_source);
self.start_data_source_updates(source_id).await?;
let mut stats = self.stats.write().await;
stats.active_sources = sources.len();
Ok(())
}
pub async fn unregister_data_source(&self, source_id: &str) -> Result<(), LiveUpdateError> {
let mut sources = self.data_sources.write().await;
let mut tasks = self.update_tasks.write().await;
if !sources.contains_key(source_id) {
return Err(LiveUpdateError::DataSourceNotFound {
source_id: source_id.to_string(),
});
}
if let Some(task) = tasks.remove(source_id) {
task.abort();
}
sources.remove(source_id);
let mut stats = self.stats.write().await;
stats.active_sources = sources.len();
Ok(())
}
pub async fn subscribe_to_updates(
&self,
subscription: UpdateSubscription,
) -> Result<(), LiveUpdateError> {
let mut subscriptions = self.subscriptions.write().await;
let subscription_id = subscription.subscription_id.clone();
let sources = self.data_sources.read().await;
if !sources.contains_key(&subscription.source_id) {
return Err(LiveUpdateError::DataSourceNotFound {
source_id: subscription.source_id.clone(),
});
}
subscriptions.insert(subscription_id, subscription);
let mut stats = self.stats.write().await;
stats.active_subscriptions = subscriptions.len();
Ok(())
}
pub async fn unsubscribe_from_updates(
&self,
subscription_id: &str,
) -> Result<(), LiveUpdateError> {
let mut subscriptions = self.subscriptions.write().await;
if !subscriptions.contains_key(subscription_id) {
return Err(LiveUpdateError::SubscriptionNotFound {
subscription_id: subscription_id.to_string(),
});
}
subscriptions.remove(subscription_id);
let mut stats = self.stats.write().await;
stats.active_subscriptions = subscriptions.len();
Ok(())
}
pub async fn get_data_sources(&self) -> Vec<LiveDataSource> {
let sources = self.data_sources.read().await;
sources.values().cloned().collect()
}
pub async fn get_subscriptions(&self) -> Vec<UpdateSubscription> {
let subscriptions = self.subscriptions.read().await;
subscriptions.values().cloned().collect()
}
pub async fn get_stats(&self) -> LiveUpdateStats {
self.stats.read().await.clone()
}
pub async fn start_processing(&self) {
let receiver = self.update_receiver.write().await.take();
if let Some(mut receiver) = receiver {
let subscriptions = self.subscriptions.clone();
let stats = self.stats.clone();
tokio::spawn(async move {
while let Some(update) = receiver.recv().await {
let start_time = Instant::now();
let subscriptions_guard = subscriptions.read().await;
let matching_subscriptions: Vec<_> = subscriptions_guard
.values()
.filter(|sub| {
sub.is_active
&& sub.source_id == update.source_id
&& Self::matches_filters_static(&update, &sub.filters)
})
.collect();
for _subscription in matching_subscriptions {
}
let mut stats_guard = stats.write().await;
stats_guard.total_updates += 1;
stats_guard.last_update_time = Some(update.timestamp);
let processing_time = start_time.elapsed();
stats_guard.average_latency = processing_time;
}
});
}
}
async fn start_data_source_updates(&self, source_id: String) -> Result<(), LiveUpdateError> {
let sources = self.data_sources.clone();
let update_sender = self.update_sender.clone();
let source_id_clone = source_id.clone();
let task = tokio::spawn(async move {
let mut interval_timer = interval(Duration::from_secs(1));
if let Some(source) = sources.read().await.get(&source_id_clone) {
interval_timer = interval(source.update_interval);
}
for _ in 0..5 {
interval_timer.tick().await;
let source_exists = {
let sources_guard = sources.read().await;
sources_guard
.get(&source_id_clone)
.map(|s| s.is_active)
.unwrap_or(false)
};
if !source_exists {
break;
}
let update = LiveUpdate {
update_id: format!(
"update_{}",
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos()
),
source_id: source_id_clone.clone(),
timestamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
update_type: UpdateType::Incremental,
data: vec![DataPoint {
x: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as f64,
y: (SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
% 100) as f64,
value: Some(
(SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
% 100) as f64,
),
metadata: HashMap::new(),
}],
metadata: HashMap::new(),
priority: UpdatePriority::Normal,
};
if let Err(_) = update_sender.send(update) {
break; }
}
});
let mut tasks = self.update_tasks.write().await;
tasks.insert(source_id, task);
Ok(())
}
fn matches_filters_static(update: &LiveUpdate, filters: &[DataFilter]) -> bool {
if filters.is_empty() {
return true;
}
for filter in filters {
if !Self::matches_single_filter_static(update, filter) {
return false;
}
}
true
}
fn matches_single_filter_static(_update: &LiveUpdate, _filter: &DataFilter) -> bool {
true
}
}
impl Drop for LiveUpdateManager {
fn drop(&mut self) {
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
use tokio::time::sleep;
fn create_test_data_source(source_id: &str) -> LiveDataSource {
LiveDataSource {
source_id: source_id.to_string(),
name: format!("Test Source {}", source_id),
source_type: DataSourceType::WebSocket,
url: Some("ws://localhost:8080".to_string()),
update_interval: Duration::from_millis(100),
is_active: true,
last_update: None,
config: DataSourceConfig {
buffer_size: 1000,
max_retries: 3,
timeout: Duration::from_secs(30),
compression: true,
authentication: None,
filters: vec![],
transform: None,
},
}
}
fn create_test_subscription(
subscription_id: &str,
source_id: &str,
chart_id: &str,
) -> UpdateSubscription {
UpdateSubscription {
subscription_id: subscription_id.to_string(),
chart_id: chart_id.to_string(),
source_id: source_id.to_string(),
filters: vec![],
callback_url: None,
is_active: true,
created_at: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
last_update: None,
}
}
#[tokio::test]
#[ignore] async fn test_register_data_source() {
let manager = LiveUpdateManager::new();
let data_source = create_test_data_source("source1");
let result = manager.register_data_source(data_source.clone()).await;
assert!(result.is_ok());
let sources = manager.get_data_sources().await;
assert_eq!(sources.len(), 1);
assert_eq!(sources[0].source_id, "source1");
}
#[tokio::test]
#[ignore] async fn test_unregister_data_source() {
let manager = LiveUpdateManager::new();
let data_source = create_test_data_source("source1");
manager.register_data_source(data_source).await.unwrap();
let result = manager.unregister_data_source("source1").await;
assert!(result.is_ok());
let sources = manager.get_data_sources().await;
assert_eq!(sources.len(), 0);
}
#[tokio::test]
#[ignore] async fn test_subscribe_to_updates() {
let manager = LiveUpdateManager::new();
let data_source = create_test_data_source("source1");
let subscription = create_test_subscription("sub1", "source1", "chart1");
manager.register_data_source(data_source).await.unwrap();
let result = manager.subscribe_to_updates(subscription.clone()).await;
assert!(result.is_ok());
let subscriptions = manager.get_subscriptions().await;
assert_eq!(subscriptions.len(), 1);
assert_eq!(subscriptions[0].subscription_id, "sub1");
}
#[tokio::test]
#[ignore] async fn test_unsubscribe_from_updates() {
let manager = LiveUpdateManager::new();
let data_source = create_test_data_source("source1");
let subscription = create_test_subscription("sub1", "source1", "chart1");
manager.register_data_source(data_source).await.unwrap();
manager.subscribe_to_updates(subscription).await.unwrap();
let result = manager.unsubscribe_from_updates("sub1").await;
assert!(result.is_ok());
let subscriptions = manager.get_subscriptions().await;
assert_eq!(subscriptions.len(), 0);
}
#[tokio::test]
#[ignore] async fn test_data_source_not_found() {
let manager = LiveUpdateManager::new();
let subscription = create_test_subscription("sub1", "nonexistent", "chart1");
let result = manager.subscribe_to_updates(subscription).await;
assert!(matches!(
result,
Err(LiveUpdateError::DataSourceNotFound { .. })
));
}
#[tokio::test]
#[ignore] async fn test_subscription_not_found() {
let manager = LiveUpdateManager::new();
let result = manager.unsubscribe_from_updates("nonexistent").await;
assert!(matches!(
result,
Err(LiveUpdateError::SubscriptionNotFound { .. })
));
}
#[tokio::test]
#[ignore] async fn test_duplicate_data_source() {
let manager = LiveUpdateManager::new();
let data_source1 = create_test_data_source("source1");
let data_source2 = create_test_data_source("source1");
manager.register_data_source(data_source1).await.unwrap();
let result = manager.register_data_source(data_source2).await;
assert!(matches!(
result,
Err(LiveUpdateError::InvalidConfiguration { .. })
));
}
#[tokio::test]
#[ignore] async fn test_live_update_stats() {
let manager = LiveUpdateManager::new();
let data_source = create_test_data_source("source1");
manager.register_data_source(data_source).await.unwrap();
let stats = manager.get_stats().await;
assert_eq!(stats.active_sources, 1);
assert_eq!(stats.active_subscriptions, 0);
}
#[tokio::test]
#[ignore] async fn test_data_source_types() {
let manager = LiveUpdateManager::new();
let mut data_source = create_test_data_source("source1");
data_source.source_type = DataSourceType::Api;
data_source.url = Some("https://api.example.com/data".to_string());
let result = manager.register_data_source(data_source).await;
assert!(result.is_ok());
let sources = manager.get_data_sources().await;
assert_eq!(sources[0].source_type, DataSourceType::Api);
}
#[tokio::test]
#[ignore] async fn test_update_priority() {
let manager = LiveUpdateManager::new();
let data_source = create_test_data_source("source1");
manager.register_data_source(data_source).await.unwrap();
manager.start_processing().await;
sleep(Duration::from_millis(150)).await;
let stats = manager.get_stats().await;
assert!(stats.total_updates > 0);
}
#[tokio::test]
#[ignore] async fn test_filter_matching() {
let manager = LiveUpdateManager::new();
let data_source = create_test_data_source("source1");
let mut subscription = create_test_subscription("sub1", "source1", "chart1");
subscription.filters.push(DataFilter {
field: "value".to_string(),
operator: FilterOperator::GreaterThan,
value: serde_json::Value::Number(serde_json::Number::from(50)),
});
manager.register_data_source(data_source).await.unwrap();
manager.subscribe_to_updates(subscription).await.unwrap();
let subscriptions = manager.get_subscriptions().await;
assert_eq!(subscriptions.len(), 1);
assert_eq!(subscriptions[0].filters.len(), 1);
}
}