1use std::sync::Arc;
2
3use crate::{
4 Account, Capabilities, Capability, CapabilityUnavailableReason, Error, ExchangeInfo,
5 FuturesTrading, MarketData, PrivateStream, PublicStream, Result, SpotTrading,
6};
7
8#[derive(Clone)]
9pub struct ExchangeHandle {
10 info: Arc<dyn ExchangeInfo>,
11 market_data: Option<Arc<dyn MarketData>>,
12 spot_trading: Option<Arc<dyn SpotTrading>>,
13 futures_trading: Option<Arc<dyn FuturesTrading>>,
14 account: Option<Arc<dyn Account>>,
15 public_stream: Option<Arc<dyn PublicStream>>,
16 private_stream: Option<Arc<dyn PrivateStream>>,
17}
18
19impl ExchangeHandle {
20 pub fn builder(info: Arc<dyn ExchangeInfo>) -> Builder {
21 Builder::new(info)
22 }
23
24 pub fn info(&self) -> &dyn ExchangeInfo {
25 self.info.as_ref()
26 }
27
28 pub fn market_data(&self) -> Result<&dyn MarketData> {
29 self.market_data
30 .as_deref()
31 .ok_or_else(|| self.capability_error(Capability::MarketData))
32 }
33
34 pub fn spot_trading(&self) -> Result<&dyn SpotTrading> {
35 self.spot_trading
36 .as_deref()
37 .ok_or_else(|| self.capability_error(Capability::SpotTrading))
38 }
39
40 pub fn futures_trading(&self) -> Result<&dyn FuturesTrading> {
41 self.futures_trading
42 .as_deref()
43 .ok_or_else(|| self.capability_error(Capability::FuturesTrading))
44 }
45
46 pub fn account(&self) -> Result<&dyn Account> {
47 self.account
48 .as_deref()
49 .ok_or_else(|| self.capability_error(Capability::Account))
50 }
51
52 pub fn public_stream(&self) -> Result<&dyn PublicStream> {
53 self.public_stream
54 .as_deref()
55 .ok_or_else(|| self.capability_error(Capability::PublicStream))
56 }
57
58 pub fn private_stream(&self) -> Result<&dyn PrivateStream> {
59 self.private_stream
60 .as_deref()
61 .ok_or_else(|| self.capability_error(Capability::PrivateStream))
62 }
63
64 pub fn capabilities(&self) -> Capabilities {
65 let advertised = self.info.capabilities();
66
67 Capabilities::new(self.info.id())
68 .with_markets(advertised.markets)
69 .with_transport(advertised.transport)
70 .with_capabilities(
71 [
72 self.market_data.is_some().then_some(Capability::MarketData),
73 self.spot_trading
74 .is_some()
75 .then_some(Capability::SpotTrading),
76 self.futures_trading
77 .is_some()
78 .then_some(Capability::FuturesTrading),
79 self.account.is_some().then_some(Capability::Account),
80 self.public_stream
81 .is_some()
82 .then_some(Capability::PublicStream),
83 self.private_stream
84 .is_some()
85 .then_some(Capability::PrivateStream),
86 ]
87 .into_iter()
88 .flatten(),
89 )
90 }
91
92 fn capability_error(&self, capability: Capability) -> Error {
93 let reason = if self.info.capabilities().supports_capability(capability) {
94 CapabilityUnavailableReason::NotBound
95 } else {
96 CapabilityUnavailableReason::NotAdvertised
97 };
98
99 Error::capability_unavailable(self.info.id(), capability, reason)
100 }
101}
102
103#[derive(Clone)]
104pub struct Builder {
105 info: Arc<dyn ExchangeInfo>,
106 market_data: Option<Arc<dyn MarketData>>,
107 spot_trading: Option<Arc<dyn SpotTrading>>,
108 futures_trading: Option<Arc<dyn FuturesTrading>>,
109 account: Option<Arc<dyn Account>>,
110 public_stream: Option<Arc<dyn PublicStream>>,
111 private_stream: Option<Arc<dyn PrivateStream>>,
112}
113
114impl Builder {
115 pub fn new(info: Arc<dyn ExchangeInfo>) -> Self {
116 Self {
117 info,
118 market_data: None,
119 spot_trading: None,
120 futures_trading: None,
121 account: None,
122 public_stream: None,
123 private_stream: None,
124 }
125 }
126
127 pub fn market_data(mut self, market_data: Arc<dyn MarketData>) -> Self {
128 self.market_data = Some(market_data);
129 self
130 }
131
132 pub fn spot_trading(mut self, spot_trading: Arc<dyn SpotTrading>) -> Self {
133 self.spot_trading = Some(spot_trading);
134 self
135 }
136
137 pub fn futures_trading(mut self, futures_trading: Arc<dyn FuturesTrading>) -> Self {
138 self.futures_trading = Some(futures_trading);
139 self
140 }
141
142 pub fn account(mut self, account: Arc<dyn Account>) -> Self {
143 self.account = Some(account);
144 self
145 }
146
147 pub fn public_stream(mut self, public_stream: Arc<dyn PublicStream>) -> Self {
148 self.public_stream = Some(public_stream);
149 self
150 }
151
152 pub fn private_stream(mut self, private_stream: Arc<dyn PrivateStream>) -> Self {
153 self.private_stream = Some(private_stream);
154 self
155 }
156
157 pub fn build(self) -> ExchangeHandle {
158 ExchangeHandle {
159 info: self.info,
160 market_data: self.market_data,
161 spot_trading: self.spot_trading,
162 futures_trading: self.futures_trading,
163 account: self.account,
164 public_stream: self.public_stream,
165 private_stream: self.private_stream,
166 }
167 }
168}
169
170impl std::fmt::Debug for ExchangeHandle {
171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 f.debug_struct("ExchangeHandle")
173 .field("exchange_id", &self.info.id())
174 .field("has_market_data", &self.market_data.is_some())
175 .field("has_spot_trading", &self.spot_trading.is_some())
176 .field("has_futures_trading", &self.futures_trading.is_some())
177 .field("has_account", &self.account.is_some())
178 .field("has_public_stream", &self.public_stream.is_some())
179 .field("has_private_stream", &self.private_stream.is_some())
180 .finish()
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use std::sync::Arc;
187
188 use mkt_types::{ExchangeId, KnownExchange};
189
190 use super::ExchangeHandle;
191 use crate::{Capabilities, Capability, CapabilityUnavailableReason, Error, ExchangeInfo};
192
193 struct StubExchange {
194 capabilities: Capabilities,
195 }
196
197 impl ExchangeInfo for StubExchange {
198 fn id(&self) -> ExchangeId {
199 self.capabilities.exchange_id.clone()
200 }
201
202 fn capabilities(&self) -> Capabilities {
203 self.capabilities.clone()
204 }
205 }
206
207 #[test]
208 fn missing_unadvertised_capability_reports_not_advertised() {
209 let exchange_id = ExchangeId::from(KnownExchange::Binance);
210 let handle = ExchangeHandle::builder(Arc::new(StubExchange {
211 capabilities: Capabilities::new(exchange_id.clone()),
212 }))
213 .build();
214
215 let err = handle
216 .market_data()
217 .err()
218 .expect("market data is not advertised");
219
220 assert!(matches!(
221 err,
222 Error::CapabilityUnavailable {
223 exchange,
224 capability: Capability::MarketData,
225 reason: CapabilityUnavailableReason::NotAdvertised,
226 } if exchange == exchange_id
227 ));
228 }
229
230 #[test]
231 fn advertised_but_unbound_capability_reports_binding_gap() {
232 let exchange_id = ExchangeId::from(KnownExchange::Binance);
233 let handle = ExchangeHandle::builder(Arc::new(StubExchange {
234 capabilities: Capabilities::new(exchange_id.clone())
235 .with_capabilities([Capability::MarketData]),
236 }))
237 .build();
238
239 let err = handle
240 .market_data()
241 .err()
242 .expect("market data was advertised but not bound");
243
244 assert!(matches!(
245 err,
246 Error::CapabilityUnavailable {
247 exchange,
248 capability: Capability::MarketData,
249 reason: CapabilityUnavailableReason::NotBound,
250 } if exchange == exchange_id
251 ));
252 }
253}