1use crate::{PatchOperation, RdfPatch};
4use anyhow::Result;
5use serde::{Deserialize, Serialize};
6use std::collections::{BTreeMap, HashMap};
7use tracing::info;
8
9pub struct ConflictResolver {
10 strategy: ConflictStrategy,
11 priority_rules: Vec<PriorityRule>,
12 merge_policies: HashMap<String, MergePolicy>,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub enum ConflictStrategy {
17 FirstWins,
18 LastWins,
19 Merge,
20 Manual,
21 Priority,
22 Temporal,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct PriorityRule {
27 pub operation_type: String,
28 pub priority: i32,
29 pub source_pattern: Option<String>,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub enum MergePolicy {
34 Union,
35 Intersection,
36 CustomLogic(String),
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct ConflictReport {
41 pub conflicts_found: usize,
42 pub conflicts_resolved: usize,
43 pub resolution_strategy: ConflictStrategy,
44 pub detailed_conflicts: Vec<DetailedConflict>,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct DetailedConflict {
49 pub conflict_type: String,
50 pub operation1: PatchOperation,
51 pub operation2: PatchOperation,
52 pub resolution: ConflictResolution,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub enum ConflictResolution {
57 KeepFirst,
58 KeepSecond,
59 KeepBoth,
60 Merged(PatchOperation),
61 RequiresManualReview,
62}
63
64impl ConflictResolver {
65 pub fn new(strategy: ConflictStrategy) -> Self {
66 Self {
67 strategy,
68 priority_rules: Vec::new(),
69 merge_policies: HashMap::new(),
70 }
71 }
72
73 pub fn with_priority_rule(mut self, rule: PriorityRule) -> Self {
74 self.priority_rules.push(rule);
75 self
76 }
77
78 pub fn with_merge_policy(mut self, operation_type: String, policy: MergePolicy) -> Self {
79 self.merge_policies.insert(operation_type, policy);
80 self
81 }
82
83 pub fn resolve_conflicts(
85 &self,
86 patch1: &RdfPatch,
87 patch2: &RdfPatch,
88 ) -> Result<(RdfPatch, ConflictReport)> {
89 let mut merged_patch = RdfPatch::new();
90 merged_patch.id = format!("merged-{}-{}", patch1.id, patch2.id);
91
92 let mut conflicts = Vec::new();
93 let mut operation_map = BTreeMap::new();
94
95 for (idx, op) in patch1.operations.iter().enumerate() {
97 let key = self.operation_key(op);
98 operation_map.insert(format!("p1-{idx}-{key}"), (op, "patch1"));
99 }
100
101 for (idx, op) in patch2.operations.iter().enumerate() {
102 let key = self.operation_key(op);
103 let conflict_key = format!("p2-{idx}-{key}");
104
105 if let Some(existing) = operation_map
107 .iter()
108 .find(|(k, _)| self.operations_conflict(op, k.split('-').nth(2).unwrap_or("")))
109 {
110 let conflict = DetailedConflict {
111 conflict_type: "operation_overlap".to_string(),
112 operation1: existing.1 .0.clone(),
113 operation2: op.clone(),
114 resolution: self.resolve_operation_conflict(existing.1 .0, op)?,
115 };
116 conflicts.push(conflict);
117 } else {
118 operation_map.insert(conflict_key, (op, "patch2"));
119 }
120 }
121
122 for (_, (operation, _source)) in operation_map {
124 merged_patch.add_operation(operation.clone());
125 }
126
127 for conflict in &conflicts {
129 match &conflict.resolution {
130 ConflictResolution::KeepFirst => {
131 }
133 ConflictResolution::KeepSecond => {
134 merged_patch.add_operation(conflict.operation2.clone());
136 }
137 ConflictResolution::KeepBoth => {
138 merged_patch.add_operation(conflict.operation1.clone());
139 merged_patch.add_operation(conflict.operation2.clone());
140 }
141 ConflictResolution::Merged(merged_op) => {
142 merged_patch.add_operation(merged_op.clone());
143 }
144 ConflictResolution::RequiresManualReview => {
145 merged_patch.add_operation(PatchOperation::Header {
147 key: "conflict".to_string(),
148 value: format!("Manual review required: {:?}", conflict.conflict_type),
149 });
150 }
151 }
152 }
153
154 let report = ConflictReport {
155 conflicts_found: conflicts.len(),
156 conflicts_resolved: conflicts
157 .iter()
158 .filter(|c| !matches!(c.resolution, ConflictResolution::RequiresManualReview))
159 .count(),
160 resolution_strategy: self.strategy.clone(),
161 detailed_conflicts: conflicts,
162 };
163
164 info!(
165 "Conflict resolution completed: {}/{} conflicts resolved",
166 report.conflicts_resolved, report.conflicts_found
167 );
168 Ok((merged_patch, report))
169 }
170
171 fn operation_key(&self, operation: &PatchOperation) -> String {
172 match operation {
173 PatchOperation::Add {
174 subject,
175 predicate,
176 object,
177 } => {
178 format!("add-{subject}-{predicate}-{object}")
179 }
180 PatchOperation::Delete {
181 subject,
182 predicate,
183 object,
184 } => {
185 format!("delete-{subject}-{predicate}-{object}")
186 }
187 PatchOperation::AddGraph { graph } => {
188 format!("add-graph-{graph}")
189 }
190 PatchOperation::DeleteGraph { graph } => {
191 format!("delete-graph-{graph}")
192 }
193 _ => format!("{operation:?}"),
194 }
195 }
196
197 fn operations_conflict(&self, _op1: &PatchOperation, _op2_key: &str) -> bool {
198 false
200 }
201
202 fn resolve_operation_conflict(
203 &self,
204 op1: &PatchOperation,
205 op2: &PatchOperation,
206 ) -> Result<ConflictResolution> {
207 match self.strategy {
208 ConflictStrategy::FirstWins => Ok(ConflictResolution::KeepFirst),
209 ConflictStrategy::LastWins => Ok(ConflictResolution::KeepSecond),
210 ConflictStrategy::Merge => {
211 self.attempt_merge(op1, op2)
213 }
214 ConflictStrategy::Priority => {
215 self.resolve_by_priority(op1, op2)
217 }
218 ConflictStrategy::Temporal => {
219 Ok(ConflictResolution::KeepSecond) }
222 ConflictStrategy::Manual => Ok(ConflictResolution::RequiresManualReview),
223 }
224 }
225
226 fn attempt_merge(
227 &self,
228 op1: &PatchOperation,
229 op2: &PatchOperation,
230 ) -> Result<ConflictResolution> {
231 match (op1, op2) {
232 (
233 PatchOperation::Add {
234 subject: s1,
235 predicate: p1,
236 object: _o1,
237 },
238 PatchOperation::Add {
239 subject: s2,
240 predicate: p2,
241 object: _o2,
242 },
243 ) => {
244 if s1 == s2 && p1 == p2 {
245 Ok(ConflictResolution::KeepBoth)
247 } else {
248 Ok(ConflictResolution::KeepBoth)
249 }
250 }
251 _ => Ok(ConflictResolution::RequiresManualReview),
252 }
253 }
254
255 fn resolve_by_priority(
256 &self,
257 op1: &PatchOperation,
258 op2: &PatchOperation,
259 ) -> Result<ConflictResolution> {
260 let priority1 = self.get_operation_priority(op1);
261 let priority2 = self.get_operation_priority(op2);
262
263 if priority1 > priority2 {
264 Ok(ConflictResolution::KeepFirst)
265 } else if priority2 > priority1 {
266 Ok(ConflictResolution::KeepSecond)
267 } else {
268 Ok(ConflictResolution::RequiresManualReview)
269 }
270 }
271
272 fn get_operation_priority(&self, operation: &PatchOperation) -> i32 {
273 let op_type = match operation {
274 PatchOperation::Add { .. } => "add",
275 PatchOperation::Delete { .. } => "delete",
276 PatchOperation::AddGraph { .. } => "add_graph",
277 PatchOperation::DeleteGraph { .. } => "delete_graph",
278 _ => "other",
279 };
280
281 for rule in &self.priority_rules {
282 if rule.operation_type == op_type {
283 return rule.priority;
284 }
285 }
286
287 0 }
289}