Skip to main content

brokapi_runtime/
command_gate.rs

1//! Trading command admission control.
2
3use brokapi_core::Error;
4
5/// Decides whether a trading command may go out now.
6///
7/// A connector asks the gate before it sends a place, cancel or replace. The gate refuses in three
8/// cases, checked in this order: the connector is shutting down, it is degraded, it has not been
9/// reconciled yet. The gate starts closed (not reconciled), so the first reconcile with the broker
10/// opens it. Why: before the client knows which orders already exist at the broker, a new command
11/// could duplicate or fight an order it cannot see.
12///
13/// The gate only holds flags. The connector sets them: [`mark_reconciled`](Self::mark_reconciled)
14/// after a successful reconcile, [`mark_degraded`](Self::mark_degraded) when the
15/// [`Emitter`](crate::Emitter) reports an overflow, [`begin_shutdown`](Self::begin_shutdown) when
16/// closing. Reads (order book, portfolio, active orders) are not gated.
17///
18/// # Examples
19///
20/// ```
21/// use brokapi_core::Error;
22/// use brokapi_runtime::CommandGate;
23///
24/// let mut gate = CommandGate::new();
25/// // A new gate refuses commands until the first reconcile.
26/// assert!(matches!(gate.check_command(), Err(Error::NotReconciled)));
27///
28/// gate.mark_reconciled();
29/// assert!(gate.check_command().is_ok());
30///
31/// // An overflow of a lossless stream closes it again until the next reconcile.
32/// gate.mark_degraded();
33/// assert!(matches!(gate.check_command(), Err(Error::Degraded)));
34/// gate.clear_degraded();
35/// assert!(gate.check_command().is_ok());
36///
37/// // Shutdown wins over everything else and is final.
38/// gate.begin_shutdown();
39/// assert!(matches!(gate.check_command(), Err(Error::ShuttingDown)));
40/// ```
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub struct CommandGate {
43    reconciled: bool,
44    degraded: bool,
45    shutting_down: bool,
46}
47
48impl Default for CommandGate {
49    /// A closed gate: not reconciled, not degraded, not shutting down.
50    fn default() -> Self {
51        Self {
52            reconciled: false,
53            degraded: false,
54            shutting_down: false,
55        }
56    }
57}
58
59impl CommandGate {
60    /// Creates a closed gate. Same as [`Default::default`].
61    pub fn new() -> Self {
62        Self::default()
63    }
64
65    /// Whether [`mark_reconciled`](Self::mark_reconciled) was called.
66    pub fn is_reconciled(&self) -> bool {
67        self.reconciled
68    }
69
70    /// Whether the gate is in the degraded state.
71    pub fn is_degraded(&self) -> bool {
72        self.degraded
73    }
74
75    /// Records a successful reconcile. There is no way to undo it.
76    pub fn mark_reconciled(&mut self) {
77        self.reconciled = true;
78    }
79
80    /// Records that events were lost (see [`Emitter::is_degraded`](crate::Emitter::is_degraded)).
81    /// Commands are refused until [`clear_degraded`](Self::clear_degraded).
82    pub fn mark_degraded(&mut self) {
83        self.degraded = true;
84    }
85
86    /// Leaves the degraded state. Call it after the reconcile that made up for the lost events.
87    pub fn clear_degraded(&mut self) {
88        self.degraded = false;
89    }
90
91    /// Closes the gate for good: every later check fails with [`Error::ShuttingDown`].
92    pub fn begin_shutdown(&mut self) {
93        self.shutting_down = true;
94    }
95
96    /// Says whether a trading command may be sent now.
97    ///
98    /// # Errors
99    ///
100    /// In this order of priority:
101    ///
102    /// - [`Error::ShuttingDown`] after [`begin_shutdown`](Self::begin_shutdown).
103    /// - [`Error::Degraded`] while the gate is degraded.
104    /// - [`Error::NotReconciled`] until [`mark_reconciled`](Self::mark_reconciled).
105    ///
106    /// All three mean nothing was sent, so the command may be repeated once the cause is gone
107    /// (except after a shutdown).
108    pub fn check_command(&self) -> Result<(), Error> {
109        if self.shutting_down {
110            return Err(Error::ShuttingDown);
111        }
112        if self.degraded {
113            return Err(Error::Degraded);
114        }
115        if !self.reconciled {
116            return Err(Error::NotReconciled);
117        }
118        Ok(())
119    }
120}