use crate::error::Result;
use crate::policy::conflictable::{AttributeValue, Conflictable};
pub trait ResolutionPolicy<T: Conflictable>: Send + Sync {
fn resolve(&self, items: Vec<T>) -> Result<T>;
fn name(&self) -> &str;
}
pub struct LastWriteWinsPolicy;
impl<T: Conflictable> ResolutionPolicy<T> for LastWriteWinsPolicy {
fn resolve(&self, mut items: Vec<T>) -> Result<T> {
if items.is_empty() {
return Err(crate::Error::InvalidInput(
"Cannot resolve empty item list".to_string(),
));
}
if items.len() == 1 {
return Ok(items.into_iter().next().unwrap());
}
items.sort_by(|a, b| {
let a_time = a.timestamp().unwrap_or(0);
let b_time = b.timestamp().unwrap_or(0);
b_time.cmp(&a_time) });
Ok(items.into_iter().next().unwrap())
}
fn name(&self) -> &str {
"LAST_WRITE_WINS"
}
}
pub struct HighestAttributeWinsPolicy {
attribute_name: String,
}
impl HighestAttributeWinsPolicy {
pub fn new(attribute_name: impl Into<String>) -> Self {
Self {
attribute_name: attribute_name.into(),
}
}
}
impl<T: Conflictable> ResolutionPolicy<T> for HighestAttributeWinsPolicy {
fn resolve(&self, mut items: Vec<T>) -> Result<T> {
if items.is_empty() {
return Err(crate::Error::InvalidInput(
"Cannot resolve empty item list".to_string(),
));
}
if items.len() == 1 {
return Ok(items.into_iter().next().unwrap());
}
items.sort_by(|a, b| {
let a_attrs = a.attributes();
let b_attrs = b.attributes();
let a_val = a_attrs.get(&self.attribute_name);
let b_val = b_attrs.get(&self.attribute_name);
match (a_val, b_val) {
(Some(AttributeValue::Int(a)), Some(AttributeValue::Int(b))) => b.cmp(a),
(Some(AttributeValue::Uint(a)), Some(AttributeValue::Uint(b))) => b.cmp(a),
(Some(AttributeValue::Float(a)), Some(AttributeValue::Float(b))) => {
b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
}
(Some(a_val), Some(b_val)) => {
let a_float = a_val.as_float();
let b_float = b_val.as_float();
b_float
.partial_cmp(&a_float)
.unwrap_or(std::cmp::Ordering::Equal)
}
(Some(_), None) => std::cmp::Ordering::Less, (None, Some(_)) => std::cmp::Ordering::Greater, (None, None) => std::cmp::Ordering::Equal,
}
});
Ok(items.into_iter().next().unwrap())
}
fn name(&self) -> &str {
&self.attribute_name
}
}
pub struct RejectConflictPolicy;
impl<T: Conflictable> ResolutionPolicy<T> for RejectConflictPolicy {
fn resolve(&self, items: Vec<T>) -> Result<T> {
if items.len() > 1 {
return Err(crate::Error::ConflictDetected(format!(
"Conflict detected between {} items (policy: REJECT)",
items.len()
)));
}
items
.into_iter()
.next()
.ok_or_else(|| crate::Error::InvalidInput("Empty item list".to_string()))
}
fn name(&self) -> &str {
"REJECT_CONFLICT"
}
}
#[allow(dead_code)]
pub struct LowestAttributeWinsPolicy {
attribute_name: String,
}
#[allow(dead_code)]
impl LowestAttributeWinsPolicy {
pub fn new(attribute_name: impl Into<String>) -> Self {
Self {
attribute_name: attribute_name.into(),
}
}
}
impl<T: Conflictable> ResolutionPolicy<T> for LowestAttributeWinsPolicy {
fn resolve(&self, mut items: Vec<T>) -> Result<T> {
if items.is_empty() {
return Err(crate::Error::InvalidInput(
"Cannot resolve empty item list".to_string(),
));
}
if items.len() == 1 {
return Ok(items.into_iter().next().unwrap());
}
items.sort_by(|a, b| {
let a_attrs = a.attributes();
let b_attrs = b.attributes();
let a_val = a_attrs.get(&self.attribute_name);
let b_val = b_attrs.get(&self.attribute_name);
match (a_val, b_val) {
(Some(AttributeValue::Int(a)), Some(AttributeValue::Int(b))) => a.cmp(b),
(Some(AttributeValue::Uint(a)), Some(AttributeValue::Uint(b))) => a.cmp(b),
(Some(AttributeValue::Float(a)), Some(AttributeValue::Float(b))) => {
a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
}
(Some(a_val), Some(b_val)) => {
let a_float = a_val.as_float();
let b_float = b_val.as_float();
a_float
.partial_cmp(&b_float)
.unwrap_or(std::cmp::Ordering::Equal)
}
(Some(_), None) => std::cmp::Ordering::Less, (None, Some(_)) => std::cmp::Ordering::Greater, (None, None) => std::cmp::Ordering::Equal,
}
});
Ok(items.into_iter().next().unwrap())
}
fn name(&self) -> &str {
&self.attribute_name
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[derive(Clone)]
struct TestItem {
id: String,
timestamp: u64,
priority: i64,
confidence: f64,
}
impl Conflictable for TestItem {
fn id(&self) -> String {
self.id.clone()
}
fn conflict_keys(&self) -> Vec<String> {
vec!["test-key".to_string()]
}
fn timestamp(&self) -> Option<u64> {
Some(self.timestamp)
}
fn attributes(&self) -> HashMap<String, AttributeValue> {
let mut attrs = HashMap::new();
attrs.insert("priority".to_string(), AttributeValue::Int(self.priority));
attrs.insert(
"confidence".to_string(),
AttributeValue::Float(self.confidence),
);
attrs
}
}
#[test]
fn test_last_write_wins() {
let policy = LastWriteWinsPolicy;
let items = vec![
TestItem {
id: "item-1".to_string(),
timestamp: 1000,
priority: 1,
confidence: 0.5,
},
TestItem {
id: "item-2".to_string(),
timestamp: 2000,
priority: 2,
confidence: 0.7,
},
TestItem {
id: "item-3".to_string(),
timestamp: 1500,
priority: 3,
confidence: 0.9,
},
];
let winner = policy.resolve(items).unwrap();
assert_eq!(winner.id, "item-2"); }
#[test]
fn test_highest_attribute_wins_int() {
let policy = HighestAttributeWinsPolicy::new("priority");
let items = vec![
TestItem {
id: "item-1".to_string(),
timestamp: 1000,
priority: 1,
confidence: 0.5,
},
TestItem {
id: "item-2".to_string(),
timestamp: 2000,
priority: 5,
confidence: 0.7,
},
TestItem {
id: "item-3".to_string(),
timestamp: 1500,
priority: 3,
confidence: 0.9,
},
];
let winner = policy.resolve(items).unwrap();
assert_eq!(winner.id, "item-2"); }
#[test]
fn test_highest_attribute_wins_float() {
let policy = HighestAttributeWinsPolicy::new("confidence");
let items = vec![
TestItem {
id: "item-1".to_string(),
timestamp: 1000,
priority: 1,
confidence: 0.5,
},
TestItem {
id: "item-2".to_string(),
timestamp: 2000,
priority: 5,
confidence: 0.7,
},
TestItem {
id: "item-3".to_string(),
timestamp: 1500,
priority: 3,
confidence: 0.95,
},
];
let winner = policy.resolve(items).unwrap();
assert_eq!(winner.id, "item-3"); }
#[test]
fn test_reject_conflict_policy() {
let policy = RejectConflictPolicy;
let items = vec![
TestItem {
id: "item-1".to_string(),
timestamp: 1000,
priority: 1,
confidence: 0.5,
},
TestItem {
id: "item-2".to_string(),
timestamp: 2000,
priority: 5,
confidence: 0.7,
},
];
let result = policy.resolve(items);
assert!(result.is_err());
assert!(matches!(result, Err(crate::Error::ConflictDetected(_))));
}
#[test]
fn test_lowest_attribute_wins() {
let policy = LowestAttributeWinsPolicy::new("priority");
let items = vec![
TestItem {
id: "item-1".to_string(),
timestamp: 1000,
priority: 10,
confidence: 0.5,
},
TestItem {
id: "item-2".to_string(),
timestamp: 2000,
priority: 2,
confidence: 0.7,
},
TestItem {
id: "item-3".to_string(),
timestamp: 1500,
priority: 5,
confidence: 0.9,
},
];
let winner = policy.resolve(items).unwrap();
assert_eq!(winner.id, "item-2"); }
}