1use crate::crdt::{Mergeable, ReplicaId};
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use thiserror::Error;
8
9#[derive(Error, Debug)]
10pub enum ConflictResolutionError {
11 #[error("Unresolvable conflict: {0}")]
12 Unresolvable(String),
13 #[error("Strategy not applicable: {0}")]
14 StrategyNotApplicable(String),
15 #[error("Invalid conflict data: {0}")]
16 InvalidData(String),
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
21pub enum ConflictStrategy {
22 LastWriteWins,
24 FirstWriteWins,
26 CustomMerge,
28 ManualResolution,
30 ConflictAvoidance,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct ConflictMetadata {
37 pub replica_id: ReplicaId,
38 pub timestamp: DateTime<Utc>,
39 pub version: u64,
40 pub conflict_type: String,
41 pub resolution_strategy: ConflictStrategy,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct ConflictResolution<T> {
47 pub resolved_value: T,
48 pub strategy_used: ConflictStrategy,
49 pub metadata: ConflictMetadata,
50 pub conflicts_resolved: usize,
51}
52
53pub struct AdvancedConflictResolver {
55 strategies: HashMap<String, Box<dyn ConflictResolutionStrategy + Send + Sync>>,
56 default_strategy: ConflictStrategy,
57 conflict_history: Vec<ConflictMetadata>,
58}
59
60impl AdvancedConflictResolver {
61 pub fn new() -> Self {
62 let mut resolver = Self {
63 strategies: HashMap::new(),
64 default_strategy: ConflictStrategy::LastWriteWins,
65 conflict_history: Vec::new(),
66 };
67
68 resolver.register_strategy("lww", Box::new(LastWriteWinsStrategy));
70 resolver.register_strategy("fww", Box::new(FirstWriteWinsStrategy));
71 resolver.register_strategy("custom", Box::new(CustomMergeStrategy));
72
73 resolver
74 }
75
76 pub fn with_default_strategy(mut self, strategy: ConflictStrategy) -> Self {
77 self.default_strategy = strategy;
78 self
79 }
80
81 pub fn register_strategy(&mut self, name: &str, strategy: Box<dyn ConflictResolutionStrategy + Send + Sync>) {
82 self.strategies.insert(name.to_string(), strategy);
83 }
84
85 pub async fn resolve<T: Mergeable + Clone + Send + Sync>(
86 &mut self,
87 local: &T,
88 remote: &T,
89 metadata: Option<ConflictMetadata>,
90 ) -> Result<ConflictResolution<T>, ConflictResolutionError> {
91 let metadata = metadata.unwrap_or_else(|| ConflictMetadata {
92 replica_id: ReplicaId::default(),
93 timestamp: Utc::now(),
94 version: 1,
95 conflict_type: "default".to_string(),
96 resolution_strategy: self.default_strategy.clone(),
97 });
98
99 if !self.has_conflict(local, remote, &metadata).await? {
101 return Ok(ConflictResolution {
102 resolved_value: local.clone(),
103 strategy_used: ConflictStrategy::LastWriteWins,
104 metadata,
105 conflicts_resolved: 0,
106 });
107 }
108
109 self.conflict_history.push(metadata.clone());
111
112 let strategy = &metadata.resolution_strategy;
114 let resolved_value = match strategy {
115 ConflictStrategy::LastWriteWins => {
116 self.resolve_last_write_wins(local, remote, &metadata).await?
117 }
118 ConflictStrategy::FirstWriteWins => {
119 self.resolve_first_write_wins(local, remote, &metadata).await?
120 }
121 ConflictStrategy::CustomMerge => {
122 self.resolve_custom_merge(local, remote, &metadata).await?
123 }
124 ConflictStrategy::ManualResolution => {
125 return Err(ConflictResolutionError::Unresolvable(
126 "Manual resolution required".to_string()
127 ));
128 }
129 ConflictStrategy::ConflictAvoidance => {
130 self.resolve_conflict_avoidance(local, remote, &metadata).await?
131 }
132 };
133
134 Ok(ConflictResolution {
135 resolved_value,
136 strategy_used: strategy.clone(),
137 metadata,
138 conflicts_resolved: 1,
139 })
140 }
141
142 async fn has_conflict<T: Mergeable>(
143 &self,
144 local: &T,
145 remote: &T,
146 metadata: &ConflictMetadata,
147 ) -> Result<bool, ConflictResolutionError> {
148 Ok(local.has_conflict(remote))
150 }
151
152 async fn resolve_last_write_wins<T: Mergeable + Clone>(
153 &self,
154 local: &T,
155 remote: &T,
156 metadata: &ConflictMetadata,
157 ) -> Result<T, ConflictResolutionError> {
158 let mut result = local.clone();
160 result.merge(remote).map_err(|e| {
161 ConflictResolutionError::InvalidData(format!("Merge failed: {}", e))
162 })?;
163 Ok(result)
164 }
165
166 async fn resolve_first_write_wins<T: Mergeable + Clone>(
167 &self,
168 local: &T,
169 remote: &T,
170 metadata: &ConflictMetadata,
171 ) -> Result<T, ConflictResolutionError> {
172 let mut result = local.clone();
174 result.merge(remote).map_err(|e| {
177 ConflictResolutionError::InvalidData(format!("Merge failed: {}", e))
178 })?;
179 Ok(result)
180 }
181
182 async fn resolve_custom_merge<T: Mergeable + Clone>(
183 &self,
184 local: &T,
185 remote: &T,
186 metadata: &ConflictMetadata,
187 ) -> Result<T, ConflictResolutionError> {
188 match metadata.conflict_type.as_str() {
190 "text" => self.merge_text_conflicts(local, remote).await,
191 "numeric" => self.merge_numeric_conflicts(local, remote).await,
192 "list" => self.merge_list_conflicts(local, remote).await,
193 _ => self.resolve_last_write_wins(local, remote, metadata).await,
194 }
195 }
196
197 async fn resolve_conflict_avoidance<T: Mergeable + Clone>(
198 &self,
199 local: &T,
200 remote: &T,
201 metadata: &ConflictMetadata,
202 ) -> Result<T, ConflictResolutionError> {
203 let mut result = local.clone();
205
206 if let Ok(()) = result.merge(remote) {
208 return Ok(result);
209 }
210
211 self.resolve_last_write_wins(local, remote, metadata).await
213 }
214
215 async fn merge_text_conflicts<T: Mergeable + Clone>(
216 &self,
217 local: &T,
218 remote: &T,
219 ) -> Result<T, ConflictResolutionError> {
220 self.resolve_last_write_wins(local, remote, &ConflictMetadata {
223 replica_id: ReplicaId::default(),
224 timestamp: Utc::now(),
225 version: 1,
226 conflict_type: "text".to_string(),
227 resolution_strategy: ConflictStrategy::LastWriteWins,
228 }).await
229 }
230
231 async fn merge_numeric_conflicts<T: Mergeable + Clone>(
232 &self,
233 local: &T,
234 remote: &T,
235 ) -> Result<T, ConflictResolutionError> {
236 self.resolve_last_write_wins(local, remote, &ConflictMetadata {
239 replica_id: ReplicaId::default(),
240 timestamp: Utc::now(),
241 version: 1,
242 conflict_type: "numeric".to_string(),
243 resolution_strategy: ConflictStrategy::LastWriteWins,
244 }).await
245 }
246
247 async fn merge_list_conflicts<T: Mergeable + Clone>(
248 &self,
249 local: &T,
250 remote: &T,
251 ) -> Result<T, ConflictResolutionError> {
252 self.resolve_last_write_wins(local, remote, &ConflictMetadata {
255 replica_id: ReplicaId::default(),
256 timestamp: Utc::now(),
257 version: 1,
258 conflict_type: "list".to_string(),
259 resolution_strategy: ConflictStrategy::LastWriteWins,
260 }).await
261 }
262
263 pub fn get_conflict_history(&self) -> &[ConflictMetadata] {
264 &self.conflict_history
265 }
266
267 pub fn clear_conflict_history(&mut self) {
268 self.conflict_history.clear();
269 }
270}
271
272impl Default for AdvancedConflictResolver {
273 fn default() -> Self {
274 Self::new()
275 }
276}
277
278pub trait ConflictResolutionStrategy: Send + Sync {
280 fn name(&self) -> &str;
281 fn can_resolve(&self, conflict_type: &str) -> bool;
282}
283
284pub struct LastWriteWinsStrategy;
286
287impl ConflictResolutionStrategy for LastWriteWinsStrategy {
288 fn name(&self) -> &str {
289 "last-write-wins"
290 }
291
292 fn can_resolve(&self, _conflict_type: &str) -> bool {
293 true }
295}
296
297pub struct FirstWriteWinsStrategy;
299
300impl ConflictResolutionStrategy for FirstWriteWinsStrategy {
301 fn name(&self) -> &str {
302 "first-write-wins"
303 }
304
305 fn can_resolve(&self, _conflict_type: &str) -> bool {
306 true }
308}
309
310pub struct CustomMergeStrategy;
312
313impl ConflictResolutionStrategy for CustomMergeStrategy {
314 fn name(&self) -> &str {
315 "custom-merge"
316 }
317
318 fn can_resolve(&self, conflict_type: &str) -> bool {
319 matches!(conflict_type, "text" | "numeric" | "list")
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326 use crate::crdt::LwwRegister;
327
328 #[tokio::test]
329 async fn test_advanced_conflict_resolver_creation() {
330 let resolver = AdvancedConflictResolver::new();
331 assert_eq!(resolver.default_strategy, ConflictStrategy::LastWriteWins);
332 }
333
334 #[tokio::test]
335 async fn test_conflict_resolution_lww() {
336 let mut resolver = AdvancedConflictResolver::new();
337 let local = LwwRegister::new("local", ReplicaId::default());
338 let remote = LwwRegister::new("remote", ReplicaId::default());
339
340 let metadata = ConflictMetadata {
341 replica_id: ReplicaId::default(),
342 timestamp: Utc::now(),
343 version: 1,
344 conflict_type: "text".to_string(),
345 resolution_strategy: ConflictStrategy::LastWriteWins,
346 };
347
348 let result = resolver.resolve(&local, &remote, Some(metadata)).await;
349 assert!(result.is_ok());
350 }
351
352 #[tokio::test]
353 async fn test_conflict_strategy_registration() {
354 let mut resolver = AdvancedConflictResolver::new();
355 let custom_strategy = Box::new(CustomMergeStrategy);
356
357 resolver.register_strategy("custom", custom_strategy);
358 assert!(resolver.strategies.contains_key("custom"));
359 }
360
361 #[tokio::test]
362 async fn test_conflict_resolution_with_different_strategies() {
363 let mut resolver = AdvancedConflictResolver::new();
364 let local = LwwRegister::new("local", ReplicaId::default());
365 let remote = LwwRegister::new("remote", ReplicaId::default());
366
367 let metadata_lww = ConflictMetadata {
369 replica_id: ReplicaId::default(),
370 timestamp: Utc::now(),
371 version: 1,
372 conflict_type: "text".to_string(),
373 resolution_strategy: ConflictStrategy::LastWriteWins,
374 };
375 let result_lww = resolver.resolve(&local, &remote, Some(metadata_lww)).await;
376 assert!(result_lww.is_ok());
377
378 let metadata_fww = ConflictMetadata {
380 replica_id: ReplicaId::default(),
381 timestamp: Utc::now(),
382 version: 1,
383 conflict_type: "text".to_string(),
384 resolution_strategy: ConflictStrategy::FirstWriteWins,
385 };
386 let result_fww = resolver.resolve(&local, &remote, Some(metadata_fww)).await;
387 assert!(result_fww.is_ok());
388
389 let metadata_custom = ConflictMetadata {
391 replica_id: ReplicaId::default(),
392 timestamp: Utc::now(),
393 version: 1,
394 conflict_type: "text".to_string(),
395 resolution_strategy: ConflictStrategy::CustomMerge,
396 };
397 let result_custom = resolver.resolve(&local, &remote, Some(metadata_custom)).await;
398 assert!(result_custom.is_ok());
399 }
400
401 #[tokio::test]
402 async fn test_conflict_history_tracking() {
403 let mut resolver = AdvancedConflictResolver::new();
404
405 let local_replica = ReplicaId::default();
407 let remote_replica = ReplicaId::default();
408
409 let now = Utc::now();
411 let local = LwwRegister::new("local", local_replica).with_timestamp(now);
412 let remote = LwwRegister::new("remote", remote_replica).with_timestamp(now);
413
414 let metadata = ConflictMetadata {
415 replica_id: ReplicaId::default(),
416 timestamp: Utc::now(),
417 version: 1,
418 conflict_type: "text".to_string(),
419 resolution_strategy: ConflictStrategy::LastWriteWins,
420 };
421
422 assert_eq!(resolver.get_conflict_history().len(), 0);
424
425 let _result = resolver.resolve(&local, &remote, Some(metadata)).await;
427
428 assert_eq!(resolver.get_conflict_history().len(), 1);
430
431 resolver.clear_conflict_history();
433 assert_eq!(resolver.get_conflict_history().len(), 0);
434 }
435
436 #[tokio::test]
437 async fn test_conflict_strategy_validation() {
438 let lww_strategy = LastWriteWinsStrategy;
439 let fww_strategy = FirstWriteWinsStrategy;
440 let custom_strategy = CustomMergeStrategy;
441
442 assert!(lww_strategy.can_resolve("text"));
444 assert!(lww_strategy.can_resolve("numeric"));
445 assert!(lww_strategy.can_resolve("list"));
446
447 assert!(fww_strategy.can_resolve("text"));
448 assert!(fww_strategy.can_resolve("numeric"));
449 assert!(fww_strategy.can_resolve("list"));
450
451 assert!(custom_strategy.can_resolve("text"));
453 assert!(custom_strategy.can_resolve("numeric"));
454 assert!(custom_strategy.can_resolve("list"));
455 assert!(!custom_strategy.can_resolve("unknown"));
456 }
457
458 #[tokio::test]
459 async fn test_conflict_metadata_serialization() {
460 let metadata = ConflictMetadata {
461 replica_id: ReplicaId::default(),
462 timestamp: Utc::now(),
463 version: 1,
464 conflict_type: "text".to_string(),
465 resolution_strategy: ConflictStrategy::LastWriteWins,
466 };
467
468 let serialized = serde_json::to_string(&metadata);
470 assert!(serialized.is_ok());
471
472 let deserialized: ConflictMetadata = serde_json::from_str(&serialized.unwrap()).unwrap();
474 assert_eq!(deserialized.conflict_type, "text");
475 assert_eq!(deserialized.resolution_strategy, ConflictStrategy::LastWriteWins);
476 }
477}