use crate::id::NodeId;
use alloc::string::String;
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct NodeKindTag(pub u16);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct FieldTag(pub u16);
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RefState {
Resolved(NodeId),
Pending,
Broken,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum FieldValue {
Absent,
Bool(bool),
U64(u64),
I64(i64),
Text(String),
Node(NodeId),
Reference(RefState),
Bytes(Vec<u8>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Change {
Created {
node: NodeId,
kind: NodeKindTag,
},
Updated {
node: NodeId,
field: FieldTag,
before: FieldValue,
after: FieldValue,
},
Deleted {
node: NodeId,
kind: NodeKindTag,
},
}
impl Change {
#[must_use]
pub const fn node(&self) -> NodeId {
match self {
Self::Created { node, .. }
| Self::Updated { node, .. }
| Self::Deleted { node, .. } => *node,
}
}
#[must_use]
pub const fn is_created(&self) -> bool {
matches!(self, Self::Created { .. })
}
#[must_use]
pub const fn is_deleted(&self) -> bool {
matches!(self, Self::Deleted { .. })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Changeset {
changes: Vec<Change>,
sequence: u64,
}
impl Changeset {
#[must_use]
pub const fn new(sequence: u64) -> Self {
Self {
changes: Vec::new(),
sequence,
}
}
#[must_use]
pub const fn sequence(&self) -> u64 {
self.sequence
}
pub fn push(&mut self, change: Change) {
self.changes.push(change);
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.changes.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.changes.len()
}
pub fn iter(&self) -> impl Iterator<Item = &Change> {
self.changes.iter()
}
pub fn nodes_touched(&self) -> impl Iterator<Item = NodeId> + '_ {
self.changes.iter().map(Change::node)
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use super::*;
use crate::id::IdSpace;
use alloc::string::ToString;
use alloc::vec;
use alloc::vec::Vec;
fn node(counter: u64) -> NodeId {
NodeId::from_parts(IdSpace::MODEL, counter)
}
#[test]
fn changeset_preserves_insertion_order() {
let mut cs = Changeset::new(1);
cs.push(Change::Created {
node: node(1),
kind: NodeKindTag(7),
});
cs.push(Change::Updated {
node: node(1),
field: FieldTag(2),
before: FieldValue::Absent,
after: FieldValue::Text("Pose".to_string()),
});
cs.push(Change::Deleted {
node: node(2),
kind: NodeKindTag(7),
});
let kinds: Vec<&'static str> = cs
.iter()
.map(|c| match c {
Change::Created { .. } => "created",
Change::Updated { .. } => "updated",
Change::Deleted { .. } => "deleted",
})
.collect();
assert_eq!(kinds, ["created", "updated", "deleted"]);
assert_eq!(cs.len(), 3);
assert_eq!(cs.sequence(), 1);
}
#[test]
fn nodes_touched_reports_every_affected_node_once_per_change() {
let mut cs = Changeset::new(4);
cs.push(Change::Created {
node: node(1),
kind: NodeKindTag(1),
});
cs.push(Change::Updated {
node: node(1),
field: FieldTag(0),
before: FieldValue::Bool(false),
after: FieldValue::Bool(true),
});
cs.push(Change::Deleted {
node: node(9),
kind: NodeKindTag(1),
});
let touched: Vec<NodeId> = cs.nodes_touched().collect();
assert_eq!(touched, vec![node(1), node(1), node(9)]);
}
#[test]
fn empty_changeset_is_reported_as_empty() {
let cs = Changeset::new(0);
assert!(cs.is_empty());
assert_eq!(cs.len(), 0);
}
#[test]
fn reference_transitions_are_representable_as_field_values() {
let before = FieldValue::Reference(RefState::Pending);
let after = FieldValue::Reference(RefState::Resolved(node(3)));
assert_ne!(before, after);
}
}