1use serde::{Deserialize, Serialize};
7
8use crate::opcode::OpCodes;
9
10#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
11pub struct Commit {
12 label: String,
14 ops: Vec<OpCodes>,
18}
19
20impl Commit {
21 pub fn new(label: String, ops: Vec<OpCodes>) -> Self {
22 Self { label, ops }
23 }
24
25 pub fn label(&self) -> &str {
26 &self.label
27 }
28
29 pub fn ops(&self) -> &[OpCodes] {
30 &self.ops
31 }
32
33 #[must_use]
36 pub fn relabelled(self, label: String) -> Self {
37 Self { label, ..self }
38 }
39}
40
41#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
42pub enum CommitEnvelope {
43 CommitV1(Commit),
44}
45
46#[derive(Debug)]
57pub struct CommitBuilder {
58 label: String,
59 ops: Vec<OpCodes>,
60}
61
62impl CommitBuilder {
63 pub fn new(label: impl Into<String>) -> Self {
66 Self {
67 label: label.into(),
68 ops: Vec::new(),
69 }
70 }
71
72 pub fn push(&mut self, op: OpCodes) {
74 self.ops.push(op);
75 }
76
77 pub fn extend(&mut self, ops: impl IntoIterator<Item = OpCodes>) {
79 self.ops.extend(ops);
80 }
81
82 pub fn ops(&self) -> &[OpCodes] {
87 &self.ops
88 }
89
90 pub fn seal(self) -> Option<Commit> {
93 (!self.ops.is_empty()).then(|| Commit::new(self.label, self.ops))
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use crate::fixtures::block_id;
101 use crate::opcode::Crud;
102
103 fn delete_block(n: u32) -> OpCodes {
104 OpCodes::Block(block_id(n), Crud::Delete)
105 }
106
107 #[test]
108 fn an_empty_builder_seals_to_no_commit() {
109 assert_eq!(CommitBuilder::new("Idle gesture").seal(), None);
110 }
111
112 #[test]
115 fn ops_reads_the_gesture_so_far_without_closing_it() {
116 let mut builder = CommitBuilder::new("Solved on the way");
117 assert!(
118 builder.ops().is_empty(),
119 "a fresh gesture has pushed nothing"
120 );
121 builder.push(delete_block(1));
122 assert_eq!(builder.ops(), [delete_block(1)]);
123 builder.push(delete_block(2));
124 assert_eq!(builder.ops(), [delete_block(1), delete_block(2)]);
125 let commit = builder.seal().expect("the gesture still seals");
126 assert_eq!(commit.ops(), [delete_block(1), delete_block(2)]);
127 }
128
129 #[test]
130 fn seal_preserves_push_order_as_seq_order() {
131 let pushed = [delete_block(1), delete_block(2), delete_block(3)];
132 let mut builder = CommitBuilder::new("Deleted 3 blocks");
133 builder.push(pushed[0].clone());
134 builder.extend(pushed[1..].iter().cloned());
135 let commit = builder.seal().expect("pushed ops must seal to a commit");
136 assert_eq!(commit.label(), "Deleted 3 blocks");
137 assert_eq!(commit.ops(), pushed);
138 }
139}