summaryrefslogtreecommitdiffstats
path: root/src/store.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/store.rs')
-rw-r--r--src/store.rs63
1 files changed, 57 insertions, 6 deletions
diff --git a/src/store.rs b/src/store.rs
index 1f1398e..1815635 100644
--- a/src/store.rs
+++ b/src/store.rs
@@ -250,9 +250,19 @@ pub fn write_path(
value: &JsonValue,
) -> Result<(), StoreError> {
let (leaf, parents) = path.split_last().ok_or(StoreError::NotFound)?;
+ let maps = ensure_maps(doc, parents)?;
+ // Replace semantics: clear the key in every merged parent, or an old map
+ // would merge into the new value.
+ delete_in_all(doc, &maps, leaf)?;
+ json_to_automerge(doc, &maps[0], leaf, value)?;
+ Ok(())
+}
+
+/// The merged maps at `path`, creating missing maps on the way.
+fn ensure_maps(doc: &mut AutoCommit, path: &[String]) -> Result<Vec<ObjId>, StoreError> {
let mut maps = vec![automerge::ROOT];
- for segment in parents {
+ for segment in path {
maps = match resolve(doc, &maps, segment, None)? {
Some(Resolved::Maps(next)) => next,
Some(Resolved::Other(..)) => return Err(StoreError::Conflict),
@@ -260,12 +270,53 @@ pub fn write_path(
None => vec![doc.put_object(&maps[0], segment.as_str(), ObjType::Map)?],
};
}
+ Ok(maps)
+}
- // Replace semantics: clear the key in every merged parent, or an old map
- // would merge into the new value.
- delete_in_all(doc, &maps, leaf)?;
- json_to_automerge(doc, &maps[0], leaf, value)?;
- Ok(())
+/// The resolved value at `path` (the empty path is the root map).
+fn resolve_at(doc: &AutoCommit, path: &[String]) -> Result<Option<Resolved>, StoreError> {
+ let mut current = Resolved::Maps(vec![automerge::ROOT]);
+ for segment in path {
+ let Resolved::Maps(maps) = &current else {
+ return Ok(None);
+ };
+ match resolve(doc, maps, segment, None)? {
+ Some(next) => current = next,
+ None => return Ok(None),
+ }
+ }
+ Ok(Some(current))
+}
+
+/// Makes `dst` hold at `path` what `src`'s merged view holds there: a type-preserving
+/// copy (see `materialize_into`) if it exists, a delete otherwise. Replaces the whole
+/// subtree at `path`; the path must not be empty.
+pub(crate) fn copy_path(
+ src: &AutoCommit,
+ dst: &mut AutoCommit,
+ path: &[String],
+) -> Result<(), StoreError> {
+ let (leaf, parents) = path.split_last().ok_or(StoreError::NotFound)?;
+
+ let Some(resolved) = resolve_at(src, path)? else {
+ return match delete_path(dst, path, false) {
+ Ok(()) | Err(StoreError::NotFound) => Ok(()),
+ Err(error) => Err(error),
+ };
+ };
+
+ let maps = ensure_maps(dst, parents)?;
+ delete_in_all(dst, &maps, leaf)?;
+ let mut trace = path.to_vec();
+ copy_resolved(
+ src,
+ &resolved,
+ dst,
+ &maps[0],
+ Slot::Key(leaf),
+ &mut trace,
+ &|_| false,
+ )
}
/// Deletes the value at `path`, optionally pruning ancestors that become empty.