aboutsummaryrefslogtreecommitdiffstats
path: root/src/document/store.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-10-30 20:34:47 +0100
committertv <tv@krebsco.de>2026-10-30 20:34:47 +0100
commita29a38db0ad7fc8d42a533145c8a489d97702fb5 (patch)
treeae1689804484d2aceabf106bb82cd4f0fcf97b36 /src/document/store.rs
parentcaf212f916ce8fb31d7dba8bbe6d537092d324d5 (diff)
add support to insert values at list index
Diffstat (limited to 'src/document/store.rs')
-rw-r--r--src/document/store.rs77
1 files changed, 77 insertions, 0 deletions
diff --git a/src/document/store.rs b/src/document/store.rs
index 89058fb..13a3fc3 100644
--- a/src/document/store.rs
+++ b/src/document/store.rs
@@ -15,6 +15,12 @@ pub(crate) enum StoreError {
Conflict,
#[error("value does not exist")]
NotFound,
+ /// An insert where there is no list.
+ #[error("not a list")]
+ NotAList,
+ /// An insert past a list's end.
+ #[error("index {index} is past the list's end ({length} items)")]
+ PastTheEnd { index: usize, length: usize },
#[error(transparent)]
Doc(#[from] automerge::AutomergeError),
#[error(transparent)]
@@ -357,6 +363,29 @@ pub(crate) fn write_path(
Ok(())
}
+/// Inserts `value` into the list at `path`: at `index` (the items from there on move
+/// up one), or at the end without one. Returns where it went. An index past the end is
+/// an error (`PastTheEnd`), as is a path without a list (`NotAList`, `NotFound`).
+pub(crate) fn insert_path(
+ doc: &mut AutoCommit,
+ path: &[String],
+ index: Option<usize>,
+ value: &JsonValue,
+) -> Result<usize, StoreError> {
+ let list = match resolve_at(doc, path)? {
+ None => return Err(StoreError::NotFound),
+ Some(Resolved::Other(Value::Object(ObjType::List), id)) => id,
+ Some(_) => return Err(StoreError::NotAList),
+ };
+ let length = doc.length(&list);
+ let index = index.unwrap_or(length);
+ if index > length {
+ return Err(StoreError::PastTheEnd { index, length });
+ }
+ write_value(doc, &list, Slot::Index(index), value)?;
+ Ok(index)
+}
+
/// 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];
@@ -806,3 +835,51 @@ fn nested<T>(
}
out
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use serde_json::json;
+
+ fn path(at: &str) -> Vec<String> {
+ at.split('/').map(str::to_owned).collect()
+ }
+
+ #[test]
+ fn inserts_into_lists() {
+ let mut doc = AutoCommit::new();
+ write_path(&mut doc, &path("list"), &json!(["b", "d"])).unwrap();
+ assert_eq!(
+ insert_path(&mut doc, &path("list"), Some(0), &json!("a")).unwrap(),
+ 0
+ );
+ assert_eq!(
+ insert_path(&mut doc, &path("list"), Some(2), &json!("c")).unwrap(),
+ 2
+ );
+ assert_eq!(
+ insert_path(&mut doc, &path("list"), None, &json!({"e": 5})).unwrap(),
+ 4
+ );
+ assert_eq!(
+ read_path(&doc, &path("list")).unwrap(),
+ Some(json!(["a", "b", "c", "d", {"e": 5}]))
+ );
+ assert!(matches!(
+ insert_path(&mut doc, &path("list"), Some(6), &json!(1)),
+ Err(StoreError::PastTheEnd {
+ index: 6,
+ length: 5
+ })
+ ));
+ write_path(&mut doc, &path("map"), &json!({"k": 1})).unwrap();
+ assert!(matches!(
+ insert_path(&mut doc, &path("map"), None, &json!(1)),
+ Err(StoreError::NotAList)
+ ));
+ assert!(matches!(
+ insert_path(&mut doc, &path("nothing"), None, &json!(1)),
+ Err(StoreError::NotFound)
+ ));
+ }
+}