1use std::ops::Deref;
2use std::sync::Arc;
3use std::time::Duration;
4
5use crate::ws::managed::{
6 MakerWsEndpoint, ManagedMessageReceiver, ManagedWsConfig, ManagedWsError, ManagedWsEvent,
7 ManagedWsHandle, ManagedWsState, SendAwaitError, WaitUntilReadyError, spawn_managed_ws,
8};
9use crate::ws::types::*;
10use tokio::sync::broadcast;
11
12use super::MakerClientError;
13
14mod private {
15 pub trait Sealed {}
16}
17
18pub trait MakerDataRequest: private::Sealed + Sized {
20 type Response;
21
22 const RESPONSE_TYPE: &'static str;
23
24 fn into_message(self) -> ClientMessage;
25 fn into_response(message: ServerMessage) -> Result<Self::Response, Box<ServerMessage>>;
29}
30
31pub struct MakerResponse<T> {
32 payload: T,
33}
34
35impl<T> MakerResponse<T> {
36 #[must_use]
37 pub const fn payload(&self) -> &T {
38 &self.payload
39 }
40
41 #[must_use]
42 pub fn into_payload(self) -> T {
43 self.payload
44 }
45}
46
47impl<T> Deref for MakerResponse<T> {
48 type Target = T;
49
50 fn deref(&self) -> &Self::Target {
51 &self.payload
52 }
53}
54
55#[derive(Debug, thiserror::Error)]
56#[non_exhaustive]
57pub enum MakerDataRequestError {
58 #[error(transparent)]
59 Session(#[from] ManagedWsError),
60 #[error(transparent)]
61 Transport(#[from] SendAwaitError),
62 #[error("server rejected request {request_id}")]
63 Rejected {
64 request_id: uuid::Uuid,
65 response: Arc<ServerMessage>,
66 },
67 #[error("expected {expected}, received a different correlated response")]
68 UnexpectedResponse {
69 expected: &'static str,
70 response: Arc<ServerMessage>,
71 },
72}
73
74impl MakerDataRequestError {
75 #[must_use]
76 pub fn server_error(&self) -> Option<&ServerError> {
77 match self {
78 Self::Rejected { response, .. } => match &**response {
79 ServerMessage::RequestError(error) => Some(&error.error),
80 _ => None,
81 },
82 _ => None,
83 }
84 }
85}
86
87#[derive(Clone)]
89pub struct MakerDataClient {
90 inner: ManagedWsHandle,
91}
92
93impl MakerDataClient {
94 pub fn spawn(config: ManagedWsConfig) -> Result<Self, MakerClientError> {
95 let config = Self::prepare_config(config)?;
96 Ok(Self {
97 inner: spawn_managed_ws(config)?,
98 })
99 }
100
101 fn prepare_config(config: ManagedWsConfig) -> Result<ManagedWsConfig, MakerClientError> {
102 if config.endpoint != MakerWsEndpoint::Data {
103 return Err(MakerClientError::WrongEndpoint {
104 client: "MakerDataClient",
105 required: MakerWsEndpoint::Data,
106 actual: config.endpoint,
107 });
108 }
109 Ok(config)
110 }
111
112 pub async fn request<R: MakerDataRequest>(
113 &self,
114 request: R,
115 timeout: Duration,
116 ) -> Result<MakerResponse<R::Response>, MakerDataRequestError> {
117 let message = self
118 .inner
119 .send_await_ready(request.into_message(), timeout)
120 .await?;
121 if let ServerMessage::RequestError(error) = &*message {
122 return Err(MakerDataRequestError::Rejected {
123 request_id: error.request_id,
124 response: message,
125 });
126 }
127 let message = match Arc::try_unwrap(message) {
128 Ok(owned) => owned,
129 Err(shared) => (*shared).clone(),
130 };
131 match R::into_response(message) {
132 Ok(payload) => Ok(MakerResponse { payload }),
133 Err(other) => Err(MakerDataRequestError::UnexpectedResponse {
134 expected: R::RESPONSE_TYPE,
135 response: Arc::from(other),
136 }),
137 }
138 }
139
140 pub fn subscribe_messages(&self) -> ManagedMessageReceiver {
141 self.inner.subscribe_messages()
142 }
143
144 pub fn subscribe_events(&self) -> broadcast::Receiver<ManagedWsEvent> {
145 self.inner.subscribe_events()
146 }
147
148 #[must_use]
149 pub fn state(&self) -> ManagedWsState {
150 self.inner.state()
151 }
152
153 pub async fn wait_until_ready(&self) -> Result<u64, WaitUntilReadyError> {
154 self.inner.wait_until_ready().await
155 }
156
157 #[must_use]
158 pub const fn raw(&self) -> &ManagedWsHandle {
159 &self.inner
160 }
161
162 #[must_use]
163 pub fn into_raw(self) -> ManagedWsHandle {
164 self.inner
165 }
166
167 pub async fn close(&self) -> Result<(), ManagedWsError> {
168 self.inner.close().await
169 }
170}
171
172macro_rules! data_request {
173 ($request:ty, $client_variant:ident, $response:ty, $server_variant:ident) => {
174 impl private::Sealed for $request {}
175
176 impl MakerDataRequest for $request {
177 type Response = $response;
178
179 const RESPONSE_TYPE: &'static str = stringify!($server_variant);
180
181 fn into_message(self) -> ClientMessage {
182 ClientMessage::$client_variant(self)
183 }
184
185 fn into_response(message: ServerMessage) -> Result<Self::Response, Box<ServerMessage>> {
186 match message {
187 ServerMessage::$server_variant(response) => Ok(response),
188 other => Err(Box::new(other)),
189 }
190 }
191 }
192 };
193}
194
195data_request!(
196 GetActiveRfqsMessage,
197 GetActiveRfqs,
198 ActiveRfqsData,
199 ActiveRfqs
200);
201data_request!(GetMarketsMessage, GetMarkets, MarketsData, Markets);
202data_request!(
203 GetMarketDescriptorsMessage,
204 GetMarketDescriptors,
205 MarketDescriptorsData,
206 MarketDescriptors
207);
208data_request!(GetExpiriesMessage, GetExpiries, ExpiriesData, Expiries);
209data_request!(GetTokensMessage, GetTokens, TokensData, Tokens);
210data_request!(
211 GetIndicativePricesMessage,
212 GetIndicativePrices,
213 IndicativePricesMessage,
214 IndicativePrices
215);
216data_request!(
217 GetMakerPositionsMessage,
218 GetMakerPositions,
219 MakerPositionsMessage,
220 MakerPositions
221);
222data_request!(GetMyQuotesMessage, GetMyQuotes, MyQuotesMessage, MyQuotes);
223data_request!(
224 GetOrderStatusMessage,
225 GetOrderStatus,
226 OrderStatusMessage,
227 OrderStatus
228);
229data_request!(
230 GetMarketsForMakerMessage,
231 GetMarketsForMaker,
232 MakerMarketsMessage,
233 MakerMarkets
234);
235data_request!(GetTokenCapsMessage, GetTokenCaps, TokenCapsData, TokenCaps);
236data_request!(GetMyCapsMessage, GetMyCaps, MyCapsData, MyCaps);
237data_request!(CheckQuoteMessage, CheckQuote, QuoteCheckData, QuoteCheck);
238data_request!(GetMyTradesMessage, GetMyTrades, MyTradesMessage, MyTrades);
239data_request!(
240 GetEarnSummaryMessage,
241 GetEarnSummary,
242 EarnSummaryData,
243 EarnSummary
244);
245data_request!(GetMmSummaryMessage, GetMmSummary, MmSummaryData, MmSummary);
246data_request!(
247 GetTokenMarketsInfoMessage,
248 GetTokenMarketsInfo,
249 TokenMarketsInfoData,
250 TokenMarketsInfo
251);
252
253#[cfg(test)]
254mod tests {
255 use std::sync::Arc;
256
257 use super::*;
258 use crate::ws::managed::ManagedWsConfig;
259 use tokio::sync::mpsc::error::TryRecvError;
260
261 struct LeavesReadyWhileEncoding {
262 handle: ManagedWsHandle,
263 request_id: uuid::Uuid,
264 }
265
266 impl private::Sealed for LeavesReadyWhileEncoding {}
267
268 impl MakerDataRequest for LeavesReadyWhileEncoding {
269 type Response = MmSummaryData;
270
271 const RESPONSE_TYPE: &'static str = "MmSummary";
272
273 fn into_message(self) -> ClientMessage {
274 self.handle.inject_state(ManagedWsState::Authenticated {
275 connection_epoch: 7,
276 });
277 ClientMessage::GetMmSummary(GetMmSummaryMessage {
278 request_id: self.request_id,
279 })
280 }
281
282 fn into_response(message: ServerMessage) -> Result<Self::Response, Box<ServerMessage>> {
283 match message {
284 ServerMessage::MmSummary(response) => Ok(response),
285 other => Err(Box::new(other)),
286 }
287 }
288 }
289
290 fn config() -> ManagedWsConfig {
291 ManagedWsConfig::new(
292 "ws://localhost",
293 HelloData {
294 protocol_version: "1.0.0".to_string(),
295 features: Vec::new(),
296 client_name: None,
297 client_version: None,
298 },
299 Arc::new(crate::orders::BytesSigner::from_secret([1u8; 32])),
300 )
301 }
302
303 #[test]
304 fn data_client_rejects_quote_endpoint_before_spawning() {
305 assert!(matches!(
306 MakerDataClient::spawn(config()),
307 Err(MakerClientError::WrongEndpoint {
308 required: MakerWsEndpoint::Data,
309 actual: MakerWsEndpoint::Quote,
310 ..
311 })
312 ));
313 }
314
315 #[test]
316 fn data_request_maps_to_one_typed_response() {
317 let request_id = uuid::Uuid::new_v4();
318 let request = GetActiveRfqsMessage { request_id };
319 assert!(matches!(
320 request.into_message(),
321 ClientMessage::GetActiveRfqs(GetActiveRfqsMessage {
322 request_id: actual
323 }) if actual == request_id
324 ));
325
326 let response = ServerMessage::ActiveRfqs(ActiveRfqsData {
327 request_id,
328 rfqs: Vec::new(),
329 });
330 assert_eq!(
331 <GetActiveRfqsMessage as MakerDataRequest>::into_response(response)
332 .expect("typed response")
333 .request_id,
334 request_id
335 );
336 assert!(matches!(
337 <GetActiveRfqsMessage as MakerDataRequest>::into_response(ServerMessage::RequireInvite),
338 Err(boxed) if matches!(*boxed, ServerMessage::RequireInvite)
339 ));
340 }
341
342 #[tokio::test]
343 async fn typed_request_does_not_enqueue_an_unbound_query_after_leaving_ready() {
344 let (handle, mut commands) = ManagedWsHandle::test_handle(1, 1);
345 handle.inject_state(ManagedWsState::Ready {
346 connection_epoch: 7,
347 });
348 let client = MakerDataClient {
349 inner: handle.clone(),
350 };
351
352 let result = tokio::select! {
353 result = client.request(
354 LeavesReadyWhileEncoding {
355 handle,
356 request_id: uuid::Uuid::new_v4(),
357 },
358 Duration::from_secs(1),
359 ) => result,
360 _ = commands.recv() => panic!("typed data request must not enqueue after leaving Ready"),
361 };
362
363 assert!(matches!(
364 result,
365 Err(MakerDataRequestError::Transport(SendAwaitError::NotReady))
366 ));
367 assert!(matches!(commands.try_recv(), Err(TryRecvError::Empty)));
368 }
369}