From 2aa76d370d960153e0d098bad61918ca92b9fed6 Mon Sep 17 00:00:00 2001 From: tv Date: Sat, 31 Oct 2026 03:51:10 +0100 Subject: unify path deleters --- src/api/store.rs | 2 +- src/document/store.rs | 80 +++++++++++++++++++-------------------------------- 2 files changed, 31 insertions(+), 51 deletions(-) (limited to 'src') diff --git a/src/api/store.rs b/src/api/store.rs index f60d30d..9c8cafd 100644 --- a/src/api/store.rs +++ b/src/api/store.rs @@ -306,7 +306,7 @@ pub(crate) async fn delete_store_entry( let prune = query.prune; state - .mutate_doc(move |doc| store::delete_at(doc, &path, prune)) + .mutate_doc(move |doc| store::delete_path(doc, &path, prune)) .await .map_err(|e| store_status(e, "Failed to delete store value"))?; diff --git a/src/document/store.rs b/src/document/store.rs index 9134795..b698d19 100644 --- a/src/document/store.rs +++ b/src/document/store.rs @@ -603,74 +603,54 @@ pub(crate) fn copy_path_to( ) } -/// Deletes the value at `path` as the API names it: through lists too, where a segment -/// is an index (`list_index`; the items after it move down one); `prune` doesn't reach -/// through a list. Within maps only, as `delete_path`. -pub(crate) fn delete_at( +/// Deletes the value at `path`, through lists too, where a segment is an index +/// (`list_index`; the items after it move down one). With `prune`, the maps above it +/// that it leaves empty go too, up to (not including) the root or a list. +pub(crate) fn delete_path( doc: &mut AutoCommit, path: &[String], prune: bool, ) -> Result<(), StoreError> { let (leaf, parents) = path.split_last().ok_or(StoreError::NotFound)?; - let mut current = Resolved::Maps(vec![automerge::ROOT]); - let mut through_list = false; + + // levels[d] = what path[..d] resolves to + let mut levels = vec![Resolved::Maps(vec![automerge::ROOT])]; for segment in parents { - if let Resolved::Other(Value::Object(ObjType::List), _) = current { - through_list = true; - } - current = step(doc, ¤t, segment, None)?.ok_or(StoreError::NotFound)?; + let next = step(doc, levels.last().expect("non-empty"), segment, None)? + .ok_or(StoreError::NotFound)?; + levels.push(next); } - match (¤t, through_list) { - (Resolved::Other(Value::Object(ObjType::List), list), _) => { - let index = list_index(leaf).ok_or(StoreError::NotFound)?; - if index >= doc.length(list) { + + match levels.last().expect("non-empty") { + Resolved::Maps(maps) => { + if resolve(doc, maps, leaf, None)?.is_none() { return Err(StoreError::NotFound); } - doc.delete(list, index)?; - Ok(()) + delete_in_all(doc, maps, leaf)?; } - (Resolved::Maps(maps), true) => { - if resolve(doc, maps, leaf, None)?.is_none() { + Resolved::Other(Value::Object(ObjType::List), list) => { + let index = list_index(leaf).ok_or(StoreError::NotFound)?; + if index >= doc.length(list) { return Err(StoreError::NotFound); } - delete_in_all(doc, maps, leaf) + doc.delete(list, index)?; + return Ok(()); } - (Resolved::Maps(_), false) => delete_path(doc, path, prune), - (Resolved::Other(..), _) => Err(StoreError::NotFound), + Resolved::Other(..) => return Err(StoreError::NotFound), } -} - -/// Deletes the value at `path`, optionally pruning ancestors that become empty. -pub(crate) fn delete_path( - doc: &mut AutoCommit, - path: &[String], - prune: bool, -) -> Result<(), StoreError> { - let (leaf, parents) = path.split_last().ok_or(StoreError::NotFound)?; - - // levels[d] = merged maps at path[..d] - let mut levels = vec![vec![automerge::ROOT]]; - for segment in parents { - let next = match resolve(doc, levels.last().expect("non-empty"), segment, None)? { - Some(Resolved::Maps(maps)) => maps, - _ => return Err(StoreError::NotFound), - }; - levels.push(next); - } - - let maps = levels.last().expect("non-empty"); - if resolve(doc, maps, leaf, None)?.is_none() { - return Err(StoreError::NotFound); - } - delete_in_all(doc, maps, leaf)?; if prune { - // Never prune the root (depth 0). + // Never prune the root (depth 0), nor through a list. for depth in (1..levels.len()).rev() { - if !merged_keys(doc, &levels[depth]).is_empty() { + let (Resolved::Maps(above), Resolved::Maps(maps)) = + (&levels[depth - 1], &levels[depth]) + else { + break; + }; + if !merged_keys(doc, maps).is_empty() { break; } - delete_in_all(doc, &levels[depth - 1], &path[depth - 1])?; + delete_in_all(doc, above, &path[depth - 1])?; } } @@ -1051,7 +1031,7 @@ mod tests { read_path(&doc, &path("list")).unwrap(), Some(json!(["a", {"k": 2}, "d", "e"])) ); - delete_at(&mut doc, &path("list/2"), false).unwrap(); + delete_path(&mut doc, &path("list/2"), false).unwrap(); assert_eq!( read_path(&doc, &path("list")).unwrap(), Some(json!(["a", {"k": 2}, "e"])) -- cgit v1.3.1