1use crate::log::change::Change;
10use crate::log::command::{Command, PropTag, PropWrite};
11use crate::log::id::{ElementId, WriteOrder};
12use crate::log::state::{DocState, Liveness};
13
14#[derive(Clone, PartialEq, Eq, Debug, thiserror::Error)]
18pub enum FoldError {
19 #[error("command {index} names element {id}, which has not been created")]
24 UnknownElement { id: ElementId, index: usize },
25}
26
27pub fn apply(state: &mut DocState, change: &Change) -> Result<(), FoldError> {
30 for (index, command) in change.commands().iter().enumerate() {
31 apply_command(state, command, change.order_of(index), index)?;
32 }
33 Ok(())
34}
35
36pub fn fold<'a>(
38 state: &mut DocState,
39 changes: impl IntoIterator<Item = &'a Change>,
40) -> Result<(), FoldError> {
41 for change in changes {
42 apply(state, change)?;
43 }
44 Ok(())
45}
46
47fn apply_command(
48 state: &mut DocState,
49 command: &Command,
50 order: WriteOrder,
51 index: usize,
52) -> Result<(), FoldError> {
53 let missing = |id| FoldError::UnknownElement { id, index };
54 match command {
55 Command::Create {
56 id,
57 kind,
58 parent,
59 init,
60 } => {
61 state.ensure(*id, *kind, order);
62 for write in init.iter() {
63 if write.tag() != PropTag::Parent {
67 state.write(*id, write.tag(), Some(write.clone()), order);
68 }
69 }
70 state.write(
71 *id,
72 PropTag::Parent,
73 Some(PropWrite::Parent(*parent)),
74 order,
75 );
76 state.set_liveness(*id, Liveness::Live, order);
77 }
78 Command::SetProp { id, change } => {
79 state
80 .write(*id, change.tag(), change.new_value(), order)
81 .ok_or_else(|| missing(*id))?;
82 }
83 Command::Delete { id } => {
84 state
85 .set_liveness(*id, Liveness::Deleted, order)
86 .ok_or_else(|| missing(*id))?;
87 }
88 Command::Restore { id } => {
89 state
90 .set_liveness(*id, Liveness::Live, order)
91 .ok_or_else(|| missing(*id))?;
92 }
93 }
94 Ok(())
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use crate::log::change::SemanticLabel;
101 use crate::log::command::{ElementKind, PropChange, PropSet};
102 use crate::log::encode;
103 use crate::log::id::{ActorId, Lamport, Stamp};
104
105 fn actor(byte: u8) -> ActorId {
106 ActorId::from_uuid(uuid::Uuid::from_bytes([byte; 16]))
107 }
108
109 fn element(byte: u8) -> ElementId {
110 ElementId::from_uuid(uuid::Uuid::from_bytes([byte; 16]))
111 }
112
113 fn change(lamport: u64, actor_byte: u8, commands: Vec<Command>) -> Change {
114 Change::new(
115 Stamp {
116 lamport: Lamport::new(lamport),
117 actor: actor(actor_byte),
118 },
119 [],
120 SemanticLabel::new("test"),
121 commands,
122 )
123 }
124
125 fn create(id: ElementId) -> Command {
126 Command::Create {
127 id,
128 kind: ElementKind::Block,
129 parent: ElementId::DOCUMENT,
130 init: PropSet::new(),
131 }
132 }
133
134 fn set_accent(id: ElementId, from: Option<u8>, to: u8) -> Command {
135 Command::SetProp {
136 id,
137 change: PropChange::Accent {
138 old: from,
139 new: Some(to),
140 },
141 }
142 }
143
144 fn folded(changes: &[Change]) -> DocState {
145 let mut state = DocState::new();
146 fold(&mut state, changes).expect("the fixture is in causal order");
147 state
148 }
149
150 #[test]
151 fn a_create_seeds_the_parent_register_so_a_move_is_an_ordinary_write() {
152 let id = element(1);
153 let state = folded(&[change(1, 1, vec![create(id)])]);
154 assert_eq!(
155 state.get(id, PropTag::Parent),
156 Some(&PropWrite::Parent(ElementId::DOCUMENT))
157 );
158 }
159
160 #[test]
163 fn the_create_field_beats_a_parent_in_the_init_set() {
164 let id = element(1);
165 let state = folded(&[change(
166 1,
167 1,
168 vec![Command::Create {
169 id,
170 kind: ElementKind::Block,
171 parent: ElementId::DOCUMENT,
172 init: PropSet::new().with(PropWrite::Parent(element(9))),
173 }],
174 )]);
175 assert_eq!(
176 state.get(id, PropTag::Parent),
177 Some(&PropWrite::Parent(ElementId::DOCUMENT))
178 );
179 }
180
181 #[test]
184 fn concurrent_writes_converge_whichever_order_they_arrive() {
185 let id = element(1);
186 let seed = change(1, 0, vec![create(id)]);
187 let alice = change(5, 1, vec![set_accent(id, None, 1)]);
188 let bob = change(5, 2, vec![set_accent(id, None, 2)]);
189
190 let one = folded(&[seed.clone(), alice.clone(), bob.clone()]);
191 let other = folded(&[seed, bob, alice]);
192
193 assert_eq!(one, other);
194 assert_eq!(
195 one.get(id, PropTag::Accent),
196 Some(&PropWrite::Accent(2)),
197 "equal lamports break toward the higher actor id"
198 );
199 }
200
201 #[test]
202 fn delivering_the_same_change_twice_changes_nothing() {
203 let id = element(1);
204 let seed = change(1, 1, vec![create(id)]);
205 let edit = change(2, 1, vec![set_accent(id, None, 4)]);
206
207 let once = folded(&[seed.clone(), edit.clone()]);
208 let twice = folded(&[seed.clone(), edit.clone(), edit, seed]);
209 assert_eq!(once, twice);
210 }
211
212 #[test]
215 fn a_batch_writing_one_register_twice_ends_on_its_last_write() {
216 let id = element(1);
217 let state = folded(&[change(
218 1,
219 1,
220 vec![
221 create(id),
222 set_accent(id, None, 1),
223 set_accent(id, Some(1), 2),
224 ],
225 )]);
226 assert_eq!(state.get(id, PropTag::Accent), Some(&PropWrite::Accent(2)));
227 }
228
229 #[test]
230 fn an_edit_of_an_uncreated_element_is_refused_rather_than_dropped() {
231 let mut state = DocState::new();
232 let id = element(3);
233 let result = apply(&mut state, &change(1, 1, vec![set_accent(id, None, 1)]));
234 assert_eq!(result, Err(FoldError::UnknownElement { id, index: 0 }));
235 }
236
237 #[test]
238 fn deleting_hides_an_element_but_keeps_it_restorable() {
239 let id = element(1);
240 let mut state = folded(&[change(1, 1, vec![create(id), Command::Delete { id }])]);
241
242 assert!(state.live(id).is_none());
243 assert!(state.contains(id), "the tombstone keeps the identity");
244
245 apply(&mut state, &change(2, 1, vec![Command::Restore { id }]))
246 .expect("restoring a tombstone");
247 assert!(state.live(id).is_some());
248 }
249
250 #[test]
252 fn an_edit_concurrent_with_a_delete_converges() {
253 let id = element(1);
254 let seed = change(1, 0, vec![create(id)]);
255 let edit = change(4, 1, vec![set_accent(id, None, 5)]);
256 let delete = change(6, 2, vec![Command::Delete { id }]);
257
258 let one = folded(&[seed.clone(), edit.clone(), delete.clone()]);
259 let other = folded(&[seed, delete, edit]);
260
261 assert_eq!(one, other);
262 assert!(
263 one.live(id).is_none(),
264 "the later-ordered delete decides liveness"
265 );
266 assert_eq!(
267 one.element(id).expect("tombstone").get(PropTag::Accent),
268 Some(&PropWrite::Accent(5)),
269 "the losing edit is kept, so a restore brings it back with the edit"
270 );
271 }
272
273 #[test]
277 fn replicas_agree_byte_for_byte_not_just_structurally() {
278 let (a, b) = (element(1), element(2));
279 let seed = change(1, 0, vec![create(a), create(b)]);
280 let alice = change(3, 1, vec![set_accent(a, None, 1)]);
281 let bob = change(3, 2, vec![set_accent(b, None, 2)]);
282
283 let one = encode::state_to_bytes(&folded(&[seed.clone(), alice.clone(), bob.clone()]));
284 let other = encode::state_to_bytes(&folded(&[seed, bob, alice]));
285 assert_eq!(one, other);
286 }
287
288 #[test]
289 fn replay_is_a_function_of_the_log_alone() {
290 let id = element(1);
291 let log = [
292 change(1, 1, vec![create(id)]),
293 change(2, 1, vec![set_accent(id, None, 1)]),
294 ];
295 assert_eq!(
296 encode::state_to_bytes(&folded(&log)),
297 encode::state_to_bytes(&folded(&log)),
298 "two replays of one log must agree"
299 );
300 }
301}