use super::PreTradeLock;
use crate::core::account_outcome::AccountAdjustmentOutcome;
pub struct PreTradeReservation {
inner: Option<Box<dyn ReservationHandle>>,
lock: PreTradeLock,
account_adjustments: Vec<AccountAdjustmentOutcome>,
}
pub(crate) trait ReservationHandle {
fn commit(self: Box<Self>);
fn rollback(self: Box<Self>);
}
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 fn account_adjustments(&self) -> &[AccountAdjustmentOutcome] {
&self.account_adjustments
}
pub(crate) fn from_handle(
inner: Box<dyn ReservationHandle>,
lock: PreTradeLock,
account_adjustments: Vec<AccountAdjustmentOutcome>,
) -> Self {
Self {
inner: Some(inner),
lock,
account_adjustments,
}
}
}
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::core::DEFAULT_POLICY_GROUP_ID;
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::with_capacity(2);
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)),
PreTradeLock::default(),
Vec::new(),
);
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::with_capacity(2);
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)),
PreTradeLock::default(),
Vec::new(),
);
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(),
account_adjustments: Vec::new(),
};
reservation.commit();
}
#[test]
fn rollback_is_noop_for_finalized_reservation() {
let mut reservation = PreTradeReservation {
inner: None,
lock: PreTradeLock::default(),
account_adjustments: Vec::new(),
};
reservation.rollback();
}
#[test]
fn commit_with_locked_reservation_handle() {
let mut reservation = PreTradeReservation::from_handle(
Box::new(LockedReservationHandle),
PreTradeLock::new(),
Vec::new(),
);
reservation.commit();
}
#[test]
fn lock_returns_reservation_lock_with_some_price() {
let price = Price::from_str("185").expect("price must be valid");
let reservation = PreTradeReservation::from_handle(
Box::new(LockedReservationHandle),
PreTradeLock::from_entries([(DEFAULT_POLICY_GROUP_ID, price)]),
Vec::new(),
);
let prices: Vec<_> = reservation
.lock()
.prices_of(DEFAULT_POLICY_GROUP_ID)
.collect();
assert_eq!(prices, vec![price]);
}
#[test]
fn lock_returns_reservation_lock_with_none_price() {
let reservation = PreTradeReservation::from_handle(
Box::new(LockedReservationHandle),
PreTradeLock::new(),
Vec::new(),
);
assert!(reservation
.lock()
.prices_of(DEFAULT_POLICY_GROUP_ID)
.next()
.is_none());
}
#[test]
fn commit_executes_commit_mutations() {
let calls = Rc::new(RefCell::new(Vec::new()));
let mut mutations = Mutations::with_capacity(1);
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)),
PreTradeLock::default(),
Vec::new(),
);
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::with_capacity(1);
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)),
PreTradeLock::default(),
Vec::new(),
);
reservation.rollback();
assert_eq!(&*calls.borrow(), &["rollback"]);
}
struct LockedReservationHandle;
impl ReservationHandle for LockedReservationHandle {
fn commit(self: Box<Self>) {}
fn rollback(self: Box<Self>) {}
}
}