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