use crate::crdt::{Mergeable, ReplicaId};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ConflictResolutionError {
#[error("Unresolvable conflict: {0}")]
Unresolvable(String),
#[error("Strategy not applicable: {0}")]
StrategyNotApplicable(String),
#[error("Invalid conflict data: {0}")]
InvalidData(String),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ConflictStrategy {
LastWriteWins,
FirstWriteWins,
CustomMerge,
ManualResolution,
ConflictAvoidance,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConflictMetadata {
pub replica_id: ReplicaId,
pub timestamp: DateTime<Utc>,
pub version: u64,
pub conflict_type: String,
pub resolution_strategy: ConflictStrategy,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConflictResolution<T> {
pub resolved_value: T,
pub strategy_used: ConflictStrategy,
pub metadata: ConflictMetadata,
pub conflicts_resolved: usize,
}
pub struct AdvancedConflictResolver {
strategies: HashMap<String, Box<dyn ConflictResolutionStrategy + Send + Sync>>,
default_strategy: ConflictStrategy,
conflict_history: Vec<ConflictMetadata>,
}
impl AdvancedConflictResolver {
pub fn new() -> Self {
let mut resolver = Self {
strategies: HashMap::new(),
default_strategy: ConflictStrategy::LastWriteWins,
conflict_history: Vec::new(),
};
resolver.register_strategy("lww", Box::new(LastWriteWinsStrategy));
resolver.register_strategy("fww", Box::new(FirstWriteWinsStrategy));
resolver.register_strategy("custom", Box::new(CustomMergeStrategy));
resolver
}
pub fn with_default_strategy(mut self, strategy: ConflictStrategy) -> Self {
self.default_strategy = strategy;
self
}
pub fn register_strategy(&mut self, name: &str, strategy: Box<dyn ConflictResolutionStrategy + Send + Sync>) {
self.strategies.insert(name.to_string(), strategy);
}
pub async fn resolve<T: Mergeable + Clone + Send + Sync>(
&mut self,
local: &T,
remote: &T,
metadata: Option<ConflictMetadata>,
) -> Result<ConflictResolution<T>, ConflictResolutionError> {
let metadata = metadata.unwrap_or_else(|| ConflictMetadata {
replica_id: ReplicaId::default(),
timestamp: Utc::now(),
version: 1,
conflict_type: "default".to_string(),
resolution_strategy: self.default_strategy.clone(),
});
if !self.has_conflict(local, remote, &metadata).await? {
return Ok(ConflictResolution {
resolved_value: local.clone(),
strategy_used: ConflictStrategy::LastWriteWins,
metadata,
conflicts_resolved: 0,
});
}
self.conflict_history.push(metadata.clone());
let strategy = &metadata.resolution_strategy;
let resolved_value = match strategy {
ConflictStrategy::LastWriteWins => {
self.resolve_last_write_wins(local, remote, &metadata).await?
}
ConflictStrategy::FirstWriteWins => {
self.resolve_first_write_wins(local, remote, &metadata).await?
}
ConflictStrategy::CustomMerge => {
self.resolve_custom_merge(local, remote, &metadata).await?
}
ConflictStrategy::ManualResolution => {
return Err(ConflictResolutionError::Unresolvable(
"Manual resolution required".to_string()
));
}
ConflictStrategy::ConflictAvoidance => {
self.resolve_conflict_avoidance(local, remote, &metadata).await?
}
};
Ok(ConflictResolution {
resolved_value,
strategy_used: strategy.clone(),
metadata,
conflicts_resolved: 1,
})
}
async fn has_conflict<T: Mergeable>(
&self,
local: &T,
remote: &T,
metadata: &ConflictMetadata,
) -> Result<bool, ConflictResolutionError> {
Ok(local.has_conflict(remote))
}
async fn resolve_last_write_wins<T: Mergeable + Clone>(
&self,
local: &T,
remote: &T,
metadata: &ConflictMetadata,
) -> Result<T, ConflictResolutionError> {
let mut result = local.clone();
result.merge(remote).map_err(|e| {
ConflictResolutionError::InvalidData(format!("Merge failed: {}", e))
})?;
Ok(result)
}
async fn resolve_first_write_wins<T: Mergeable + Clone>(
&self,
local: &T,
remote: &T,
metadata: &ConflictMetadata,
) -> Result<T, ConflictResolutionError> {
let mut result = local.clone();
result.merge(remote).map_err(|e| {
ConflictResolutionError::InvalidData(format!("Merge failed: {}", e))
})?;
Ok(result)
}
async fn resolve_custom_merge<T: Mergeable + Clone>(
&self,
local: &T,
remote: &T,
metadata: &ConflictMetadata,
) -> Result<T, ConflictResolutionError> {
match metadata.conflict_type.as_str() {
"text" => self.merge_text_conflicts(local, remote).await,
"numeric" => self.merge_numeric_conflicts(local, remote).await,
"list" => self.merge_list_conflicts(local, remote).await,
_ => self.resolve_last_write_wins(local, remote, metadata).await,
}
}
async fn resolve_conflict_avoidance<T: Mergeable + Clone>(
&self,
local: &T,
remote: &T,
metadata: &ConflictMetadata,
) -> Result<T, ConflictResolutionError> {
let mut result = local.clone();
if let Ok(()) = result.merge(remote) {
return Ok(result);
}
self.resolve_last_write_wins(local, remote, metadata).await
}
async fn merge_text_conflicts<T: Mergeable + Clone>(
&self,
local: &T,
remote: &T,
) -> Result<T, ConflictResolutionError> {
self.resolve_last_write_wins(local, remote, &ConflictMetadata {
replica_id: ReplicaId::default(),
timestamp: Utc::now(),
version: 1,
conflict_type: "text".to_string(),
resolution_strategy: ConflictStrategy::LastWriteWins,
}).await
}
async fn merge_numeric_conflicts<T: Mergeable + Clone>(
&self,
local: &T,
remote: &T,
) -> Result<T, ConflictResolutionError> {
self.resolve_last_write_wins(local, remote, &ConflictMetadata {
replica_id: ReplicaId::default(),
timestamp: Utc::now(),
version: 1,
conflict_type: "numeric".to_string(),
resolution_strategy: ConflictStrategy::LastWriteWins,
}).await
}
async fn merge_list_conflicts<T: Mergeable + Clone>(
&self,
local: &T,
remote: &T,
) -> Result<T, ConflictResolutionError> {
self.resolve_last_write_wins(local, remote, &ConflictMetadata {
replica_id: ReplicaId::default(),
timestamp: Utc::now(),
version: 1,
conflict_type: "list".to_string(),
resolution_strategy: ConflictStrategy::LastWriteWins,
}).await
}
pub fn get_conflict_history(&self) -> &[ConflictMetadata] {
&self.conflict_history
}
pub fn clear_conflict_history(&mut self) {
self.conflict_history.clear();
}
}
impl Default for AdvancedConflictResolver {
fn default() -> Self {
Self::new()
}
}
pub trait ConflictResolutionStrategy: Send + Sync {
fn name(&self) -> &str;
fn can_resolve(&self, conflict_type: &str) -> bool;
}
pub struct LastWriteWinsStrategy;
impl ConflictResolutionStrategy for LastWriteWinsStrategy {
fn name(&self) -> &str {
"last-write-wins"
}
fn can_resolve(&self, _conflict_type: &str) -> bool {
true }
}
pub struct FirstWriteWinsStrategy;
impl ConflictResolutionStrategy for FirstWriteWinsStrategy {
fn name(&self) -> &str {
"first-write-wins"
}
fn can_resolve(&self, _conflict_type: &str) -> bool {
true }
}
pub struct CustomMergeStrategy;
impl ConflictResolutionStrategy for CustomMergeStrategy {
fn name(&self) -> &str {
"custom-merge"
}
fn can_resolve(&self, conflict_type: &str) -> bool {
matches!(conflict_type, "text" | "numeric" | "list")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crdt::LwwRegister;
#[tokio::test]
async fn test_advanced_conflict_resolver_creation() {
let resolver = AdvancedConflictResolver::new();
assert_eq!(resolver.default_strategy, ConflictStrategy::LastWriteWins);
}
#[tokio::test]
async fn test_conflict_resolution_lww() {
let mut resolver = AdvancedConflictResolver::new();
let local = LwwRegister::new("local", ReplicaId::default());
let remote = LwwRegister::new("remote", ReplicaId::default());
let metadata = ConflictMetadata {
replica_id: ReplicaId::default(),
timestamp: Utc::now(),
version: 1,
conflict_type: "text".to_string(),
resolution_strategy: ConflictStrategy::LastWriteWins,
};
let result = resolver.resolve(&local, &remote, Some(metadata)).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_conflict_strategy_registration() {
let mut resolver = AdvancedConflictResolver::new();
let custom_strategy = Box::new(CustomMergeStrategy);
resolver.register_strategy("custom", custom_strategy);
assert!(resolver.strategies.contains_key("custom"));
}
#[tokio::test]
async fn test_conflict_resolution_with_different_strategies() {
let mut resolver = AdvancedConflictResolver::new();
let local = LwwRegister::new("local", ReplicaId::default());
let remote = LwwRegister::new("remote", ReplicaId::default());
let metadata_lww = ConflictMetadata {
replica_id: ReplicaId::default(),
timestamp: Utc::now(),
version: 1,
conflict_type: "text".to_string(),
resolution_strategy: ConflictStrategy::LastWriteWins,
};
let result_lww = resolver.resolve(&local, &remote, Some(metadata_lww)).await;
assert!(result_lww.is_ok());
let metadata_fww = ConflictMetadata {
replica_id: ReplicaId::default(),
timestamp: Utc::now(),
version: 1,
conflict_type: "text".to_string(),
resolution_strategy: ConflictStrategy::FirstWriteWins,
};
let result_fww = resolver.resolve(&local, &remote, Some(metadata_fww)).await;
assert!(result_fww.is_ok());
let metadata_custom = ConflictMetadata {
replica_id: ReplicaId::default(),
timestamp: Utc::now(),
version: 1,
conflict_type: "text".to_string(),
resolution_strategy: ConflictStrategy::CustomMerge,
};
let result_custom = resolver.resolve(&local, &remote, Some(metadata_custom)).await;
assert!(result_custom.is_ok());
}
#[tokio::test]
async fn test_conflict_history_tracking() {
let mut resolver = AdvancedConflictResolver::new();
let local_replica = ReplicaId::default();
let remote_replica = ReplicaId::default();
let now = Utc::now();
let local = LwwRegister::new("local", local_replica).with_timestamp(now);
let remote = LwwRegister::new("remote", remote_replica).with_timestamp(now);
let metadata = ConflictMetadata {
replica_id: ReplicaId::default(),
timestamp: Utc::now(),
version: 1,
conflict_type: "text".to_string(),
resolution_strategy: ConflictStrategy::LastWriteWins,
};
assert_eq!(resolver.get_conflict_history().len(), 0);
let _result = resolver.resolve(&local, &remote, Some(metadata)).await;
assert_eq!(resolver.get_conflict_history().len(), 1);
resolver.clear_conflict_history();
assert_eq!(resolver.get_conflict_history().len(), 0);
}
#[tokio::test]
async fn test_conflict_strategy_validation() {
let lww_strategy = LastWriteWinsStrategy;
let fww_strategy = FirstWriteWinsStrategy;
let custom_strategy = CustomMergeStrategy;
assert!(lww_strategy.can_resolve("text"));
assert!(lww_strategy.can_resolve("numeric"));
assert!(lww_strategy.can_resolve("list"));
assert!(fww_strategy.can_resolve("text"));
assert!(fww_strategy.can_resolve("numeric"));
assert!(fww_strategy.can_resolve("list"));
assert!(custom_strategy.can_resolve("text"));
assert!(custom_strategy.can_resolve("numeric"));
assert!(custom_strategy.can_resolve("list"));
assert!(!custom_strategy.can_resolve("unknown"));
}
#[tokio::test]
async fn test_conflict_metadata_serialization() {
let metadata = ConflictMetadata {
replica_id: ReplicaId::default(),
timestamp: Utc::now(),
version: 1,
conflict_type: "text".to_string(),
resolution_strategy: ConflictStrategy::LastWriteWins,
};
let serialized = serde_json::to_string(&metadata);
assert!(serialized.is_ok());
let deserialized: ConflictMetadata = serde_json::from_str(&serialized.unwrap()).unwrap();
assert_eq!(deserialized.conflict_type, "text");
assert_eq!(deserialized.resolution_strategy, ConflictStrategy::LastWriteWins);
}
}