diff options
| author | tv <tv@krebsco.de> | 2026-10-31 19:51:52 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-10-31 19:51:52 +0100 |
| commit | f4b0a7c5159d418e22682d2bd7d413fb3587ca7f (patch) | |
| tree | 14a8a240970c43cc06e0042a376c80bd9f7b3418 /src | |
| parent | c83604d8f82b6c67a64e3cee6f5c012ee4a10fc1 (diff) | |
store function accept any document
Diffstat (limited to 'src')
| -rw-r--r-- | src/document/mod.rs | 4 | ||||
| -rw-r--r-- | src/document/store.rs | 92 | ||||
| -rw-r--r-- | src/remake/records.rs | 16 | ||||
| -rw-r--r-- | src/swarm/members.rs | 12 |
4 files changed, 71 insertions, 53 deletions
diff --git a/src/document/mod.rs b/src/document/mod.rs index a348774..9633241 100644 --- a/src/document/mod.rs +++ b/src/document/mod.rs @@ -2,8 +2,10 @@ //! (merged-view paths over JSON) the HTTP API and remakes work with. /// The engine's types, under the names the rest of the crate uses: the document -/// (Automerge's `AutoCommit`), its changes and their hashes, and its writers (actors). +/// (Automerge's `AutoCommit`), its changes and their hashes, and its writers (actors); +/// and what can be written like a document (`Transactable`). /// Only `document` and `remake` use Automerge itself (`architecture.md`, The document). +pub(crate) use automerge::transaction::Transactable; pub(crate) use automerge::{ActorId, AutoCommit as Document, Change, ChangeHash}; /// The whole document as bytes, as the engine saves it: a backup, the document diff --git a/src/document/store.rs b/src/document/store.rs index c6c428c..7d54626 100644 --- a/src/document/store.rs +++ b/src/document/store.rs @@ -118,7 +118,7 @@ enum Resolved { /// All concurrent values of `key` across a merged set of parent maps, newest first. fn candidates( - doc: &AutoCommit, + doc: &impl ReadDoc, parents: &[ObjId], prop: impl Into<Prop> + Clone, ) -> Result<Vec<(Value<'static>, ObjId)>, StoreError> { @@ -133,7 +133,7 @@ fn candidates( } fn resolve( - doc: &AutoCommit, + doc: &impl ReadDoc, parents: &[ObjId], key: &str, log: Option<&mut ConflictLog>, @@ -143,7 +143,7 @@ fn resolve( /// The item at `index` of `list` (concurrent writes there resolved as for a key). fn resolve_item( - doc: &AutoCommit, + doc: &impl ReadDoc, list: &ObjId, index: usize, log: Option<&mut ConflictLog>, @@ -163,7 +163,7 @@ fn resolve_item( /// What `segment` names within `current`: a key of merged maps, or the item of a list /// at the index it spells (`list_index`); None for anything else, and in a scalar. fn step( - doc: &AutoCommit, + doc: &impl ReadDoc, current: &Resolved, segment: &str, log: Option<&mut ConflictLog>, @@ -197,7 +197,7 @@ fn from_the_end(segment: &str) -> Option<usize> { /// None if it has none. An index before the start names nothing (`NotFound`). Only /// segments within a list count: in a map, `-1` is a key like any other. pub(crate) fn positive_path( - doc: &AutoCommit, + doc: &impl ReadDoc, path: &[String], insertion: bool, ) -> Result<Option<Vec<String>>, StoreError> { @@ -228,7 +228,7 @@ pub(crate) fn positive_path( /// Resolves `key` (a map key or a list index) in `parents`; `name` is how conflict /// reports name it. fn resolve_prop( - doc: &AutoCommit, + doc: &impl ReadDoc, parents: &[ObjId], prop: impl Into<Prop> + Clone, name: &str, @@ -261,12 +261,12 @@ fn resolve_prop( } } -fn merged_keys(doc: &AutoCommit, maps: &[ObjId]) -> BTreeSet<String> { +fn merged_keys(doc: &impl ReadDoc, maps: &[ObjId]) -> BTreeSet<String> { maps.iter().flat_map(|map| doc.keys(map)).collect() } fn resolved_to_json( - doc: &AutoCommit, + doc: &impl ReadDoc, resolved: &Resolved, mut log: Option<&mut ConflictLog>, ) -> Result<JsonValue, StoreError> { @@ -316,7 +316,7 @@ fn resolved_to_json( } fn object_to_json( - doc: &AutoCommit, + doc: &impl ReadDoc, id: &ObjId, mut log: Option<&mut ConflictLog>, ) -> Result<JsonValue, StoreError> { @@ -340,7 +340,7 @@ fn object_to_json( } } -fn delete_in_all(doc: &mut AutoCommit, maps: &[ObjId], key: &str) -> Result<(), StoreError> { +fn delete_in_all(doc: &mut impl Transactable, maps: &[ObjId], key: &str) -> Result<(), StoreError> { for map in maps { if doc.get(map, key)?.is_some() { doc.delete(map, key)?; @@ -350,7 +350,7 @@ fn delete_in_all(doc: &mut AutoCommit, maps: &[ObjId], key: &str) -> Result<(), } fn read_path_inner( - doc: &AutoCommit, + doc: &impl ReadDoc, path: &[String], mut log: Option<&mut ConflictLog>, ) -> Result<Option<JsonValue>, StoreError> { @@ -362,14 +362,14 @@ fn read_path_inner( /// Reads a path; the empty path is the whole store. pub(crate) fn read_path( - doc: &AutoCommit, + doc: &impl ReadDoc, path: &[String], ) -> Result<Option<JsonValue>, StoreError> { read_path_inner(doc, path, None) } pub(crate) fn read_path_with_conflicts( - doc: &AutoCommit, + doc: &impl ReadDoc, path: &[String], ) -> Result<Option<(JsonValue, Vec<ConflictReport>)>, StoreError> { let mut log = ConflictLog { @@ -383,7 +383,7 @@ pub(crate) fn read_path_with_conflicts( /// For streams: the value, plus whether the path or subtree involves any conflict. /// Reports are collected without values, so this costs little more than read_path. pub(crate) fn read_path_traced( - doc: &AutoCommit, + doc: &impl ReadDoc, path: &[String], ) -> Result<(Option<JsonValue>, bool), StoreError> { let mut log = ConflictLog::default(); @@ -401,7 +401,7 @@ const SHALLOW_INLINE: usize = 256; /// scalar; a map's has its keys' summaries too, under `entries`. Concurrent values /// resolved as a read resolves them; conflicts not reported. pub(crate) fn read_shallow( - doc: &AutoCommit, + doc: &impl ReadDoc, path: &[String], ) -> Result<Option<JsonValue>, StoreError> { let Some(resolved) = resolve_through(doc, path)? else { @@ -412,14 +412,14 @@ pub(crate) fn read_shallow( /// The resolved value at `path` (the empty path is the root map), through maps and /// lists (`step`). -fn resolve_through(doc: &AutoCommit, path: &[String]) -> Result<Option<Resolved>, StoreError> { +fn resolve_through(doc: &impl ReadDoc, path: &[String]) -> Result<Option<Resolved>, StoreError> { resolve_logged(doc, path, None) } /// `resolve_through`, noting the conflicts on the way in `log` (as ancestors, but the /// last segment's: the requested value's), its path left at `path`. fn resolve_logged( - doc: &AutoCommit, + doc: &impl ReadDoc, path: &[String], mut log: Option<&mut ConflictLog>, ) -> Result<Option<Resolved>, StoreError> { @@ -445,7 +445,11 @@ fn resolve_logged( /// A summary of `resolved` (`read_shallow`); with `entries`, a map's keys' too, or a /// list's items' (by index). -fn summary(doc: &AutoCommit, resolved: &Resolved, entries: bool) -> Result<JsonValue, StoreError> { +fn summary( + doc: &impl ReadDoc, + resolved: &Resolved, + entries: bool, +) -> Result<JsonValue, StoreError> { let mut out = serde_json::Map::new(); match resolved { Resolved::Maps(maps) => { @@ -508,7 +512,7 @@ fn summary(doc: &AutoCommit, resolved: &Resolved, entries: bool) -> Result<JsonV } /// Keys of the merged map at `path`; empty if it doesn't exist or isn't a map. -pub(crate) fn keys_at(doc: &AutoCommit, path: &[String]) -> Result<Vec<String>, StoreError> { +pub(crate) fn keys_at(doc: &impl ReadDoc, path: &[String]) -> Result<Vec<String>, StoreError> { Ok(match resolve_through(doc, path)? { Some(Resolved::Maps(maps)) => merged_keys(doc, &maps).into_iter().collect(), _ => Vec::new(), @@ -518,7 +522,7 @@ pub(crate) fn keys_at(doc: &AutoCommit, path: &[String]) -> Result<Vec<String>, /// Every concurrent value at `path`, newest first; empty if there is none. Where /// `read_path` picks one (or merges concurrent maps into one), this keeps them apart. pub(crate) fn read_candidates( - doc: &AutoCommit, + doc: &impl ReadDoc, path: &[String], ) -> Result<Vec<JsonValue>, StoreError> { let (leaf, parents) = path.split_last().ok_or(StoreError::NotFound)?; @@ -538,7 +542,7 @@ pub(crate) fn read_candidates( /// the segment is an index (`list_index`): an item there is replaced, one at the end /// appended; past the end, or anything but an index, names nothing (`NotFound`). pub(crate) fn write_path( - doc: &mut AutoCommit, + doc: &mut impl Transactable, path: &[String], value: &JsonValue, ) -> Result<(), StoreError> { @@ -575,7 +579,7 @@ enum Parent { /// The parent of a write at `parents` + one more segment: missing maps are created on /// the way; through a list only by an index of an item it has. A scalar or text on the /// way is a `Conflict`. -fn parent_for_write(doc: &mut AutoCommit, parents: &[String]) -> Result<Parent, StoreError> { +fn parent_for_write(doc: &mut impl Transactable, parents: &[String]) -> Result<Parent, StoreError> { let mut current = Parent::Maps(vec![automerge::ROOT]); for segment in parents { let next = match ¤t { @@ -607,7 +611,7 @@ fn parent_for_write(doc: &mut AutoCommit, parents: &[String]) -> Result<Parent, /// `PastTheEnd`; a parent that isn't a list `NotAList`; anything but an index there, or /// no parent, `NotFound`. pub(crate) fn insert_path( - doc: &mut AutoCommit, + doc: &mut impl Transactable, path: &[String], value: &JsonValue, ) -> Result<(), StoreError> { @@ -630,8 +634,8 @@ pub(crate) fn insert_path( /// 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, + src: &impl ReadDoc, + dst: &mut impl Transactable, path: &[String], ) -> Result<(), StoreError> { copy_path_to(src, path, dst, path) @@ -639,9 +643,9 @@ pub(crate) fn copy_path( /// Like [`copy_path`], but puts what `src` holds at `from` at `to` in `dst`. pub(crate) fn copy_path_to( - src: &AutoCommit, + src: &impl ReadDoc, from: &[String], - dst: &mut AutoCommit, + dst: &mut impl Transactable, to: &[String], ) -> Result<(), StoreError> { let (leaf, parents) = to.split_last().ok_or(StoreError::NotFound)?; @@ -675,7 +679,7 @@ pub(crate) fn copy_path_to( /// (`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, + doc: &mut impl Transactable, path: &[String], prune: bool, ) -> Result<(), StoreError> { @@ -739,8 +743,8 @@ pub(crate) fn delete_path( /// `skip` sees the path of every map entry and list element (list indices as decimal /// strings); returning true leaves that subtree out. pub(crate) fn materialize_into( - src: &AutoCommit, - dst: &mut AutoCommit, + src: &impl ReadDoc, + dst: &mut impl Transactable, skip: &dyn Fn(&[String]) -> bool, ) -> Result<(), StoreError> { let mut path = Vec::new(); @@ -755,9 +759,9 @@ pub(crate) fn materialize_into( } fn copy_maps( - src: &AutoCommit, + src: &impl ReadDoc, maps: &[ObjId], - dst: &mut AutoCommit, + dst: &mut impl Transactable, target: &ObjId, path: &mut Vec<String>, skip: &dyn Fn(&[String]) -> bool, @@ -776,9 +780,9 @@ fn copy_maps( } fn copy_resolved( - src: &AutoCommit, + src: &impl ReadDoc, resolved: &Resolved, - dst: &mut AutoCommit, + dst: &mut impl Transactable, parent: &ObjId, slot: Slot, path: &mut Vec<String>, @@ -806,10 +810,10 @@ fn copy_resolved( #[allow(clippy::too_many_arguments)] fn copy_object( - src: &AutoCommit, + src: &impl ReadDoc, obj_type: ObjType, id: &ObjId, - dst: &mut AutoCommit, + dst: &mut impl Transactable, parent: &ObjId, slot: Slot, path: &mut Vec<String>, @@ -863,7 +867,7 @@ enum Slot<'a> { } fn put_object( - doc: &mut AutoCommit, + doc: &mut impl Transactable, parent: &ObjId, slot: Slot, obj_type: ObjType, @@ -875,7 +879,7 @@ fn put_object( } fn put_scalar( - doc: &mut AutoCommit, + doc: &mut impl Transactable, parent: &ObjId, slot: Slot, value: impl Into<ScalarValue>, @@ -888,7 +892,7 @@ fn put_scalar( } fn write_value( - doc: &mut AutoCommit, + doc: &mut impl Transactable, parent: &ObjId, slot: Slot, value: &JsonValue, @@ -926,7 +930,7 @@ fn write_value( } fn json_to_automerge( - doc: &mut AutoCommit, + doc: &mut impl Transactable, parent: &ObjId, key: &str, value: &JsonValue, @@ -997,7 +1001,11 @@ fn op_id_string(id: &ObjId) -> String { } } -fn value_to_json(doc: &AutoCommit, value: &Value<'_>, id: &ObjId) -> Result<JsonValue, StoreError> { +fn value_to_json( + doc: &impl ReadDoc, + value: &Value<'_>, + id: &ObjId, +) -> Result<JsonValue, StoreError> { match value { Value::Scalar(scalar) => Ok(scalar_to_json(scalar.clone().into_owned())?), Value::Object(_) => object_to_json(doc, id, None), @@ -1007,7 +1015,7 @@ fn value_to_json(doc: &AutoCommit, value: &Value<'_>, id: &ObjId) -> Result<Json impl ConflictLog { fn record( &mut self, - doc: &AutoCommit, + doc: &impl ReadDoc, key: &str, all: &[(Value<'static>, ObjId)], merged: bool, diff --git a/src/remake/records.rs b/src/remake/records.rs index 40b563a..fecae4c 100644 --- a/src/remake/records.rs +++ b/src/remake/records.rs @@ -38,7 +38,7 @@ use crate::clock::UnixMillis; use crate::document::meta::{self, REMAKES, VOTES}; use crate::document::replica::Epoch; use crate::document::store::{self, StoreError}; -use crate::document::{ChangeHash, Document}; +use crate::document::{ChangeHash, Document, Transactable}; /// One day, for retention defaults. const DAY_MS: u64 = 24 * 60 * 60 * 1000; @@ -446,7 +446,7 @@ pub(crate) fn load_register(doc: &Document) -> Register { } fn write_part<T: Serialize>( - doc: &mut Document, + doc: &mut impl Transactable, id: &str, part: &str, value: &T, @@ -456,7 +456,7 @@ fn write_part<T: Serialize>( } pub(crate) fn write_proposal( - doc: &mut Document, + doc: &mut impl Transactable, id: &str, proposal: &Proposal, ) -> Result<(), StoreError> { @@ -464,7 +464,7 @@ pub(crate) fn write_proposal( } pub(crate) fn write_decision( - doc: &mut Document, + doc: &mut impl Transactable, id: &str, decision: &Decision, ) -> Result<(), StoreError> { @@ -472,7 +472,7 @@ pub(crate) fn write_decision( } pub(crate) fn write_abort_request( - doc: &mut Document, + doc: &mut impl Transactable, id: &str, request: &AbortRequest, ) -> Result<(), StoreError> { @@ -480,7 +480,11 @@ pub(crate) fn write_abort_request( } /// Writes `voter`'s entry in the votes register (replacing its earlier one). -pub(crate) fn write_vote(doc: &mut Document, voter: &str, vote: &Vote) -> Result<(), StoreError> { +pub(crate) fn write_vote( + doc: &mut impl Transactable, + voter: &str, + vote: &Vote, +) -> Result<(), StoreError> { let json = serde_json::to_value(vote).map_err(anyhow::Error::from)?; store::write_path(doc, ®ister_path(voter), &json) } diff --git a/src/swarm/members.rs b/src/swarm/members.rs index 8d7f909..9885750 100644 --- a/src/swarm/members.rs +++ b/src/swarm/members.rs @@ -21,9 +21,9 @@ use std::time::Duration; use serde_json::{Value as JsonValue, json}; use tracing::{debug, info, warn}; -use crate::document::Document; use crate::document::meta; use crate::document::store::{self, StoreError}; +use crate::document::{Document, Transactable}; use crate::state::AppState; pub(crate) fn members_dir() -> Vec<String> { @@ -74,7 +74,11 @@ pub(crate) fn tier(doc: &Document, pk: &str) -> Tier { } } -pub(crate) fn set_tier(doc: &mut Document, pk: &str, tier: Tier) -> Result<(), StoreError> { +pub(crate) fn set_tier( + doc: &mut impl Transactable, + pk: &str, + tier: Tier, +) -> Result<(), StoreError> { store::write_path(doc, &tier_path(pk), &json!(tier.as_str())) } @@ -100,7 +104,7 @@ pub(crate) fn aliases(doc: &Document) -> HashMap<String, String> { .collect() } -fn set_alias(doc: &mut Document, pk: &str, alias: Option<&str>) -> Result<(), StoreError> { +fn set_alias(doc: &mut impl Transactable, pk: &str, alias: Option<&str>) -> Result<(), StoreError> { match alias { Some(alias) => store::write_path(doc, &alias_path(pk), &json!(alias)), None => match store::delete_path(doc, &alias_path(pk), false) { @@ -314,7 +318,7 @@ pub(crate) async fn ensure_registered(state: &AppState) { } /// Removes `pk`'s entry (an operator forgot it). Absent is fine. -pub(crate) fn unregister(doc: &mut Document, pk: &str) -> Result<(), StoreError> { +pub(crate) fn unregister(doc: &mut impl Transactable, pk: &str) -> Result<(), StoreError> { match store::delete_path(doc, &member_path(pk), false) { Err(StoreError::NotFound) => Ok(()), result => result, |
