use crate::error::Result;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct SyncConfig {
pub server_url: String,
pub doc_id: String,
pub auth_token: Option<String>,
pub multiplexed: bool,
pub write_to_disk: bool,
}
impl SyncConfig {
pub fn metadata(server_url: String, doc_id: String) -> Self {
Self {
server_url,
doc_id,
auth_token: None,
multiplexed: false,
write_to_disk: true,
}
}
pub fn body(server_url: String, doc_id: String) -> Self {
Self {
server_url,
doc_id,
auth_token: None,
multiplexed: true,
write_to_disk: true,
}
}
pub fn with_auth(mut self, token: String) -> Self {
self.auth_token = Some(token);
self
}
pub fn with_write_to_disk(mut self, write: bool) -> Self {
self.write_to_disk = write;
self
}
pub fn build_url(&self) -> String {
let mut url = self.server_url.clone();
if !url.contains('?') {
url.push_str("?doc=");
} else {
url.push_str("&doc=");
}
url.push_str(&self.doc_id);
if self.multiplexed {
url.push_str("&multiplexed=true");
}
if let Some(ref token) = self.auth_token {
url.push_str("&token=");
url.push_str(token);
}
url
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ConnectionStatus {
Disconnected,
Connecting,
Connected,
Syncing {
completed: usize,
total: usize,
},
Synced,
Reconnecting {
attempt: u32,
},
Error {
message: String,
},
}
pub type MessageCallback = Arc<dyn Fn(&[u8]) -> Option<Vec<u8>> + Send + Sync>;
pub type StatusCallback = Arc<dyn Fn(ConnectionStatus) + Send + Sync>;
pub trait SyncTransport: Send + Sync {
fn connect(&self, config: &SyncConfig) -> impl std::future::Future<Output = Result<()>> + Send;
fn send(&self, message: &[u8]) -> impl std::future::Future<Output = Result<()>> + Send;
fn send_text(&self, message: &str) -> impl std::future::Future<Output = Result<()>> + Send;
fn set_on_message(&self, callback: MessageCallback);
fn set_on_status(&self, callback: StatusCallback);
fn disconnect(&self) -> impl std::future::Future<Output = Result<()>> + Send;
fn is_connected(&self) -> bool;
fn status(&self) -> ConnectionStatus;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sync_config_metadata() {
let config = SyncConfig::metadata(
"wss://sync.example.com/sync".to_string(),
"workspace123".to_string(),
);
assert_eq!(config.server_url, "wss://sync.example.com/sync");
assert_eq!(config.doc_id, "workspace123");
assert!(!config.multiplexed);
assert!(config.write_to_disk);
assert!(config.auth_token.is_none());
}
#[test]
fn test_sync_config_body() {
let config = SyncConfig::body(
"wss://sync.example.com/sync".to_string(),
"workspace123".to_string(),
);
assert!(config.multiplexed);
}
#[test]
fn test_sync_config_with_auth() {
let config = SyncConfig::metadata(
"wss://sync.example.com/sync".to_string(),
"workspace123".to_string(),
)
.with_auth("token123".to_string());
assert_eq!(config.auth_token, Some("token123".to_string()));
}
#[test]
fn test_build_url_metadata() {
let config = SyncConfig::metadata(
"wss://sync.example.com/sync".to_string(),
"workspace123".to_string(),
);
let url = config.build_url();
assert_eq!(url, "wss://sync.example.com/sync?doc=workspace123");
}
#[test]
fn test_build_url_body() {
let config = SyncConfig::body(
"wss://sync.example.com/sync".to_string(),
"workspace123".to_string(),
);
let url = config.build_url();
assert!(url.contains("doc=workspace123"));
assert!(url.contains("multiplexed=true"));
}
#[test]
fn test_build_url_with_auth() {
let config = SyncConfig::metadata(
"wss://sync.example.com/sync".to_string(),
"workspace123".to_string(),
)
.with_auth("mytoken".to_string());
let url = config.build_url();
assert!(url.contains("token=mytoken"));
}
#[test]
fn test_build_url_existing_query_params() {
let config = SyncConfig::metadata(
"wss://sync.example.com/sync?version=1".to_string(),
"workspace123".to_string(),
);
let url = config.build_url();
assert!(url.contains("version=1"));
assert!(url.contains("&doc=workspace123"));
}
#[test]
fn test_connection_status_variants() {
let status = ConnectionStatus::Disconnected;
assert_eq!(status, ConnectionStatus::Disconnected);
let status = ConnectionStatus::Syncing {
completed: 5,
total: 10,
};
assert_eq!(
status,
ConnectionStatus::Syncing {
completed: 5,
total: 10
}
);
let status = ConnectionStatus::Error {
message: "timeout".to_string(),
};
assert_eq!(
status,
ConnectionStatus::Error {
message: "timeout".to_string()
}
);
}
}