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, ReferenceChangeReceiver, SendAwaitError, WaitUntilReadyError,
8 spawn_managed_ws,
9};
10use crate::ws::types::*;
11use tokio::sync::broadcast;
12
13use super::MakerClientError;
14
15mod private {
16 pub trait Sealed {}
17}
18
19pub trait MakerDataRequest: private::Sealed + Sized {
21 type Response;
22
23 const RESPONSE_TYPE: &'static str;
24
25 fn into_message(self) -> ClientMessage;
26 fn into_response(message: ServerMessage) -> Result<Self::Response, Box<ServerMessage>>;
30}
31
32pub struct MakerResponse<T> {
33 payload: T,
34}
35
36impl<T> MakerResponse<T> {
37 #[must_use]
38 pub const fn payload(&self) -> &T {
39 &self.payload
40 }
41
42 #[must_use]
43 pub fn into_payload(self) -> T {
44 self.payload
45 }
46}
47
48impl<T> Deref for MakerResponse<T> {
49 type Target = T;
50
51 fn deref(&self) -> &Self::Target {
52 &self.payload
53 }
54}
55
56#[derive(Debug, thiserror::Error)]
57#[non_exhaustive]
58pub enum MakerDataRequestError {
59 #[error(transparent)]
60 Session(#[from] ManagedWsError),
61 #[error(transparent)]
62 Transport(#[from] SendAwaitError),
63 #[error("server rejected request {request_id}")]
64 Rejected {
65 request_id: uuid::Uuid,
66 response: Arc<ServerMessage>,
67 },
68 #[error("expected {expected}, received a different correlated response")]
69 UnexpectedResponse {
70 expected: &'static str,
71 response: Arc<ServerMessage>,
72 },
73}
74
75impl MakerDataRequestError {
76 #[must_use]
77 pub fn server_error(&self) -> Option<&ServerError> {
78 match self {
79 Self::Rejected { response, .. } => match &**response {
80 ServerMessage::RequestError(error) => Some(&error.error),
81 _ => None,
82 },
83 _ => None,
84 }
85 }
86}
87
88const MAKER_POSITIONS_PAGE_LIMIT: u32 = 500;
90
91#[derive(Clone)]
93pub struct MakerDataClient {
94 inner: ManagedWsHandle,
95}
96
97impl MakerDataClient {
98 pub fn spawn(config: ManagedWsConfig) -> Result<Self, MakerClientError> {
99 let config = Self::prepare_config(config)?;
100 Ok(Self {
101 inner: spawn_managed_ws(config)?,
102 })
103 }
104
105 fn prepare_config(config: ManagedWsConfig) -> Result<ManagedWsConfig, MakerClientError> {
106 if config.endpoint != MakerWsEndpoint::Data {
107 return Err(MakerClientError::WrongEndpoint {
108 client: "MakerDataClient",
109 required: MakerWsEndpoint::Data,
110 actual: config.endpoint,
111 });
112 }
113 Ok(config)
114 }
115
116 pub async fn request<R: MakerDataRequest>(
117 &self,
118 request: R,
119 timeout: Duration,
120 ) -> Result<MakerResponse<R::Response>, MakerDataRequestError> {
121 let message = self
122 .inner
123 .send_await_ready(request.into_message(), timeout)
124 .await?;
125 if let ServerMessage::RequestError(error) = &*message {
126 return Err(MakerDataRequestError::Rejected {
127 request_id: error.request_id,
128 response: message,
129 });
130 }
131 let message = match Arc::try_unwrap(message) {
132 Ok(owned) => owned,
133 Err(shared) => (*shared).clone(),
134 };
135 match R::into_response(message) {
136 Ok(payload) => Ok(MakerResponse { payload }),
137 Err(other) => Err(MakerDataRequestError::UnexpectedResponse {
138 expected: R::RESPONSE_TYPE,
139 response: Arc::from(other),
140 }),
141 }
142 }
143
144 pub async fn all_maker_positions(
148 &self,
149 mut filter: GetMakerPositionsMessage,
150 timeout: Duration,
151 ) -> Result<MakerPositionsMessage, MakerDataRequestError> {
152 let mut all = MakerPositionsMessage {
153 request_id: filter.request_id,
154 positions: Vec::new(),
155 unrenderable: Vec::new(),
156 has_more: false,
157 };
158 filter.limit = Some(MAKER_POSITIONS_PAGE_LIMIT);
159 filter.cursor = None;
160 filter.cursor_id = None;
161 loop {
162 filter.request_id = uuid::Uuid::new_v4();
163 let page = self.request(filter.clone(), timeout).await?.into_payload();
164 let last = page
166 .positions
167 .iter()
168 .map(|p| (p.created_at, p.pda.as_str()))
169 .chain(
170 page.unrenderable
171 .iter()
172 .map(|p| (p.created_at, p.pda.as_str())),
173 )
174 .min()
175 .map(|(created_at, pda)| (created_at, pda.to_owned()));
176 all.positions.extend(page.positions);
177 all.unrenderable.extend(page.unrenderable);
178 match (page.has_more, last) {
179 (true, Some((created_at, pda))) => {
180 filter.cursor = Some(created_at);
181 filter.cursor_id = Some(pda);
182 }
183 _ => return Ok(all),
184 }
185 }
186 }
187
188 pub fn subscribe_messages(&self) -> ManagedMessageReceiver {
194 self.inner.subscribe_messages()
195 }
196
197 pub fn subscribe_reference_changes(&self) -> ReferenceChangeReceiver {
198 self.inner.subscribe_reference_changes()
199 }
200
201 pub fn subscribe_events(&self) -> broadcast::Receiver<ManagedWsEvent> {
202 self.inner.subscribe_events()
203 }
204
205 #[must_use]
206 pub fn state(&self) -> ManagedWsState {
207 self.inner.state()
208 }
209
210 pub async fn wait_until_ready(&self) -> Result<u64, WaitUntilReadyError> {
211 self.inner.wait_until_ready().await
212 }
213
214 #[must_use]
215 pub const fn raw(&self) -> &ManagedWsHandle {
216 &self.inner
217 }
218
219 #[must_use]
220 pub fn into_raw(self) -> ManagedWsHandle {
221 self.inner
222 }
223
224 pub async fn close(&self) -> Result<(), ManagedWsError> {
225 self.inner.close().await
226 }
227}
228
229macro_rules! data_request {
230 ($request:ty, $client_variant:ident, $response:ty, $server_variant:ident) => {
231 impl private::Sealed for $request {}
232
233 impl MakerDataRequest for $request {
234 type Response = $response;
235
236 const RESPONSE_TYPE: &'static str = stringify!($server_variant);
237
238 fn into_message(self) -> ClientMessage {
239 ClientMessage::$client_variant(self)
240 }
241
242 fn into_response(message: ServerMessage) -> Result<Self::Response, Box<ServerMessage>> {
243 match message {
244 ServerMessage::$server_variant(response) => Ok(response),
245 other => Err(Box::new(other)),
246 }
247 }
248 }
249 };
250}
251
252data_request!(
253 GetActiveRfqsMessage,
254 GetActiveRfqs,
255 ActiveRfqsData,
256 ActiveRfqs
257);
258data_request!(GetMarketsMessage, GetMarkets, MarketsData, Markets);
259data_request!(
260 GetMarketDescriptorsMessage,
261 GetMarketDescriptors,
262 MarketDescriptorsData,
263 MarketDescriptors
264);
265data_request!(GetExpiriesMessage, GetExpiries, ExpiriesData, Expiries);
266data_request!(GetTokensMessage, GetTokens, TokensData, Tokens);
267data_request!(
268 GetIndicativePricesMessage,
269 GetIndicativePrices,
270 IndicativePricesMessage,
271 IndicativePrices
272);
273data_request!(
274 GetMakerPositionsMessage,
275 GetMakerPositions,
276 MakerPositionsMessage,
277 MakerPositions
278);
279data_request!(GetMyQuotesMessage, GetMyQuotes, MyQuotesMessage, MyQuotes);
280data_request!(
281 GetOrderStatusMessage,
282 GetOrderStatus,
283 OrderStatusMessage,
284 OrderStatus
285);
286data_request!(
287 GetMarketsForMakerMessage,
288 GetMarketsForMaker,
289 MakerMarketsMessage,
290 MakerMarkets
291);
292data_request!(GetTokenCapsMessage, GetTokenCaps, TokenCapsData, TokenCaps);
293data_request!(GetMyCapsMessage, GetMyCaps, MyCapsData, MyCaps);
294data_request!(CheckQuoteMessage, CheckQuote, QuoteCheckData, QuoteCheck);
295data_request!(GetMyTradesMessage, GetMyTrades, MyTradesMessage, MyTrades);
296data_request!(
297 GetEarnSummaryMessage,
298 GetEarnSummary,
299 EarnSummaryData,
300 EarnSummary
301);
302data_request!(GetMmSummaryMessage, GetMmSummary, MmSummaryData, MmSummary);
303data_request!(
304 GetTokenMarketsInfoMessage,
305 GetTokenMarketsInfo,
306 TokenMarketsInfoData,
307 TokenMarketsInfo
308);
309
310#[cfg(test)]
311mod tests {
312 use std::sync::Arc;
313
314 use super::*;
315 use crate::ws::managed::ManagedWsConfig;
316 use tokio::sync::mpsc::error::TryRecvError;
317
318 struct LeavesReadyWhileEncoding {
319 handle: ManagedWsHandle,
320 request_id: uuid::Uuid,
321 }
322
323 impl private::Sealed for LeavesReadyWhileEncoding {}
324
325 impl MakerDataRequest for LeavesReadyWhileEncoding {
326 type Response = MmSummaryData;
327
328 const RESPONSE_TYPE: &'static str = "MmSummary";
329
330 fn into_message(self) -> ClientMessage {
331 self.handle.inject_state(ManagedWsState::Authenticated {
332 connection_epoch: 7,
333 });
334 ClientMessage::GetMmSummary(GetMmSummaryMessage {
335 request_id: self.request_id,
336 })
337 }
338
339 fn into_response(message: ServerMessage) -> Result<Self::Response, Box<ServerMessage>> {
340 match message {
341 ServerMessage::MmSummary(response) => Ok(response),
342 other => Err(Box::new(other)),
343 }
344 }
345 }
346
347 fn config() -> ManagedWsConfig {
348 ManagedWsConfig::new(
349 "ws://localhost",
350 HelloData {
351 protocol_version: "1.0.0".to_string(),
352 features: Vec::new(),
353 client_name: None,
354 client_version: None,
355 },
356 Arc::new(crate::orders::BytesSigner::from_secret([1u8; 32])),
357 "owner",
358 )
359 }
360
361 #[test]
362 fn data_client_rejects_quote_endpoint_before_spawning() {
363 assert!(matches!(
364 MakerDataClient::spawn(config()),
365 Err(MakerClientError::WrongEndpoint {
366 required: MakerWsEndpoint::Data,
367 actual: MakerWsEndpoint::Quote,
368 ..
369 })
370 ));
371 }
372
373 #[test]
374 fn data_request_maps_to_one_typed_response() {
375 let request_id = uuid::Uuid::new_v4();
376 let request = GetActiveRfqsMessage { request_id };
377 assert!(matches!(
378 request.into_message(),
379 ClientMessage::GetActiveRfqs(GetActiveRfqsMessage {
380 request_id: actual
381 }) if actual == request_id
382 ));
383
384 let response = ServerMessage::ActiveRfqs(ActiveRfqsData {
385 request_id,
386 rfqs: Vec::new(),
387 });
388 assert_eq!(
389 <GetActiveRfqsMessage as MakerDataRequest>::into_response(response)
390 .expect("typed response")
391 .request_id,
392 request_id
393 );
394 assert!(matches!(
395 <GetActiveRfqsMessage as MakerDataRequest>::into_response(ServerMessage::RequireInvite),
396 Err(boxed) if matches!(*boxed, ServerMessage::RequireInvite)
397 ));
398 }
399
400 #[tokio::test]
401 async fn typed_request_does_not_enqueue_an_unbound_query_after_leaving_ready() {
402 let (handle, mut commands) = ManagedWsHandle::test_handle(1, 1);
403 handle.inject_state(ManagedWsState::Ready {
404 connection_epoch: 7,
405 });
406 let client = MakerDataClient {
407 inner: handle.clone(),
408 };
409
410 let result = tokio::select! {
411 result = client.request(
412 LeavesReadyWhileEncoding {
413 handle,
414 request_id: uuid::Uuid::new_v4(),
415 },
416 Duration::from_secs(1),
417 ) => result,
418 _ = commands.recv() => panic!("typed data request must not enqueue after leaving Ready"),
419 };
420
421 assert!(matches!(
422 result,
423 Err(MakerDataRequestError::Transport(SendAwaitError::NotReady))
424 ));
425 assert!(matches!(commands.try_recv(), Err(TryRecvError::Empty)));
426 }
427 #[tokio::test]
428 async fn data_client_exposes_all_owner_pushes() {
429 let (handle, _commands) = ManagedWsHandle::test_handle(1, 8);
430 let client = MakerDataClient {
431 inner: handle.clone(),
432 };
433 let mut messages = client.subscribe_messages();
434 for message in owner_pushes() {
435 let expected = serde_json::to_value(&message).unwrap();
436 handle.inject_message(message);
437 let received = messages.recv().await.unwrap();
438 assert_eq!(serde_json::to_value(received.message()).unwrap(), expected);
439 }
440 }
441
442 fn owner_pushes() -> Vec<ServerMessage> {
443 let caps = serde_json::json!({"positions": {"current": 1, "limit": 10}, "notional": [], "balances": []});
444 [
445 serde_json::json!({"type": "MakerCapsUpdated", "data": {"caps_snapshot": caps, "reason": "lifecycle"}}),
446 serde_json::json!({"type": "PositionUpdated", "data": {
447 "position": {"pda": "p", "market": "m", "underlying_mint": "u", "quote_mint": "q",
448 "position_type": "covered_call", "status": "open", "strike": 1, "quantity": 2,
449 "price": 3, "created_at": 1, "expiry_ts": 2},
450 "update_type": "created", "caps_snapshot": caps
451 }}),
452 serde_json::json!({"type": "TradeExecuted", "data": {"trade": {
453 "id": "00000000-0000-0000-0000-000000000001", "market": "m", "position_type": "covered_call",
454 "strike": 1, "quantity": 2, "price": 3, "taker": "t", "maker": "o", "tx_signature": "s", "executed_at": 1
455 }}}),
456 ].into_iter().map(|value| serde_json::from_value(value).unwrap()).collect()
457 }
458}