pub struct Mutation {
commit: Box<dyn FnOnce()>,
rollback: Box<dyn FnOnce()>,
}
impl Mutation {
pub fn new(commit: impl FnOnce() + 'static, rollback: impl FnOnce() + 'static) -> Self {
Self {
commit: Box::new(commit),
rollback: Box::new(rollback),
}
}
}
#[derive(Default)]
pub struct Mutations {
mutations: Vec<Mutation>,
}
impl Mutations {
pub fn new() -> Self {
Self {
mutations: Vec::new(),
}
}
pub fn push(&mut self, mutation: Mutation) {
self.mutations.push(mutation);
}
pub(crate) fn commit_all(self) {
for mutation in self.mutations {
(mutation.commit)();
}
}
pub(crate) fn rollback_all(self) {
for mutation in self.mutations.into_iter().rev() {
(mutation.rollback)();
}
}
#[cfg(test)]
pub(crate) fn is_empty(&self) -> bool {
self.mutations.is_empty()
}
}
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use std::rc::Rc;
use super::{Mutation, Mutations};
fn noop_action() {}
#[test]
fn commit_all_applies_in_registration_order() {
let calls = Rc::new(RefCell::new(Vec::new()));
let mut mutations = Mutations::new();
for id in ["a", "b", "c"] {
let c = Rc::clone(&calls);
mutations.push(Mutation::new(
move || {
c.borrow_mut().push(id);
},
noop_action,
));
}
mutations.commit_all();
assert_eq!(&*calls.borrow(), &["a", "b", "c"]);
}
#[test]
fn rollback_all_applies_in_reverse_order() {
let calls = Rc::new(RefCell::new(Vec::new()));
let mut mutations = Mutations::new();
for id in ["a", "b", "c"] {
let r = Rc::clone(&calls);
mutations.push(Mutation::new(noop_action, move || {
r.borrow_mut().push(id);
}));
}
mutations.rollback_all();
assert_eq!(&*calls.borrow(), &["c", "b", "a"]);
}
#[test]
fn default_creates_empty_mutations() {
let mutations = Mutations::default();
assert!(mutations.is_empty());
}
#[test]
fn commit_all_on_empty_is_noop() {
noop_action();
Mutations::new().commit_all();
}
#[test]
fn rollback_all_on_empty_is_noop() {
Mutations::new().rollback_all();
}
}