#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum DraftGroupExecutionError<E> {
#[error("execution graph has no target or draft group {0:?}")]
UnknownGroup(String),
#[error("target or draft group execution failed")]
Execution(#[source] E),
}
pub fn execute_draft_group<S, I, O, E>(
graph: &crate::ExecutionGraph,
group: &str,
input: I,
state: &mut S,
execute: impl FnOnce(usize, &str, I, &mut S) -> Result<O, E>,
) -> Result<O, DraftGroupExecutionError<E>> {
let index = graph
.group_index(group)
.ok_or_else(|| DraftGroupExecutionError::UnknownGroup(group.to_owned()))?;
execute(index, graph.groups()[index].id(), input, state)
.map_err(DraftGroupExecutionError::Execution)
}
#[derive(Debug, Clone)]
pub struct DraftStateTransaction<S: Clone> {
checkpoint: S,
draft: S,
}
impl<S: Clone> DraftStateTransaction<S> {
pub fn fork(state: &S) -> Self {
Self {
checkpoint: state.clone(),
draft: state.clone(),
}
}
pub const fn draft(&self) -> &S {
&self.draft
}
pub fn draft_mut(&mut self) -> &mut S {
&mut self.draft
}
pub const fn checkpoint(&self) -> &S {
&self.checkpoint
}
pub fn commit_draft(self, canonical: &mut S) {
canonical.clone_from(&self.draft);
}
pub fn rollback(self, canonical: &mut S) {
canonical.clone_from(&self.checkpoint);
}
pub fn commit_verified(self) {}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn one_transaction_owns_fork_commit_and_rollback() {
let mut canonical = vec![1, 2];
let mut commit = DraftStateTransaction::fork(&canonical);
commit.draft_mut().push(3);
commit.commit_draft(&mut canonical);
assert_eq!(canonical, [1, 2, 3]);
let mut rollback = DraftStateTransaction::fork(&canonical);
rollback.draft_mut().push(4);
canonical.push(9); rollback.rollback(&mut canonical);
assert_eq!(canonical, [1, 2, 3]);
let verified = DraftStateTransaction::fork(&canonical);
canonical.push(5);
verified.commit_verified();
assert_eq!(canonical, [1, 2, 3, 5]);
}
#[test]
fn prediction_and_external_draft_groups_drive_transactional_acceptance() {
let graph = crate::ExecutionGraph::new(
vec![
crate::ExecutionGroupSpec::root("target"),
crate::ExecutionGroupSpec::root("external-drafter"),
crate::ExecutionGroupSpec::with_dependencies(
"prediction.0",
["target", "external-drafter"],
),
],
"prediction.0",
)
.unwrap();
let mut trace = Vec::new();
let execute = |group: &str, input: u32, state: &mut Vec<u32>, trace: &mut Vec<String>| {
execute_draft_group(
&graph,
group,
input,
state,
|group_index, id, input, state| {
let output = match group_index {
0 => input + 1,
1 => input + 3,
2 => input + 2,
_ => unreachable!("fixture graph has exactly three groups"),
};
state.push(output);
trace.push(id.to_owned());
Ok::<_, std::convert::Infallible>(output)
},
)
.unwrap()
};
let mut canonical = vec![10];
let target_output = execute("target", 0, &mut canonical, &mut trace);
assert_eq!(target_output, 1);
let mut embedded = DraftStateTransaction::fork(&canonical);
let embedded_output = execute("prediction.0", 9, embedded.draft_mut(), &mut trace);
assert_eq!(embedded_output, 11);
embedded.commit_draft(&mut canonical);
let mut accepted_external = DraftStateTransaction::fork(&canonical);
let accepted = execute(
"external-drafter",
9,
accepted_external.draft_mut(),
&mut trace,
);
assert_eq!(accepted, 12);
accepted_external.commit_draft(&mut canonical);
let mut rejected_external = DraftStateTransaction::fork(&canonical);
let rejected = execute(
"external-drafter",
10,
rejected_external.draft_mut(),
&mut trace,
);
assert_eq!(rejected, 13);
rejected_external.rollback(&mut canonical);
assert_eq!(canonical, [10, 1, 11, 12]);
assert!(!canonical.contains(&rejected));
assert_eq!(
trace,
[
"target",
"prediction.0",
"external-drafter",
"external-drafter"
]
);
}
}