use super::PreTradeLock;
pub struct PreTradeReservation {
inner: Option<Box<dyn ReservationHandle>>,
lock: PreTradeLock,
}
pub(crate) trait ReservationHandle {
fn commit(self: Box<Self>);
fn rollback(self: Box<Self>);
fn lock(&self) -> PreTradeLock;
}
impl PreTradeReservation {
pub fn commit(&mut self) {
self.inner
.take()
.expect("pre-trade reservation already consumed")
.commit();
}
pub fn rollback(&mut self) {
if let Some(inner) = self.inner.take() {
inner.rollback();
}
}
pub fn lock(&self) -> &PreTradeLock {
&self.lock
}
pub(crate) fn from_handle(inner: Box<dyn ReservationHandle>) -> Self {
let lock = inner.lock();
Self {
inner: Some(inner),
lock,
}
}
}
impl Drop for PreTradeReservation {
fn drop(&mut self) {
if let Some(inner) = self.inner.take() {
inner.rollback();
}
}
}
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use std::rc::Rc;
use super::{PreTradeLock, PreTradeReservation, ReservationHandle};
use crate::param::Price;
use crate::pretrade::handle::ReservationHandleImpl;
use crate::{Mutation, Mutations};
fn noop_action() {}
#[test]
fn drop_without_explicit_finalize_rolls_back() {
let calls = Rc::new(RefCell::new(Vec::new()));
let mut mutations = Mutations::new();
let r1 = Rc::clone(&calls);
mutations.push(Mutation::new(noop_action, move || {
r1.borrow_mut().push("m1");
}));
let r2 = Rc::clone(&calls);
mutations.push(Mutation::new(noop_action, move || {
r2.borrow_mut().push("m2");
}));
let reservation =
PreTradeReservation::from_handle(Box::new(ReservationHandleImpl::new(mutations)));
drop(reservation);
assert_eq!(&*calls.borrow(), &["m2", "m1"]);
}
#[test]
fn drop_without_explicit_finalize_can_ignore_non_kill_switch_mutations() {
let calls = Rc::new(RefCell::new(Vec::new()));
let mut mutations = Mutations::new();
let rollback_calls = Rc::clone(&calls);
mutations.push(Mutation::new(noop_action, move || {
rollback_calls.borrow_mut().push("rollback");
}));
mutations.push(Mutation::new(noop_action, noop_action));
let reservation =
PreTradeReservation::from_handle(Box::new(ReservationHandleImpl::new(mutations)));
drop(reservation);
assert_eq!(&*calls.borrow(), &["rollback"]);
}
#[test]
#[should_panic(expected = "pre-trade reservation already consumed")]
fn commit_panics_for_finalized_reservation() {
let mut reservation = PreTradeReservation {
inner: None,
lock: PreTradeLock::default(),
};
reservation.commit();
}
#[test]
fn rollback_is_noop_for_finalized_reservation() {
let mut reservation = PreTradeReservation {
inner: None,
lock: PreTradeLock::default(),
};
reservation.rollback();
}
#[test]
fn commit_with_locked_reservation_handle() {
let mut reservation = PreTradeReservation::from_handle(Box::new(LockedReservationHandle {
lock: PreTradeLock::new(None),
}));
reservation.commit();
}
#[test]
fn lock_returns_handle_lock_with_some_price() {
let price = Price::from_str("185").expect("price must be valid");
let reservation = PreTradeReservation::from_handle(Box::new(LockedReservationHandle {
lock: PreTradeLock::new(Some(price)),
}));
assert_eq!(reservation.lock().price(), Some(price));
}
#[test]
fn lock_returns_handle_lock_with_none_price() {
let reservation = PreTradeReservation::from_handle(Box::new(LockedReservationHandle {
lock: PreTradeLock::new(None),
}));
assert_eq!(reservation.lock().price(), None);
}
#[test]
fn commit_executes_commit_mutations() {
let calls = Rc::new(RefCell::new(Vec::new()));
let mut mutations = Mutations::new();
let commit_calls = Rc::clone(&calls);
mutations.push(Mutation::new(
move || {
commit_calls.borrow_mut().push("commit");
},
noop_action,
));
let mut reservation =
PreTradeReservation::from_handle(Box::new(ReservationHandleImpl::new(mutations)));
reservation.commit();
assert_eq!(&*calls.borrow(), &["commit"]);
}
#[test]
fn rollback_executes_rollback_mutations() {
let calls = Rc::new(RefCell::new(Vec::new()));
let mut mutations = Mutations::new();
let rollback_calls = Rc::clone(&calls);
mutations.push(Mutation::new(noop_action, move || {
rollback_calls.borrow_mut().push("rollback");
}));
let mut reservation =
PreTradeReservation::from_handle(Box::new(ReservationHandleImpl::new(mutations)));
reservation.rollback();
assert_eq!(&*calls.borrow(), &["rollback"]);
}
struct LockedReservationHandle {
lock: PreTradeLock,
}
impl ReservationHandle for LockedReservationHandle {
fn commit(self: Box<Self>) {}
fn rollback(self: Box<Self>) {}
fn lock(&self) -> PreTradeLock {
self.lock
}
}
}