Skip to main content

icydb_core/patch/
merge.rs

1use crate::{
2    patch::{ListPatch, MapPatch, SetPatch},
3    traits::{Atomic, UpdateView},
4};
5use std::{
6    collections::{
7        BTreeMap, BTreeSet, HashMap, HashSet, btree_map::Entry as BTreeMapEntry,
8        hash_map::Entry as HashMapEntry,
9    },
10    hash::{BuildHasher, Hash},
11};
12use thiserror::Error as ThisError;
13
14///
15/// MergePatch
16///
17
18pub trait MergePatch: UpdateView {
19    fn merge(&mut self, patch: Self::UpdateViewType) -> Result<(), MergePatchError>;
20}
21
22impl<T> MergePatch for T
23where
24    T: Atomic + UpdateView<UpdateViewType = T>,
25{
26    fn merge(&mut self, patch: Self::UpdateViewType) -> Result<(), MergePatchError> {
27        *self = patch;
28
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    fn merge(&mut self, patch: Self::UpdateViewType) -> Result<(), MergePatchError> {
122        match patch {
123            None => {
124                // Explicit delete
125                *self = None;
126            }
127            Some(inner_patch) => {
128                if let Some(inner_value) = self.as_mut() {
129                    inner_value
130                        .merge(inner_patch)
131                        .map_err(|err| err.with_field("value"))?;
132                } else {
133                    let mut new_value = T::default();
134                    new_value
135                        .merge(inner_patch)
136                        .map_err(|err| err.with_field("value"))?;
137                    *self = Some(new_value);
138                }
139            }
140        }
141
142        Ok(())
143    }
144}
145
146impl<T> MergePatch for Vec<T>
147where
148    T: MergePatch + Default,
149{
150    fn merge(&mut self, patches: Self::UpdateViewType) -> Result<(), MergePatchError> {
151        for patch in patches {
152            match patch {
153                ListPatch::Update { index, patch } => {
154                    if let Some(elem) = self.get_mut(index) {
155                        elem.merge(patch).map_err(|err| err.with_index(index))?;
156                    }
157                }
158
159                ListPatch::Insert { index, value } => {
160                    let idx = index.min(self.len());
161                    let mut elem = T::default();
162                    elem.merge(value).map_err(|err| err.with_index(idx))?;
163                    self.insert(idx, elem);
164                }
165
166                ListPatch::Push { value } => {
167                    let idx = self.len();
168                    let mut elem = T::default();
169                    elem.merge(value).map_err(|err| err.with_index(idx))?;
170                    self.push(elem);
171                }
172
173                ListPatch::Overwrite { values } => {
174                    self.clear();
175                    self.reserve(values.len());
176
177                    for (index, value) in values.into_iter().enumerate() {
178                        let mut elem = T::default();
179                        elem.merge(value).map_err(|err| err.with_index(index))?;
180                        self.push(elem);
181                    }
182                }
183
184                ListPatch::Remove { index } => {
185                    if index < self.len() {
186                        self.remove(index);
187                    }
188                }
189
190                ListPatch::Clear => self.clear(),
191            }
192        }
193
194        Ok(())
195    }
196}
197
198impl<T, S> MergePatch for HashSet<T, S>
199where
200    T: MergePatch + Clone + Default + Eq + Hash,
201    S: BuildHasher + Default,
202{
203    fn merge(&mut self, patches: Self::UpdateViewType) -> Result<(), MergePatchError> {
204        for patch in patches {
205            match patch {
206                SetPatch::Insert(value) => {
207                    let mut elem = T::default();
208                    elem.merge(value).map_err(|err| err.with_field("insert"))?;
209                    self.insert(elem);
210                }
211
212                SetPatch::Remove(value) => {
213                    let mut elem = T::default();
214                    elem.merge(value).map_err(|err| err.with_field("remove"))?;
215                    self.remove(&elem);
216                }
217
218                SetPatch::Overwrite { values } => {
219                    self.clear();
220
221                    for (index, value) in values.into_iter().enumerate() {
222                        let mut elem = T::default();
223                        elem.merge(value)
224                            .map_err(|err| err.with_field("overwrite").with_index(index))?;
225                        self.insert(elem);
226                    }
227                }
228
229                SetPatch::Clear => self.clear(),
230            }
231        }
232
233        Ok(())
234    }
235}
236
237/// Internal representation used to normalize map patches before application.
238enum MapPatchOp<K, V> {
239    Insert { key: K, value: V },
240    Remove { key: K },
241    Replace { key: K, value: V },
242    Clear,
243}
244
245impl<K, V, S> MergePatch for HashMap<K, V, S>
246where
247    K: MergePatch + Clone + Default + Eq + Hash,
248    V: MergePatch + Default,
249    S: BuildHasher + Default,
250{
251    #[expect(clippy::too_many_lines)]
252    fn merge(&mut self, patches: Self::UpdateViewType) -> Result<(), MergePatchError> {
253        // Phase 1: decode patch payload into concrete keys.
254        let mut ops = Vec::with_capacity(patches.len());
255        for patch in patches {
256            match patch {
257                MapPatch::Insert { key, value } => {
258                    let mut key_value = K::default();
259                    key_value
260                        .merge(key)
261                        .map_err(|err| err.with_field("insert").with_field("key"))?;
262                    ops.push(MapPatchOp::Insert {
263                        key: key_value,
264                        value,
265                    });
266                }
267                MapPatch::Remove { key } => {
268                    let mut key_value = K::default();
269                    key_value
270                        .merge(key)
271                        .map_err(|err| err.with_field("remove").with_field("key"))?;
272                    ops.push(MapPatchOp::Remove { key: key_value });
273                }
274                MapPatch::Replace { key, value } => {
275                    let mut key_value = K::default();
276                    key_value
277                        .merge(key)
278                        .map_err(|err| err.with_field("replace").with_field("key"))?;
279                    ops.push(MapPatchOp::Replace {
280                        key: key_value,
281                        value,
282                    });
283                }
284                MapPatch::Clear => ops.push(MapPatchOp::Clear),
285            }
286        }
287
288        // Phase 2: reject ambiguous patch batches to keep semantics deterministic.
289        let mut saw_clear = false;
290        let mut touched = HashSet::with_capacity(ops.len());
291        for op in &ops {
292            match op {
293                MapPatchOp::Clear => {
294                    if saw_clear {
295                        return Err(MergePatchError::InvalidShape {
296                            expected: "at most one Clear operation per map patch batch",
297                            actual: "duplicate Clear operations",
298                        });
299                    }
300                    saw_clear = true;
301                    if ops.len() != 1 {
302                        return Err(MergePatchError::CardinalityViolation {
303                            expected: 1,
304                            actual: ops.len(),
305                        });
306                    }
307                }
308                MapPatchOp::Insert { key, .. }
309                | MapPatchOp::Remove { key }
310                | MapPatchOp::Replace { key, .. } => {
311                    if saw_clear {
312                        return Err(MergePatchError::InvalidShape {
313                            expected: "Clear must be the only operation in a map patch batch",
314                            actual: "Clear combined with key operation",
315                        });
316                    }
317                    if !touched.insert(key.clone()) {
318                        return Err(MergePatchError::InvalidShape {
319                            expected: "unique key operations per map patch batch",
320                            actual: "duplicate key operation",
321                        });
322                    }
323                }
324            }
325        }
326
327        if saw_clear {
328            self.clear();
329            return Ok(());
330        }
331
332        // Phase 3: apply deterministic map operations.
333        for op in ops {
334            match op {
335                MapPatchOp::Insert { key, value } => match self.entry(key) {
336                    HashMapEntry::Occupied(mut slot) => {
337                        slot.get_mut()
338                            .merge(value)
339                            .map_err(|err| err.with_field("insert").with_field("value"))?;
340                    }
341                    HashMapEntry::Vacant(slot) => {
342                        let mut value_value = V::default();
343                        value_value
344                            .merge(value)
345                            .map_err(|err| err.with_field("insert").with_field("value"))?;
346                        slot.insert(value_value);
347                    }
348                },
349
350                MapPatchOp::Remove { key } => {
351                    if self.remove(&key).is_none() {
352                        return Err(MergePatchError::MissingKey {
353                            operation: "remove",
354                        });
355                    }
356                }
357
358                MapPatchOp::Replace { key, value } => match self.entry(key) {
359                    HashMapEntry::Occupied(mut slot) => {
360                        slot.get_mut()
361                            .merge(value)
362                            .map_err(|err| err.with_field("replace").with_field("value"))?;
363                    }
364                    HashMapEntry::Vacant(_) => {
365                        return Err(MergePatchError::MissingKey {
366                            operation: "replace",
367                        });
368                    }
369                },
370
371                MapPatchOp::Clear => {
372                    return Err(MergePatchError::InvalidShape {
373                        expected: "Clear to be handled before apply phase",
374                        actual: "Clear reached apply phase",
375                    });
376                }
377            }
378        }
379
380        Ok(())
381    }
382}
383
384impl<T> MergePatch for BTreeSet<T>
385where
386    T: MergePatch + Clone + Default + Ord,
387{
388    fn merge(&mut self, patches: Self::UpdateViewType) -> Result<(), MergePatchError> {
389        for patch in patches {
390            match patch {
391                SetPatch::Insert(value) => {
392                    let mut elem = T::default();
393                    elem.merge(value).map_err(|err| err.with_field("insert"))?;
394                    self.insert(elem);
395                }
396                SetPatch::Remove(value) => {
397                    let mut elem = T::default();
398                    elem.merge(value).map_err(|err| err.with_field("remove"))?;
399                    self.remove(&elem);
400                }
401                SetPatch::Overwrite { values } => {
402                    self.clear();
403
404                    for (index, value) in values.into_iter().enumerate() {
405                        let mut elem = T::default();
406                        elem.merge(value)
407                            .map_err(|err| err.with_field("overwrite").with_index(index))?;
408                        self.insert(elem);
409                    }
410                }
411                SetPatch::Clear => self.clear(),
412            }
413        }
414
415        Ok(())
416    }
417}
418
419impl<K, V> MergePatch for BTreeMap<K, V>
420where
421    K: MergePatch + Clone + Default + Ord,
422    V: MergePatch + Default,
423{
424    #[expect(clippy::too_many_lines)]
425    fn merge(&mut self, patches: Self::UpdateViewType) -> Result<(), MergePatchError> {
426        // Phase 1: decode patch payload into concrete keys.
427        let mut ops = Vec::with_capacity(patches.len());
428        for patch in patches {
429            match patch {
430                MapPatch::Insert { key, value } => {
431                    let mut key_value = K::default();
432                    key_value
433                        .merge(key)
434                        .map_err(|err| err.with_field("insert").with_field("key"))?;
435                    ops.push(MapPatchOp::Insert {
436                        key: key_value,
437                        value,
438                    });
439                }
440                MapPatch::Remove { key } => {
441                    let mut key_value = K::default();
442                    key_value
443                        .merge(key)
444                        .map_err(|err| err.with_field("remove").with_field("key"))?;
445                    ops.push(MapPatchOp::Remove { key: key_value });
446                }
447                MapPatch::Replace { key, value } => {
448                    let mut key_value = K::default();
449                    key_value
450                        .merge(key)
451                        .map_err(|err| err.with_field("replace").with_field("key"))?;
452                    ops.push(MapPatchOp::Replace {
453                        key: key_value,
454                        value,
455                    });
456                }
457                MapPatch::Clear => ops.push(MapPatchOp::Clear),
458            }
459        }
460
461        // Phase 2: reject ambiguous patch batches to keep semantics deterministic.
462        let mut saw_clear = false;
463        let mut touched = BTreeSet::new();
464        for op in &ops {
465            match op {
466                MapPatchOp::Clear => {
467                    if saw_clear {
468                        return Err(MergePatchError::InvalidShape {
469                            expected: "at most one Clear operation per map patch batch",
470                            actual: "duplicate Clear operations",
471                        });
472                    }
473                    saw_clear = true;
474                    if ops.len() != 1 {
475                        return Err(MergePatchError::CardinalityViolation {
476                            expected: 1,
477                            actual: ops.len(),
478                        });
479                    }
480                }
481                MapPatchOp::Insert { key, .. }
482                | MapPatchOp::Remove { key }
483                | MapPatchOp::Replace { key, .. } => {
484                    if saw_clear {
485                        return Err(MergePatchError::InvalidShape {
486                            expected: "Clear must be the only operation in a map patch batch",
487                            actual: "Clear combined with key operation",
488                        });
489                    }
490                    if !touched.insert(key.clone()) {
491                        return Err(MergePatchError::InvalidShape {
492                            expected: "unique key operations per map patch batch",
493                            actual: "duplicate key operation",
494                        });
495                    }
496                }
497            }
498        }
499        if saw_clear {
500            self.clear();
501            return Ok(());
502        }
503
504        // Phase 3: apply deterministic map operations.
505        for op in ops {
506            match op {
507                MapPatchOp::Insert { key, value } => match self.entry(key) {
508                    BTreeMapEntry::Occupied(mut slot) => {
509                        slot.get_mut()
510                            .merge(value)
511                            .map_err(|err| err.with_field("insert").with_field("value"))?;
512                    }
513                    BTreeMapEntry::Vacant(slot) => {
514                        let mut value_value = V::default();
515                        value_value
516                            .merge(value)
517                            .map_err(|err| err.with_field("insert").with_field("value"))?;
518                        slot.insert(value_value);
519                    }
520                },
521                MapPatchOp::Remove { key } => {
522                    if self.remove(&key).is_none() {
523                        return Err(MergePatchError::MissingKey {
524                            operation: "remove",
525                        });
526                    }
527                }
528                MapPatchOp::Replace { key, value } => match self.entry(key) {
529                    BTreeMapEntry::Occupied(mut slot) => {
530                        slot.get_mut()
531                            .merge(value)
532                            .map_err(|err| err.with_field("replace").with_field("value"))?;
533                    }
534                    BTreeMapEntry::Vacant(_) => {
535                        return Err(MergePatchError::MissingKey {
536                            operation: "replace",
537                        });
538                    }
539                },
540                MapPatchOp::Clear => {
541                    return Err(MergePatchError::InvalidShape {
542                        expected: "Clear to be handled before apply phase",
543                        actual: "Clear reached apply phase",
544                    });
545                }
546            }
547        }
548
549        Ok(())
550    }
551}