1use crate::id::NodeId;
6use alloc::string::String;
7use alloc::vec::Vec;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
15pub struct NodeKindTag(pub u16);
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
22pub struct FieldTag(pub u16);
23
24#[derive(Debug, Clone, PartialEq, Eq)]
27pub enum RefState {
28 Resolved(NodeId),
30 Pending,
32 Broken,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
38#[non_exhaustive]
39pub enum FieldValue {
40 Absent,
42 Bool(bool),
44 U64(u64),
46 I64(i64),
48 Text(String),
50 Node(NodeId),
52 Reference(RefState),
54 Bytes(Vec<u8>),
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
60pub enum Change {
61 Created {
63 node: NodeId,
65 kind: NodeKindTag,
67 },
68 Updated {
70 node: NodeId,
72 field: FieldTag,
74 before: FieldValue,
76 after: FieldValue,
78 },
79 Deleted {
81 node: NodeId,
83 kind: NodeKindTag,
85 },
86}
87
88impl Change {
89 #[must_use]
91 pub const fn node(&self) -> NodeId {
92 match self {
93 Self::Created { node, .. }
94 | Self::Updated { node, .. }
95 | Self::Deleted { node, .. } => *node,
96 }
97 }
98
99 #[must_use]
101 pub const fn is_created(&self) -> bool {
102 matches!(self, Self::Created { .. })
103 }
104
105 #[must_use]
107 pub const fn is_deleted(&self) -> bool {
108 matches!(self, Self::Deleted { .. })
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq)]
119pub struct Changeset {
120 changes: Vec<Change>,
121 sequence: u64,
122}
123
124impl Changeset {
125 #[must_use]
127 pub const fn new(sequence: u64) -> Self {
128 Self {
129 changes: Vec::new(),
130 sequence,
131 }
132 }
133
134 #[must_use]
136 pub const fn sequence(&self) -> u64 {
137 self.sequence
138 }
139
140 pub fn push(&mut self, change: Change) {
142 self.changes.push(change);
143 }
144
145 #[must_use]
147 pub fn is_empty(&self) -> bool {
148 self.changes.is_empty()
149 }
150
151 #[must_use]
153 pub fn len(&self) -> usize {
154 self.changes.len()
155 }
156
157 pub fn iter(&self) -> impl Iterator<Item = &Change> {
159 self.changes.iter()
160 }
161
162 pub fn nodes_touched(&self) -> impl Iterator<Item = NodeId> + '_ {
164 self.changes.iter().map(Change::node)
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 #![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
172 use super::*;
173 use crate::id::IdSpace;
174 use alloc::string::ToString;
175 use alloc::vec;
176 use alloc::vec::Vec;
177
178 fn node(counter: u64) -> NodeId {
179 NodeId::from_parts(IdSpace::MODEL, counter)
180 }
181
182 #[test]
183 fn changeset_preserves_insertion_order() {
184 let mut cs = Changeset::new(1);
185 cs.push(Change::Created {
186 node: node(1),
187 kind: NodeKindTag(7),
188 });
189 cs.push(Change::Updated {
190 node: node(1),
191 field: FieldTag(2),
192 before: FieldValue::Absent,
193 after: FieldValue::Text("Pose".to_string()),
194 });
195 cs.push(Change::Deleted {
196 node: node(2),
197 kind: NodeKindTag(7),
198 });
199
200 let kinds: Vec<&'static str> = cs
201 .iter()
202 .map(|c| match c {
203 Change::Created { .. } => "created",
204 Change::Updated { .. } => "updated",
205 Change::Deleted { .. } => "deleted",
206 })
207 .collect();
208 assert_eq!(kinds, ["created", "updated", "deleted"]);
209 assert_eq!(cs.len(), 3);
210 assert_eq!(cs.sequence(), 1);
211 }
212
213 #[test]
214 fn nodes_touched_reports_every_affected_node_once_per_change() {
215 let mut cs = Changeset::new(4);
216 cs.push(Change::Created {
217 node: node(1),
218 kind: NodeKindTag(1),
219 });
220 cs.push(Change::Updated {
221 node: node(1),
222 field: FieldTag(0),
223 before: FieldValue::Bool(false),
224 after: FieldValue::Bool(true),
225 });
226 cs.push(Change::Deleted {
227 node: node(9),
228 kind: NodeKindTag(1),
229 });
230
231 let touched: Vec<NodeId> = cs.nodes_touched().collect();
232 assert_eq!(touched, vec![node(1), node(1), node(9)]);
233 }
234
235 #[test]
236 fn empty_changeset_is_reported_as_empty() {
237 let cs = Changeset::new(0);
238 assert!(cs.is_empty());
239 assert_eq!(cs.len(), 0);
240 }
241
242 #[test]
243 fn reference_transitions_are_representable_as_field_values() {
244 let before = FieldValue::Reference(RefState::Pending);
245 let after = FieldValue::Reference(RefState::Resolved(node(3)));
246 assert_ne!(before, after);
247 }
248}