1use serde::ser::Error as _;
4
5use crate::error::WebSocketError;
6use crate::model::{quote::*, trading::*, ws_types::JsonRpcRequest};
7
8#[cold]
12#[inline(never)]
13fn serialization_error(msg: impl std::fmt::Display) -> WebSocketError {
14 WebSocketError::Serialization(serde_json::Error::custom(msg))
15}
16
17#[inline]
20fn check_finite(field: &'static str, value: f64) -> Result<(), WebSocketError> {
21 if value.is_finite() {
22 Ok(())
23 } else {
24 Err(serialization_error(format_args!(
25 "field `{field}` must be finite, got {value}"
26 )))
27 }
28}
29
30#[inline]
32fn check_finite_opt(field: &'static str, value: Option<f64>) -> Result<(), WebSocketError> {
33 match value {
34 Some(v) => check_finite(field, v),
35 None => Ok(()),
36 }
37}
38
39#[derive(Debug, Clone)]
41pub struct RequestBuilder {
42 id_counter: u64,
43}
44
45impl Default for RequestBuilder {
46 fn default() -> Self {
47 Self::new()
48 }
49}
50
51impl RequestBuilder {
52 pub fn new() -> Self {
54 Self { id_counter: 1 }
55 }
56
57 pub fn build_request(
59 &mut self,
60 method: &str,
61 params: Option<serde_json::Value>,
62 ) -> JsonRpcRequest {
63 let id = self.id_counter;
64 self.id_counter += 1;
65
66 JsonRpcRequest {
67 jsonrpc: "2.0".to_string(),
68 id: serde_json::Value::Number(serde_json::Number::from(id)),
69 method: method.to_string(),
70 params,
71 }
72 }
73
74 pub fn build_auth_request(&mut self, client_id: &str, client_secret: &str) -> JsonRpcRequest {
76 let params = serde_json::json!({
77 "grant_type": "client_credentials",
78 "client_id": client_id,
79 "client_secret": client_secret
80 });
81
82 self.build_request("public/auth", Some(params))
83 }
84
85 pub fn build_subscribe_request(&mut self, channels: Vec<String>) -> JsonRpcRequest {
87 let params = serde_json::json!({
88 "channels": channels
89 });
90
91 self.build_request("public/subscribe", Some(params))
92 }
93
94 pub fn build_unsubscribe_request(&mut self, channels: Vec<String>) -> JsonRpcRequest {
96 let params = serde_json::json!({
97 "channels": channels
98 });
99
100 self.build_request("public/unsubscribe", Some(params))
101 }
102
103 pub fn build_public_unsubscribe_all_request(&mut self) -> JsonRpcRequest {
111 self.build_request(
112 crate::constants::methods::PUBLIC_UNSUBSCRIBE_ALL,
113 Some(serde_json::json!({})),
114 )
115 }
116
117 pub fn build_private_unsubscribe_all_request(&mut self) -> JsonRpcRequest {
126 self.build_request(
127 crate::constants::methods::PRIVATE_UNSUBSCRIBE_ALL,
128 Some(serde_json::json!({})),
129 )
130 }
131
132 pub fn build_test_request(&mut self) -> JsonRpcRequest {
134 self.build_request(crate::constants::methods::PUBLIC_TEST, None)
135 }
136
137 pub fn build_set_heartbeat_request(&mut self, interval: u64) -> JsonRpcRequest {
150 let params = serde_json::json!({
151 "interval": interval
152 });
153 self.build_request(
154 crate::constants::methods::PUBLIC_SET_HEARTBEAT,
155 Some(params),
156 )
157 }
158
159 pub fn build_disable_heartbeat_request(&mut self) -> JsonRpcRequest {
168 self.build_request(
169 crate::constants::methods::PUBLIC_DISABLE_HEARTBEAT,
170 Some(serde_json::json!({})),
171 )
172 }
173
174 pub fn build_hello_request(
188 &mut self,
189 client_name: &str,
190 client_version: &str,
191 ) -> JsonRpcRequest {
192 let params = serde_json::json!({
193 "client_name": client_name,
194 "client_version": client_version
195 });
196 self.build_request(crate::constants::methods::PUBLIC_HELLO, Some(params))
197 }
198
199 pub fn build_get_time_request(&mut self) -> JsonRpcRequest {
201 self.build_request("public/get_time", None)
202 }
203
204 pub fn build_enable_cancel_on_disconnect_request(&mut self) -> JsonRpcRequest {
214 self.build_request(
215 crate::constants::methods::PRIVATE_ENABLE_CANCEL_ON_DISCONNECT,
216 Some(serde_json::json!({})),
217 )
218 }
219
220 pub fn build_disable_cancel_on_disconnect_request(&mut self) -> JsonRpcRequest {
229 self.build_request(
230 crate::constants::methods::PRIVATE_DISABLE_CANCEL_ON_DISCONNECT,
231 Some(serde_json::json!({})),
232 )
233 }
234
235 pub fn build_get_cancel_on_disconnect_request(&mut self) -> JsonRpcRequest {
243 self.build_request(
244 crate::constants::methods::PRIVATE_GET_CANCEL_ON_DISCONNECT,
245 Some(serde_json::json!({})),
246 )
247 }
248
249 pub fn build_mass_quote_request(
257 &mut self,
258 request: MassQuoteRequest,
259 ) -> Result<JsonRpcRequest, WebSocketError> {
260 for quote in &request.quotes {
261 check_finite("quotes[].amount", quote.amount)?;
262 check_finite("quotes[].price", quote.price)?;
263 }
264 let params = serde_json::to_value(request)?;
265 Ok(self.build_request("private/mass_quote", Some(params)))
266 }
267
268 pub fn build_cancel_quotes_request(
276 &mut self,
277 request: CancelQuotesRequest,
278 ) -> Result<JsonRpcRequest, WebSocketError> {
279 if let Some((min, max)) = request.delta_range {
280 check_finite("delta_range.min", min)?;
281 check_finite("delta_range.max", max)?;
282 }
283 let params = serde_json::to_value(request)?;
284 Ok(self.build_request("private/cancel_quotes", Some(params)))
285 }
286
287 pub fn build_set_mmp_config_request(
297 &mut self,
298 config: MmpGroupConfig,
299 ) -> Result<JsonRpcRequest, WebSocketError> {
300 check_finite("quantity_limit", config.quantity_limit)?;
301 check_finite("delta_limit", config.delta_limit)?;
302
303 let mut params = serde_json::json!({
304 "mmp_group": config.mmp_group,
305 "quantity_limit": config.quantity_limit,
306 "delta_limit": config.delta_limit,
307 "interval": config.interval,
308 "frozen_time": config.frozen_time
309 });
310
311 if config.interval == 0 {
313 params["interval"] = serde_json::Value::Number(serde_json::Number::from(0));
314 }
315
316 Ok(self.build_request("private/set_mmp_config", Some(params)))
317 }
318
319 pub fn build_get_mmp_config_request(&mut self, mmp_group: Option<String>) -> JsonRpcRequest {
321 let params = if let Some(group) = mmp_group {
322 serde_json::json!({
323 "mmp_group": group
324 })
325 } else {
326 serde_json::json!({})
327 };
328
329 self.build_request("private/get_mmp_config", Some(params))
330 }
331
332 pub fn build_reset_mmp_request(&mut self, mmp_group: Option<String>) -> JsonRpcRequest {
334 let params = if let Some(group) = mmp_group {
335 serde_json::json!({
336 "mmp_group": group
337 })
338 } else {
339 serde_json::json!({})
340 };
341
342 self.build_request("private/reset_mmp", Some(params))
343 }
344
345 pub fn build_get_open_orders_request(
347 &mut self,
348 currency: Option<String>,
349 kind: Option<String>,
350 type_filter: Option<String>,
351 ) -> JsonRpcRequest {
352 let mut params = serde_json::json!({});
353
354 if let Some(currency) = currency {
355 params["currency"] = serde_json::Value::String(currency);
356 }
357 if let Some(kind) = kind {
358 params["kind"] = serde_json::Value::String(kind);
359 }
360 if let Some(type_filter) = type_filter {
361 params["type"] = serde_json::Value::String(type_filter);
362 }
363
364 self.build_request("private/get_open_orders", Some(params))
365 }
366
367 pub fn build_buy_request(
375 &mut self,
376 request: &OrderRequest,
377 ) -> Result<JsonRpcRequest, WebSocketError> {
378 check_finite("amount", request.amount)?;
379 check_finite_opt("price", request.price)?;
380 check_finite_opt("max_show", request.max_show)?;
381 check_finite_opt("trigger_price", request.trigger_price)?;
382 let params = serde_json::to_value(request)?;
383 Ok(self.build_request("private/buy", Some(params)))
384 }
385
386 pub fn build_sell_request(
394 &mut self,
395 request: &OrderRequest,
396 ) -> Result<JsonRpcRequest, WebSocketError> {
397 check_finite("amount", request.amount)?;
398 check_finite_opt("price", request.price)?;
399 check_finite_opt("max_show", request.max_show)?;
400 check_finite_opt("trigger_price", request.trigger_price)?;
401 let params = serde_json::to_value(request)?;
402 Ok(self.build_request("private/sell", Some(params)))
403 }
404
405 pub fn build_cancel_request(&mut self, order_id: &str) -> JsonRpcRequest {
407 let params = serde_json::json!({
408 "order_id": order_id
409 });
410
411 self.build_request("private/cancel", Some(params))
412 }
413
414 pub fn build_cancel_all_request(&mut self) -> JsonRpcRequest {
416 self.build_request("private/cancel_all", Some(serde_json::json!({})))
417 }
418
419 pub fn build_cancel_all_by_currency_request(&mut self, currency: &str) -> JsonRpcRequest {
421 let params = serde_json::json!({
422 "currency": currency
423 });
424
425 self.build_request("private/cancel_all_by_currency", Some(params))
426 }
427
428 pub fn build_cancel_all_by_instrument_request(
430 &mut self,
431 instrument_name: &str,
432 ) -> JsonRpcRequest {
433 let params = serde_json::json!({
434 "instrument_name": instrument_name
435 });
436
437 self.build_request("private/cancel_all_by_instrument", Some(params))
438 }
439
440 pub fn build_edit_request(
448 &mut self,
449 request: &EditOrderRequest,
450 ) -> Result<JsonRpcRequest, WebSocketError> {
451 check_finite("amount", request.amount)?;
452 check_finite_opt("price", request.price)?;
453 check_finite_opt("trigger_price", request.trigger_price)?;
454 let params = serde_json::to_value(request)?;
455 Ok(self.build_request("private/edit", Some(params)))
456 }
457
458 pub fn build_get_positions_request(
471 &mut self,
472 currency: Option<&str>,
473 kind: Option<&str>,
474 ) -> JsonRpcRequest {
475 let mut params = serde_json::Map::new();
476
477 if let Some(currency) = currency {
478 params.insert(
479 "currency".to_string(),
480 serde_json::Value::String(currency.to_string()),
481 );
482 }
483
484 if let Some(kind) = kind {
485 params.insert(
486 "kind".to_string(),
487 serde_json::Value::String(kind.to_string()),
488 );
489 }
490
491 if params.is_empty() {
492 self.build_request(crate::constants::methods::PRIVATE_GET_POSITIONS, None)
493 } else {
494 self.build_request(
495 crate::constants::methods::PRIVATE_GET_POSITIONS,
496 Some(serde_json::Value::Object(params)),
497 )
498 }
499 }
500
501 pub fn build_get_account_summary_request(
512 &mut self,
513 currency: &str,
514 extended: Option<bool>,
515 ) -> JsonRpcRequest {
516 let mut params = serde_json::Map::new();
517 params.insert(
518 "currency".to_string(),
519 serde_json::Value::String(currency.to_string()),
520 );
521
522 if let Some(extended) = extended {
523 params.insert("extended".to_string(), serde_json::Value::Bool(extended));
524 }
525
526 self.build_request(
527 crate::constants::methods::PRIVATE_GET_ACCOUNT_SUMMARY,
528 Some(serde_json::Value::Object(params)),
529 )
530 }
531
532 pub fn build_get_order_state_request(&mut self, order_id: &str) -> JsonRpcRequest {
542 let params = serde_json::json!({
543 "order_id": order_id
544 });
545
546 self.build_request(
547 crate::constants::methods::PRIVATE_GET_ORDER_STATE,
548 Some(params),
549 )
550 }
551
552 pub fn build_get_order_history_by_currency_request(
564 &mut self,
565 currency: &str,
566 kind: Option<&str>,
567 count: Option<u32>,
568 ) -> JsonRpcRequest {
569 let mut params = serde_json::Map::new();
570 params.insert(
571 "currency".to_string(),
572 serde_json::Value::String(currency.to_string()),
573 );
574
575 if let Some(kind) = kind {
576 params.insert(
577 "kind".to_string(),
578 serde_json::Value::String(kind.to_string()),
579 );
580 }
581
582 if let Some(count) = count {
583 params.insert(
584 "count".to_string(),
585 serde_json::Value::Number(serde_json::Number::from(count)),
586 );
587 }
588
589 self.build_request(
590 crate::constants::methods::PRIVATE_GET_ORDER_HISTORY_BY_CURRENCY,
591 Some(serde_json::Value::Object(params)),
592 )
593 }
594
595 pub fn build_close_position_request(
614 &mut self,
615 instrument_name: &str,
616 order_type: &str,
617 price: Option<f64>,
618 ) -> Result<JsonRpcRequest, WebSocketError> {
619 let mut params = serde_json::Map::new();
620 params.insert(
621 "instrument_name".to_string(),
622 serde_json::Value::String(instrument_name.to_string()),
623 );
624 params.insert(
625 "type".to_string(),
626 serde_json::Value::String(order_type.to_string()),
627 );
628
629 if let Some(price) = price {
630 check_finite("price", price)?;
631 params.insert("price".to_string(), serde_json::to_value(price)?);
632 }
633
634 Ok(self.build_request(
635 crate::constants::methods::PRIVATE_CLOSE_POSITION,
636 Some(serde_json::Value::Object(params)),
637 ))
638 }
639
640 pub fn build_move_positions_request(
659 &mut self,
660 currency: &str,
661 source_uid: u64,
662 target_uid: u64,
663 trades: &[crate::model::MovePositionTrade],
664 ) -> Result<JsonRpcRequest, WebSocketError> {
665 for trade in trades {
666 check_finite("trades[_].amount", trade.amount)?;
667 check_finite_opt("trades[_].price", trade.price)?;
668 }
669 let trades_json = serde_json::to_value(trades)?;
670
671 let params = serde_json::json!({
672 "currency": currency,
673 "source_uid": source_uid,
674 "target_uid": target_uid,
675 "trades": trades_json
676 });
677
678 Ok(self.build_request(
679 crate::constants::methods::PRIVATE_MOVE_POSITIONS,
680 Some(params),
681 ))
682 }
683}