Skip to main content

icydb_core/patch/
merge.rs

1use crate::patch::{AtomicPatch, ListPatch, MapPatch, SetPatch};
2use std::{
3    collections::{
4        BTreeMap, BTreeSet, HashMap, HashSet, btree_map::Entry as BTreeMapEntry,
5        hash_map::Entry as HashMapEntry,
6    },
7    hash::{BuildHasher, Hash},
8};
9use thiserror::Error as ThisError;
10
11///
12/// MergePatch
13///
14
15pub trait MergePatch {
16    type Patch;
17
18    fn merge(&mut self, patch: Self::Patch) -> Result<(), MergePatchError>;
19}
20
21impl<T> MergePatch for T
22where
23    T: AtomicPatch,
24{
25    type Patch = Self;
26
27    fn merge(&mut self, patch: Self) -> Result<(), MergePatchError> {
28        *self = patch;
29        Ok(())
30    }
31}
32
33///
34/// MergePatchError
35///
36/// Structured failures for user-driven patch application.
37///
38
39#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
40pub enum MergePatchError {
41    #[error("invalid patch shape: expected {expected}, found {actual}")]
42    InvalidShape {
43        expected: &'static str,
44        actual: &'static str,
45    },
46
47    #[error("missing key for map operation: {operation}")]
48    MissingKey { operation: &'static str },
49
50    #[error("invalid patch cardinality: expected {expected}, found {actual}")]
51    CardinalityViolation { expected: usize, actual: usize },
52
53    #[error("patch merge failed at {path}: {source}")]
54    Context {
55        path: String,
56        #[source]
57        source: Box<Self>,
58    },
59}
60
61impl MergePatchError {
62    /// Prepend a field segment to the merge error path.
63    #[must_use]
64    pub fn with_field(self, field: impl AsRef<str>) -> Self {
65        self.with_path_segment(field.as_ref())
66    }
67
68    /// Prepend an index segment to the merge error path.
69    #[must_use]
70    pub fn with_index(self, index: usize) -> Self {
71        self.with_path_segment(format!("[{index}]"))
72    }
73
74    /// Return the full contextual path, if available.
75    #[must_use]
76    pub const fn path(&self) -> Option<&str> {
77        match self {
78            Self::Context { path, .. } => Some(path.as_str()),
79            _ => None,
80        }
81    }
82
83    /// Return the innermost, non-context merge error variant.
84    #[must_use]
85    pub fn leaf(&self) -> &Self {
86        match self {
87            Self::Context { source, .. } => source.leaf(),
88            _ => self,
89        }
90    }
91
92    #[must_use]
93    fn with_path_segment(self, segment: impl Into<String>) -> Self {
94        let segment = segment.into();
95        match self {
96            Self::Context { path, source } => Self::Context {
97                path: Self::join_segments(segment.as_str(), path.as_str()),
98                source,
99            },
100            source => Self::Context {
101                path: segment,
102                source: Box::new(source),
103            },
104        }
105    }
106
107    #[must_use]
108    fn join_segments(prefix: &str, suffix: &str) -> String {
109        if suffix.starts_with('[') {
110            format!("{prefix}{suffix}")
111        } else {
112            format!("{prefix}.{suffix}")
113        }
114    }
115}
116
117impl<T> MergePatch for Option<T>
118where
119    T: MergePatch + Default,
120{
121    type Patch = Option<T::Patch>;
122
123    fn merge(&mut self, patch: Self::Patch) -> Result<(), MergePatchError> {
124        match patch {
125            None => {
126                // Explicit delete
127                *self = None;
128            }
129            Some(inner_patch) => {
130                if let Some(inner_value) = self.as_mut() {
131                    inner_value
132                        .merge(inner_patch)
133                        .map_err(|err| err.with_field("value"))?;
134                } else {
135                    let mut new_value = T::default();
136                    new_value
137                        .merge(inner_patch)
138                        .map_err(|err| err.with_field("value"))?;
139                    *self = Some(new_value);
140                }
141            }
142        }
143
144        Ok(())
145    }
146}
147
148impl<T> MergePatch for Vec<T>
149where
150    T: MergePatch + Default,
151{
152    type Patch = Vec<ListPatch<T::Patch>>;
153
154    fn merge(&mut self, patches: Self::Patch) -> Result<(), MergePatchError> {
155        for patch in patches {
156            match patch {
157                ListPatch::Update { index, patch } => {
158                    if let Some(elem) = self.get_mut(index) {
159                        elem.merge(patch).map_err(|err| err.with_index(index))?;
160                    }
161                }
162
163                ListPatch::Insert { index, value } => {
164                    let idx = index.min(self.len());
165                    let mut elem = T::default();
166                    elem.merge(value).map_err(|err| err.with_index(idx))?;
167                    self.insert(idx, elem);
168                }
169
170                ListPatch::Push { value } => {
171                    let idx = self.len();
172                    let mut elem = T::default();
173                    elem.merge(value).map_err(|err| err.with_index(idx))?;
174                    self.push(elem);
175                }
176
177                ListPatch::Overwrite { values } => {
178                    self.clear();
179                    self.reserve(values.len());
180
181                    for (index, value) in values.into_iter().enumerate() {
182                        let mut elem = T::default();
183                        elem.merge(value).map_err(|err| err.with_index(index))?;
184                        self.push(elem);
185                    }
186                }
187
188                ListPatch::Remove { index } => {
189                    if index < self.len() {
190                        self.remove(index);
191                    }
192                }
193
194                ListPatch::Clear => self.clear(),
195            }
196        }
197
198        Ok(())
199    }
200}
201
202impl<T, S> MergePatch for HashSet<T, S>
203where
204    T: MergePatch + Clone + Default + Eq + Hash,
205    S: BuildHasher + Default,
206{
207    type Patch = Vec<SetPatch<T::Patch>>;
208
209    fn merge(&mut self, patches: Self::Patch) -> Result<(), MergePatchError> {
210        for patch in patches {
211            match patch {
212                SetPatch::Insert(value) => {
213                    let mut elem = T::default();
214                    elem.merge(value).map_err(|err| err.with_field("insert"))?;
215                    self.insert(elem);
216                }
217
218                SetPatch::Remove(value) => {
219                    let mut elem = T::default();
220                    elem.merge(value).map_err(|err| err.with_field("remove"))?;
221                    self.remove(&elem);
222                }
223
224                SetPatch::Overwrite { values } => {
225                    self.clear();
226
227                    for (index, value) in values.into_iter().enumerate() {
228                        let mut elem = T::default();
229                        elem.merge(value)
230                            .map_err(|err| err.with_field("overwrite").with_index(index))?;
231                        self.insert(elem);
232                    }
233                }
234
235                SetPatch::Clear => self.clear(),
236            }
237        }
238
239        Ok(())
240    }
241}
242
243/// Internal representation used to normalize map patches before application.
244enum MapPatchOp<K, V> {
245    Insert { key: K, value: V },
246    Remove { key: K },
247    Replace { key: K, value: V },
248    Clear,
249}
250
251impl<K, V, S> MergePatch for HashMap<K, V, S>
252where
253    K: MergePatch + Clone + Default + Eq + Hash,
254    V: MergePatch + Default,
255    S: BuildHasher + Default,
256{
257    type Patch = Vec<MapPatch<K::Patch, V::Patch>>;
258
259    #[expect(clippy::too_many_lines)]
260    fn merge(&mut self, patches: Self::Patch) -> Result<(), MergePatchError> {
261        // Phase 1: decode patch payload into concrete keys.
262        let mut ops = Vec::with_capacity(patches.len());
263        for patch in patches {
264            match patch {
265                MapPatch::Insert { key, value } => {
266                    let mut key_value = K::default();
267                    key_value
268                        .merge(key)
269                        .map_err(|err| err.with_field("insert").with_field("key"))?;
270                    ops.push(MapPatchOp::Insert {
271                        key: key_value,
272                        value,
273                    });
274                }
275                MapPatch::Remove { key } => {
276                    let mut key_value = K::default();
277                    key_value
278                        .merge(key)
279                        .map_err(|err| err.with_field("remove").with_field("key"))?;
280                    ops.push(MapPatchOp::Remove { key: key_value });
281                }
282                MapPatch::Replace { key, value } => {
283                    let mut key_value = K::default();
284                    key_value
285                        .merge(key)
286                        .map_err(|err| err.with_field("replace").with_field("key"))?;
287                    ops.push(MapPatchOp::Replace {
288                        key: key_value,
289                        value,
290                    });
291                }
292                MapPatch::Clear => ops.push(MapPatchOp::Clear),
293            }
294        }
295
296        // Phase 2: reject ambiguous patch batches to keep semantics deterministic.
297        let mut saw_clear = false;
298        let mut touched = HashSet::with_capacity(ops.len());
299        for op in &ops {
300            match op {
301                MapPatchOp::Clear => {
302                    if saw_clear {
303                        return Err(MergePatchError::InvalidShape {
304                            expected: "at most one Clear operation per map patch batch",
305                            actual: "duplicate Clear operations",
306                        });
307                    }
308                    saw_clear = true;
309                    if ops.len() != 1 {
310                        return Err(MergePatchError::CardinalityViolation {
311                            expected: 1,
312                            actual: ops.len(),
313                        });
314                    }
315                }
316                MapPatchOp::Insert { key, .. }
317                | MapPatchOp::Remove { key }
318                | MapPatchOp::Replace { key, .. } => {
319                    if saw_clear {
320                        return Err(MergePatchError::InvalidShape {
321                            expected: "Clear must be the only operation in a map patch batch",
322                            actual: "Clear combined with key operation",
323                        });
324                    }
325                    if !touched.insert(key.clone()) {
326                        return Err(MergePatchError::InvalidShape {
327                            expected: "unique key operations per map patch batch",
328                            actual: "duplicate key operation",
329                        });
330                    }
331                }
332            }
333        }
334
335        if saw_clear {
336            self.clear();
337            return Ok(());
338        }
339
340        // Phase 3: apply deterministic map operations.
341        for op in ops {
342            match op {
343                MapPatchOp::Insert { key, value } => match self.entry(key) {
344                    HashMapEntry::Occupied(mut slot) => {
345                        slot.get_mut()
346                            .merge(value)
347                            .map_err(|err| err.with_field("insert").with_field("value"))?;
348                    }
349                    HashMapEntry::Vacant(slot) => {
350                        let mut value_value = V::default();
351                        value_value
352                            .merge(value)
353                            .map_err(|err| err.with_field("insert").with_field("value"))?;
354                        slot.insert(value_value);
355                    }
356                },
357
358                MapPatchOp::Remove { key } => {
359                    if self.remove(&key).is_none() {
360                        return Err(MergePatchError::MissingKey {
361                            operation: "remove",
362                        });
363                    }
364                }
365
366                MapPatchOp::Replace { key, value } => match self.entry(key) {
367                    HashMapEntry::Occupied(mut slot) => {
368                        slot.get_mut()
369                            .merge(value)
370                            .map_err(|err| err.with_field("replace").with_field("value"))?;
371                    }
372                    HashMapEntry::Vacant(_) => {
373                        return Err(MergePatchError::MissingKey {
374                            operation: "replace",
375                        });
376                    }
377                },
378
379                MapPatchOp::Clear => {
380                    return Err(MergePatchError::InvalidShape {
381                        expected: "Clear to be handled before apply phase",
382                        actual: "Clear reached apply phase",
383                    });
384                }
385            }
386        }
387
388        Ok(())
389    }
390}
391
392impl<T> MergePatch for BTreeSet<T>
393where
394    T: MergePatch + Clone + Default + Ord,
395{
396    type Patch = Vec<SetPatch<T::Patch>>;
397
398    fn merge(&mut self, patches: Self::Patch) -> Result<(), MergePatchError> {
399        for patch in patches {
400            match patch {
401                SetPatch::Insert(value) => {
402                    let mut elem = T::default();
403                    elem.merge(value).map_err(|err| err.with_field("insert"))?;
404                    self.insert(elem);
405                }
406                SetPatch::Remove(value) => {
407                    let mut elem = T::default();
408                    elem.merge(value).map_err(|err| err.with_field("remove"))?;
409                    self.remove(&elem);
410                }
411                SetPatch::Overwrite { values } => {
412                    self.clear();
413
414                    for (index, value) in values.into_iter().enumerate() {
415                        let mut elem = T::default();
416                        elem.merge(value)
417                            .map_err(|err| err.with_field("overwrite").with_index(index))?;
418                        self.insert(elem);
419                    }
420                }
421                SetPatch::Clear => self.clear(),
422            }
423        }
424
425        Ok(())
426    }
427}
428
429impl<K, V> MergePatch for BTreeMap<K, V>
430where
431    K: MergePatch + Clone + Default + Ord,
432    V: MergePatch + Default,
433{
434    type Patch = Vec<MapPatch<K::Patch, V::Patch>>;
435
436    #[expect(clippy::too_many_lines)]
437    fn merge(&mut self, patches: Self::Patch) -> Result<(), MergePatchError> {
438        // Phase 1: decode patch payload into concrete keys.
439        let mut ops = Vec::with_capacity(patches.len());
440        for patch in patches {
441            match patch {
442                MapPatch::Insert { key, value } => {
443                    let mut key_value = K::default();
444                    key_value
445                        .merge(key)
446                        .map_err(|err| err.with_field("insert").with_field("key"))?;
447                    ops.push(MapPatchOp::Insert {
448                        key: key_value,
449                        value,
450                    });
451                }
452                MapPatch::Remove { key } => {
453                    let mut key_value = K::default();
454                    key_value
455                        .merge(key)
456                        .map_err(|err| err.with_field("remove").with_field("key"))?;
457                    ops.push(MapPatchOp::Remove { key: key_value });
458                }
459                MapPatch::Replace { key, value } => {
460                    let mut key_value = K::default();
461                    key_value
462                        .merge(key)
463                        .map_err(|err| err.with_field("replace").with_field("key"))?;
464                    ops.push(MapPatchOp::Replace {
465                        key: key_value,
466                        value,
467                    });
468                }
469                MapPatch::Clear => ops.push(MapPatchOp::Clear),
470            }
471        }
472
473        // Phase 2: reject ambiguous patch batches to keep semantics deterministic.
474        let mut saw_clear = false;
475        let mut touched = BTreeSet::new();
476        for op in &ops {
477            match op {
478                MapPatchOp::Clear => {
479                    if saw_clear {
480                        return Err(MergePatchError::InvalidShape {
481                            expected: "at most one Clear operation per map patch batch",
482                            actual: "duplicate Clear operations",
483                        });
484                    }
485                    saw_clear = true;
486                    if ops.len() != 1 {
487                        return Err(MergePatchError::CardinalityViolation {
488                            expected: 1,
489                            actual: ops.len(),
490                        });
491                    }
492                }
493                MapPatchOp::Insert { key, .. }
494                | MapPatchOp::Remove { key }
495                | MapPatchOp::Replace { key, .. } => {
496                    if saw_clear {
497                        return Err(MergePatchError::InvalidShape {
498                            expected: "Clear must be the only operation in a map patch batch",
499                            actual: "Clear combined with key operation",
500                        });
501                    }
502                    if !touched.insert(key.clone()) {
503                        return Err(MergePatchError::InvalidShape {
504                            expected: "unique key operations per map patch batch",
505                            actual: "duplicate key operation",
506                        });
507                    }
508                }
509            }
510        }
511        if saw_clear {
512            self.clear();
513            return Ok(());
514        }
515
516        // Phase 3: apply deterministic map operations.
517        for op in ops {
518            match op {
519                MapPatchOp::Insert { key, value } => match self.entry(key) {
520                    BTreeMapEntry::Occupied(mut slot) => {
521                        slot.get_mut()
522                            .merge(value)
523                            .map_err(|err| err.with_field("insert").with_field("value"))?;
524                    }
525                    BTreeMapEntry::Vacant(slot) => {
526                        let mut value_value = V::default();
527                        value_value
528                            .merge(value)
529                            .map_err(|err| err.with_field("insert").with_field("value"))?;
530                        slot.insert(value_value);
531                    }
532                },
533                MapPatchOp::Remove { key } => {
534                    if self.remove(&key).is_none() {
535                        return Err(MergePatchError::MissingKey {
536                            operation: "remove",
537                        });
538                    }
539                }
540                MapPatchOp::Replace { key, value } => match self.entry(key) {
541                    BTreeMapEntry::Occupied(mut slot) => {
542                        slot.get_mut()
543                            .merge(value)
544                            .map_err(|err| err.with_field("replace").with_field("value"))?;
545                    }
546                    BTreeMapEntry::Vacant(_) => {
547                        return Err(MergePatchError::MissingKey {
548                            operation: "replace",
549                        });
550                    }
551                },
552                MapPatchOp::Clear => {
553                    return Err(MergePatchError::InvalidShape {
554                        expected: "Clear to be handled before apply phase",
555                        actual: "Clear reached apply phase",
556                    });
557                }
558            }
559        }
560
561        Ok(())
562    }
563}
564
565macro_rules! impl_update_view {
566    ($($type:ty),*) => {
567        $(
568            impl MergePatch for $type {
569                type Patch = Self;
570
571                fn merge(
572                    &mut self,
573                    patch: Self::Patch,
574                ) -> Result<(), MergePatchError> {
575                    *self = patch;
576                    Ok(())
577                }
578            }
579        )*
580    };
581}
582
583impl_update_view!(bool, i8, i16, i32, i64, u8, u16, u32, u64, String);