1#[derive(Debug, thiserror::Error)]
5#[non_exhaustive]
6pub enum DraftGroupExecutionError<E> {
7 #[error("execution graph has no target or draft group {0:?}")]
9 UnknownGroup(String),
10 #[error("target or draft group execution failed")]
12 Execution(#[source] E),
13}
14
15pub fn execute_draft_group<S, I, O, E>(
18 graph: &crate::ExecutionGraph,
19 group: &str,
20 input: I,
21 state: &mut S,
22 execute: impl FnOnce(usize, &str, I, &mut S) -> Result<O, E>,
23) -> Result<O, DraftGroupExecutionError<E>> {
24 let index = graph
25 .group_index(group)
26 .ok_or_else(|| DraftGroupExecutionError::UnknownGroup(group.to_owned()))?;
27 execute(index, graph.groups()[index].id(), input, state)
28 .map_err(DraftGroupExecutionError::Execution)
29}
30
31#[derive(Debug, Clone)]
38pub struct DraftStateTransaction<S: Clone> {
39 checkpoint: S,
40 draft: S,
41}
42
43impl<S: Clone> DraftStateTransaction<S> {
44 pub fn fork(state: &S) -> Self {
46 Self {
47 checkpoint: state.clone(),
48 draft: state.clone(),
49 }
50 }
51
52 pub const fn draft(&self) -> &S {
54 &self.draft
55 }
56
57 pub fn draft_mut(&mut self) -> &mut S {
59 &mut self.draft
60 }
61
62 pub const fn checkpoint(&self) -> &S {
64 &self.checkpoint
65 }
66
67 pub fn commit_draft(self, canonical: &mut S) {
69 canonical.clone_from(&self.draft);
70 }
71
72 pub fn rollback(self, canonical: &mut S) {
75 canonical.clone_from(&self.checkpoint);
76 }
77
78 pub fn commit_verified(self) {}
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn one_transaction_owns_fork_commit_and_rollback() {
89 let mut canonical = vec![1, 2];
90 let mut commit = DraftStateTransaction::fork(&canonical);
91 commit.draft_mut().push(3);
92 commit.commit_draft(&mut canonical);
93 assert_eq!(canonical, [1, 2, 3]);
94
95 let mut rollback = DraftStateTransaction::fork(&canonical);
96 rollback.draft_mut().push(4);
97 canonical.push(9); rollback.rollback(&mut canonical);
99 assert_eq!(canonical, [1, 2, 3]);
100
101 let verified = DraftStateTransaction::fork(&canonical);
102 canonical.push(5);
103 verified.commit_verified();
104 assert_eq!(canonical, [1, 2, 3, 5]);
105 }
106
107 #[test]
108 fn prediction_and_external_draft_groups_drive_transactional_acceptance() {
109 let graph = crate::ExecutionGraph::new(
110 vec![
111 crate::ExecutionGroupSpec::root("target"),
112 crate::ExecutionGroupSpec::root("external-drafter"),
113 crate::ExecutionGroupSpec::with_dependencies(
114 "prediction.0",
115 ["target", "external-drafter"],
116 ),
117 ],
118 "prediction.0",
119 )
120 .unwrap();
121 let mut trace = Vec::new();
122 let execute = |group: &str, input: u32, state: &mut Vec<u32>, trace: &mut Vec<String>| {
123 execute_draft_group(
124 &graph,
125 group,
126 input,
127 state,
128 |group_index, id, input, state| {
129 let output = match group_index {
130 0 => input + 1,
131 1 => input + 3,
132 2 => input + 2,
133 _ => unreachable!("fixture graph has exactly three groups"),
134 };
135 state.push(output);
136 trace.push(id.to_owned());
137 Ok::<_, std::convert::Infallible>(output)
138 },
139 )
140 .unwrap()
141 };
142
143 let mut canonical = vec![10];
144 let target_output = execute("target", 0, &mut canonical, &mut trace);
145 assert_eq!(target_output, 1);
146
147 let mut embedded = DraftStateTransaction::fork(&canonical);
148 let embedded_output = execute("prediction.0", 9, embedded.draft_mut(), &mut trace);
149 assert_eq!(embedded_output, 11);
150 embedded.commit_draft(&mut canonical);
151
152 let mut accepted_external = DraftStateTransaction::fork(&canonical);
153 let accepted = execute(
154 "external-drafter",
155 9,
156 accepted_external.draft_mut(),
157 &mut trace,
158 );
159 assert_eq!(accepted, 12);
160 accepted_external.commit_draft(&mut canonical);
161
162 let mut rejected_external = DraftStateTransaction::fork(&canonical);
163 let rejected = execute(
164 "external-drafter",
165 10,
166 rejected_external.draft_mut(),
167 &mut trace,
168 );
169 assert_eq!(rejected, 13);
170 rejected_external.rollback(&mut canonical);
171
172 assert_eq!(canonical, [10, 1, 11, 12]);
173 assert!(!canonical.contains(&rejected));
174 assert_eq!(
175 trace,
176 [
177 "target",
178 "prediction.0",
179 "external-drafter",
180 "external-drafter"
181 ]
182 );
183 }
184}