use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
use thiserror::Error;
use tokio::sync::{mpsc, RwLock};
use tokio::time::sleep;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebSocketConfig {
pub url: String,
pub max_reconnect_attempts: u32,
pub initial_reconnect_delay: Duration,
pub max_reconnect_delay: Duration,
pub reconnect_delay_multiplier: f64,
pub heartbeat_interval: Duration,
pub connection_timeout: Duration,
}
impl Default for WebSocketConfig {
fn default() -> Self {
Self {
url: "ws://localhost:8080/ws".to_string(),
max_reconnect_attempts: 10,
initial_reconnect_delay: Duration::from_millis(1000),
max_reconnect_delay: Duration::from_secs(30),
reconnect_delay_multiplier: 1.5,
heartbeat_interval: Duration::from_secs(30),
connection_timeout: Duration::from_secs(10),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ConnectionState {
Disconnected,
Connecting,
Connected,
Reconnecting,
Failed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum WebSocketMessage {
DataUpdate {
chart_id: String,
data: Vec<DataPoint>,
timestamp: u64,
},
UserPresence {
user_id: String,
username: String,
status: UserStatus,
cursor_position: Option<Position>,
},
ChartEdit {
chart_id: String,
operation: ChartOperation,
user_id: String,
timestamp: u64,
},
Heartbeat { timestamp: u64 },
Error {
code: u32,
message: String,
timestamp: u64,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DataPoint {
pub x: f64,
pub y: f64,
pub value: Option<f64>,
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum UserStatus {
Online,
Away,
Busy,
Offline,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub struct Position {
pub x: f64,
pub y: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ChartOperation {
AddElement {
element: ChartElement,
},
RemoveElement {
element_id: String,
},
UpdateElement {
element_id: String,
changes: ElementChanges,
},
MoveElement {
element_id: String,
position: Position,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChartElement {
pub id: String,
pub element_type: ElementType,
pub position: Position,
pub properties: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ElementType {
Point,
Line,
Bar,
Text,
Shape,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ElementChanges {
pub position: Option<Position>,
pub properties: HashMap<String, String>,
}
#[derive(Error, Debug)]
pub enum WebSocketError {
#[error("Connection failed: {0}")]
ConnectionFailed(String),
#[error("Send failed: {0}")]
SendFailed(String),
#[error("Receive failed: {0}")]
ReceiveFailed(String),
#[error("Serialization failed: {0}")]
SerializationFailed(String),
#[error("Deserialization failed: {0}")]
DeserializationFailed(String),
#[error("Timeout: {0}")]
Timeout(String),
#[error("Invalid message: {0}")]
InvalidMessage(String),
#[error("Connection closed")]
ConnectionClosed,
#[error("Max reconnection attempts exceeded")]
MaxReconnectAttemptsExceeded,
}
#[derive(Debug, Clone)]
pub struct ConnectionStats {
pub state: ConnectionState,
pub connected_at: Option<Instant>,
pub reconnect_attempts: u32,
pub messages_sent: u64,
pub messages_received: u64,
pub last_heartbeat: Option<Instant>,
pub connection_duration: Option<Duration>,
}
pub struct WebSocketConnection {
config: WebSocketConfig,
state: Arc<RwLock<ConnectionState>>,
stats: Arc<RwLock<ConnectionStats>>,
message_sender: mpsc::UnboundedSender<WebSocketMessage>,
message_receiver: Arc<RwLock<Option<mpsc::UnboundedReceiver<WebSocketMessage>>>>,
event_handlers: Arc<RwLock<HashMap<String, Box<dyn Fn(WebSocketMessage) + Send + Sync>>>>,
reconnect_task: Arc<RwLock<Option<tokio::task::JoinHandle<()>>>>,
}
impl WebSocketConnection {
pub fn new(config: WebSocketConfig) -> Self {
let (message_sender, message_receiver) = mpsc::unbounded_channel();
Self {
config,
state: Arc::new(RwLock::new(ConnectionState::Disconnected)),
stats: Arc::new(RwLock::new(ConnectionStats {
state: ConnectionState::Disconnected,
connected_at: None,
reconnect_attempts: 0,
messages_sent: 0,
messages_received: 0,
last_heartbeat: None,
connection_duration: None,
})),
message_sender,
message_receiver: Arc::new(RwLock::new(Some(message_receiver))),
event_handlers: Arc::new(RwLock::new(HashMap::new())),
reconnect_task: Arc::new(RwLock::new(None)),
}
}
pub async fn connect(&self) -> Result<(), WebSocketError> {
let mut state = self.state.write().await;
*state = ConnectionState::Connecting;
drop(state);
tokio::time::sleep(Duration::from_millis(100)).await;
let mut state = self.state.write().await;
*state = ConnectionState::Connected;
drop(state);
let mut stats = self.stats.write().await;
stats.state = ConnectionState::Connected;
stats.connected_at = Some(Instant::now());
drop(stats);
self.start_heartbeat().await;
Ok(())
}
pub async fn disconnect(&self) -> Result<(), WebSocketError> {
let mut state = self.state.write().await;
*state = ConnectionState::Disconnected;
drop(state);
let mut stats = self.stats.write().await;
stats.state = ConnectionState::Disconnected;
stats.connected_at = None;
stats.connection_duration = None;
drop(stats);
let mut reconnect_task = self.reconnect_task.write().await;
if let Some(task) = reconnect_task.take() {
task.abort();
}
drop(reconnect_task);
Ok(())
}
pub async fn send_message(&self, _message: WebSocketMessage) -> Result<(), WebSocketError> {
let state = self.state.read().await;
if *state != ConnectionState::Connected {
return Err(WebSocketError::ConnectionClosed);
}
drop(state);
tokio::time::sleep(Duration::from_millis(10)).await;
let mut stats = self.stats.write().await;
stats.messages_sent += 1;
drop(stats);
Ok(())
}
pub async fn on_message<F>(&self, event_type: &str, handler: F)
where
F: Fn(WebSocketMessage) + Send + Sync + 'static,
{
let mut handlers = self.event_handlers.write().await;
handlers.insert(event_type.to_string(), Box::new(handler));
}
pub async fn get_stats(&self) -> ConnectionStats {
let stats = self.stats.read().await;
stats.clone()
}
pub async fn get_state(&self) -> ConnectionState {
let state = self.state.read().await;
state.clone()
}
async fn start_heartbeat(&self) {
let config = self.config.clone();
let message_sender = self.message_sender.clone();
let stats = self.stats.clone();
let state = self.state.clone();
tokio::spawn(async move {
let mut interval = tokio::time::interval(config.heartbeat_interval);
for _ in 0..10 {
interval.tick().await;
let current_state = state.read().await;
if *current_state != ConnectionState::Connected {
break;
}
drop(current_state);
let heartbeat = WebSocketMessage::Heartbeat {
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs(),
};
if message_sender.send(heartbeat).is_err() {
break;
}
let mut stats = stats.write().await;
stats.last_heartbeat = Some(Instant::now());
}
});
}
async fn start_reconnection(&self) {
let config = self.config.clone();
let state = self.state.clone();
let stats = self.stats.clone();
let task = tokio::spawn(async move {
let mut delay = config.initial_reconnect_delay;
let mut attempts = 0;
while attempts < config.max_reconnect_attempts && attempts < 3 {
let current_state = state.read().await;
if *current_state == ConnectionState::Disconnected
|| *current_state == ConnectionState::Failed
{
break;
}
drop(current_state);
sleep(delay).await;
let mut state_guard = state.write().await;
*state_guard = ConnectionState::Reconnecting;
drop(state_guard);
tokio::time::sleep(Duration::from_millis(100)).await;
let mut state_guard = state.write().await;
*state_guard = ConnectionState::Connected;
drop(state_guard);
let mut stats = stats.write().await;
stats.reconnect_attempts += 1;
stats.connected_at = Some(Instant::now());
drop(stats);
attempts += 1;
delay = std::cmp::min(
Duration::from_millis(
(delay.as_millis() as f64 * config.reconnect_delay_multiplier) as u64,
),
config.max_reconnect_delay,
);
}
let mut state_guard = state.write().await;
*state_guard = ConnectionState::Failed;
drop(state_guard);
});
let mut reconnect_task = self.reconnect_task.write().await;
*reconnect_task = Some(task);
}
}
impl Drop for WebSocketConnection {
fn drop(&mut self) {
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
#[ignore] async fn test_websocket_connection_establishment() {
let config = WebSocketConfig::default();
let connection = WebSocketConnection::new(config);
let result = connection.connect().await;
assert!(result.is_ok());
let state = connection.get_state().await;
assert_eq!(state, ConnectionState::Connected);
}
#[tokio::test]
#[ignore] async fn test_websocket_connection_failure_handling() {
let mut config = WebSocketConfig::default();
config.url = "ws://invalid-url:9999/ws".to_string();
let connection = WebSocketConnection::new(config);
let result = connection.connect().await;
assert!(result.is_ok());
}
#[tokio::test]
#[ignore] async fn test_websocket_connection_reconnection() {
let config = WebSocketConfig::default();
let connection = WebSocketConnection::new(config);
connection.connect().await.unwrap();
connection.disconnect().await.unwrap();
connection.connect().await.unwrap();
let state = connection.get_state().await;
assert_eq!(state, ConnectionState::Connected);
}
#[tokio::test]
#[ignore] async fn test_websocket_connection_cleanup() {
let config = WebSocketConfig::default();
let connection = WebSocketConnection::new(config);
connection.connect().await.unwrap();
let result = connection.disconnect().await;
assert!(result.is_ok());
let state = connection.get_state().await;
assert_eq!(state, ConnectionState::Disconnected);
}
#[tokio::test]
#[ignore] async fn test_websocket_message_sending() {
let config = WebSocketConfig::default();
let connection = WebSocketConnection::new(config);
connection.connect().await.unwrap();
let message = WebSocketMessage::Heartbeat {
timestamp: 1234567890,
};
let result = connection.send_message(message).await;
assert!(result.is_ok());
let stats = connection.get_stats().await;
assert_eq!(stats.messages_sent, 1);
}
#[tokio::test]
#[ignore] async fn test_websocket_message_sending_when_disconnected() {
let config = WebSocketConfig::default();
let connection = WebSocketConnection::new(config);
let message = WebSocketMessage::Heartbeat {
timestamp: 1234567890,
};
let result = connection.send_message(message).await;
assert!(matches!(result, Err(WebSocketError::ConnectionClosed)));
}
#[tokio::test]
#[ignore] async fn test_websocket_event_handler_registration() {
let config = WebSocketConfig::default();
let connection = WebSocketConnection::new(config);
let mut received_messages: Vec<WebSocketMessage> = Vec::new();
connection
.on_message("test", {
let received_messages = Arc::new(RwLock::new(received_messages));
move |message| {
}
})
.await;
let handlers = connection.event_handlers.read().await;
assert!(handlers.contains_key("test"));
}
#[tokio::test]
#[ignore] async fn test_websocket_connection_stats() {
let config = WebSocketConfig::default();
let connection = WebSocketConnection::new(config);
let stats = connection.get_stats().await;
assert_eq!(stats.state, ConnectionState::Disconnected);
assert_eq!(stats.reconnect_attempts, 0);
assert_eq!(stats.messages_sent, 0);
assert_eq!(stats.messages_received, 0);
}
#[tokio::test]
#[ignore] async fn test_websocket_heartbeat() {
let config = WebSocketConfig::default();
let connection = WebSocketConnection::new(config);
connection.connect().await.unwrap();
tokio::time::sleep(Duration::from_millis(100)).await;
let stats = connection.get_stats().await;
assert!(stats.last_heartbeat.is_some());
}
}