aboutsummaryrefslogtreecommitdiffstats
path: root/src/document/inspect.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-07 22:35:21 +0100
committertv <tv@krebsco.de>2026-11-07 22:35:21 +0100
commit70ff0ec9e50bf506cc6d80b929a81e350289f6cc (patch)
tree14c66ce846229889abd98acf98ceb645754b7e91 /src/document/inspect.rs
parent645bae8fb8065a353237d24aff274009f37d694e (diff)
access: signed values judged on receipt
Inspection gives each operation's kind; under strict every node refuses, at a signed place, anything but an envelope that checks and replaces the one there (deleting first as replacing does), anything within one, and above one anything replacing what holds one. Auth plan M7.
Diffstat (limited to 'src/document/inspect.rs')
-rw-r--r--src/document/inspect.rs97
1 files changed, 89 insertions, 8 deletions
diff --git a/src/document/inspect.rs b/src/document/inspect.rs
index b39386e..d395008 100644
--- a/src/document/inspect.rs
+++ b/src/document/inspect.rs
@@ -11,9 +11,6 @@
//! change lacks a dependency, and can't be applied anyway) is placed at the root, which
//! is within every path's reach.
-// Judging changes on receipt is what uses it; until then, the benchmark and tests.
-#![cfg_attr(not(test), allow(dead_code))]
-
use std::collections::{BTreeSet, HashMap};
use automerge::legacy::{Key, ObjectId, OpId, OpType};
@@ -24,6 +21,31 @@ use super::{Document, ReadDoc};
/// A store path, by segments.
pub(crate) type Path = Vec<String>;
+/// An operation, as rules see it (`Inspector::ops`): where, and what it does there.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(crate) struct Op {
+ /// What it touches.
+ pub(crate) path: Path,
+ pub(crate) action: Action,
+}
+
+/// What an operation does at its path.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(crate) enum Action {
+ /// Makes a map there.
+ MakeMap,
+ /// Puts a string there.
+ PutString(String),
+ /// Deletes what is there.
+ Delete,
+ /// Changes what is there, a list or text it is in (`Place::in_list`).
+ Within,
+ /// In an object nowhere to be found (`Inspector::known`).
+ Nowhere,
+ /// Anything else there: makes another object, puts another scalar, increments.
+ Other,
+}
+
/// Where an object is: its path, cut at the first list or text on the way
/// (`in_list`: it is inside one, or is one).
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -79,10 +101,20 @@ impl<'a> Inspector<'a> {
}
}
- /// The paths each of `changes` touches, in their order. The objects they make are
- /// placed first, whatever their order, so a change is judged the same however the
- /// changes came.
+ /// The paths each of `changes` touches, in their order (`ops`): for the benchmark
+ /// and tests.
+ #[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn paths(&mut self, changes: &[ExpandedChange]) -> Vec<BTreeSet<Path>> {
+ self.ops(changes)
+ .into_iter()
+ .map(|ops| ops.into_iter().map(|op| op.path).collect())
+ .collect()
+ }
+
+ /// The operations of each of `changes`, in their order, as rules see them. The
+ /// objects they make are placed first, whatever their order, so a change is judged
+ /// the same however the changes came.
+ pub(crate) fn ops(&mut self, changes: &[ExpandedChange]) -> Vec<Vec<Op>> {
// The objects made, by the operation that made them: placed as their parents
// are, until no more can be (a parent made later in the list, placed in a later
// round).
@@ -119,9 +151,23 @@ impl<'a> Inspector<'a> {
.operations
.iter()
.map(|op| match self.known(&op.obj) {
- Some(place) => place.touched(&op.key),
+ Some(place) => Op {
+ path: place.touched(&op.key),
+ action: match &op.action {
+ _ if place.in_list || op.insert => Action::Within,
+ OpType::Make(automerge::ObjType::Map) => Action::MakeMap,
+ OpType::Put(automerge::ScalarValue::Str(text)) => {
+ Action::PutString(text.to_string())
+ }
+ OpType::Delete => Action::Delete,
+ _ => Action::Other,
+ },
+ },
// Nowhere to be found: within every path's reach.
- None => Vec::new(),
+ None => Op {
+ path: Vec::new(),
+ action: Action::Nowhere,
+ },
})
.collect()
})
@@ -214,6 +260,41 @@ mod tests {
assert_eq!(backwards, all);
}
+ /// What each operation does where: a map made, a string put, a deletion, what is
+ /// inside a list, anything else.
+ #[test]
+ fn what_operations_do() {
+ let mut doc = Document::new();
+ change(&mut doc, |doc| {
+ let config = doc.put_object(ROOT, "config", ObjType::Map).unwrap();
+ doc.put(&config, "mode", "fast").unwrap();
+ doc.put(&config, "level", 3).unwrap();
+ doc.delete(&config, "mode").unwrap();
+ let queue = doc.put_object(ROOT, "queue", ObjType::List).unwrap();
+ doc.insert(&queue, 0, "job").unwrap();
+ });
+ let empty = Document::new();
+ let ops = Inspector::new(&empty).ops(&decoded(&doc, &[]));
+ let actions: Vec<(Path, Action)> = ops[0]
+ .iter()
+ .map(|op| (op.path.clone(), op.action.clone()))
+ .collect();
+ assert_eq!(
+ actions,
+ vec![
+ (path(&["config"]), Action::MakeMap),
+ (
+ path(&["config", "mode"]),
+ Action::PutString("fast".to_owned())
+ ),
+ (path(&["config", "level"]), Action::Other),
+ (path(&["config", "mode"]), Action::Delete),
+ (path(&["queue"]), Action::Other),
+ (path(&["queue"]), Action::Within),
+ ]
+ );
+ }
+
/// Inside a list or text, a path stops at it: items move.
#[test]
fn lists_and_text_stop_paths() {