1#![allow(unused_imports)]
15use async_trait::async_trait;
16use derive_builder::Builder;
17use reqwest;
18use rust_decimal::prelude::*;
19use serde::{Deserialize, Serialize};
20use serde_json::{Value, json};
21use std::collections::BTreeMap;
22
23use crate::common::{
24 config::ConfigurationRestApi,
25 models::{ParamBuildError, RestApiResponse},
26 utils::send_request,
27};
28use crate::convert::rest_api::models;
29
30const HAS_TIME_UNIT: bool = false;
31
32#[async_trait]
33pub trait TradeApi: Send + Sync {
34 async fn accept_quote(
35 &self,
36 params: AcceptQuoteParams,
37 ) -> anyhow::Result<RestApiResponse<models::AcceptQuoteResponse>>;
38 async fn cancel_limit_order(
39 &self,
40 params: CancelLimitOrderParams,
41 ) -> anyhow::Result<RestApiResponse<models::CancelLimitOrderResponse>>;
42 async fn get_convert_trade_history(
43 &self,
44 params: GetConvertTradeHistoryParams,
45 ) -> anyhow::Result<RestApiResponse<models::GetConvertTradeHistoryResponse>>;
46 async fn order_status(
47 &self,
48 params: OrderStatusParams,
49 ) -> anyhow::Result<RestApiResponse<models::OrderStatusResponse>>;
50 async fn place_limit_order(
51 &self,
52 params: PlaceLimitOrderParams,
53 ) -> anyhow::Result<RestApiResponse<models::PlaceLimitOrderResponse>>;
54 async fn query_limit_open_orders(
55 &self,
56 params: QueryLimitOpenOrdersParams,
57 ) -> anyhow::Result<RestApiResponse<models::QueryLimitOpenOrdersResponse>>;
58 async fn send_quote_request(
59 &self,
60 params: SendQuoteRequestParams,
61 ) -> anyhow::Result<RestApiResponse<models::SendQuoteRequestResponse>>;
62}
63
64#[derive(Debug, Clone)]
65pub struct TradeApiClient {
66 configuration: ConfigurationRestApi,
67}
68
69impl TradeApiClient {
70 pub fn new(configuration: ConfigurationRestApi) -> Self {
71 Self { configuration }
72 }
73}
74
75#[derive(Clone, Debug, Builder)]
80#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
81pub struct AcceptQuoteParams {
82 #[builder(setter(into))]
87 pub quote_id: String,
88 #[builder(setter(into), default)]
92 pub recv_window: Option<i64>,
93}
94
95impl AcceptQuoteParams {
96 #[must_use]
103 pub fn builder(quote_id: String) -> AcceptQuoteParamsBuilder {
104 AcceptQuoteParamsBuilder::default().quote_id(quote_id)
105 }
106}
107#[derive(Clone, Debug, Builder)]
112#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
113pub struct CancelLimitOrderParams {
114 #[builder(setter(into))]
118 pub order_id: i64,
119 #[builder(setter(into), default)]
123 pub recv_window: Option<i64>,
124}
125
126impl CancelLimitOrderParams {
127 #[must_use]
134 pub fn builder(order_id: i64) -> CancelLimitOrderParamsBuilder {
135 CancelLimitOrderParamsBuilder::default().order_id(order_id)
136 }
137}
138#[derive(Clone, Debug, Builder)]
143#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
144pub struct GetConvertTradeHistoryParams {
145 #[builder(setter(into))]
150 pub start_time: i64,
151 #[builder(setter(into))]
156 pub end_time: i64,
157 #[builder(setter(into), default)]
161 pub limit: Option<i64>,
162 #[builder(setter(into), default)]
166 pub recv_window: Option<i64>,
167}
168
169impl GetConvertTradeHistoryParams {
170 #[must_use]
178 pub fn builder(start_time: i64, end_time: i64) -> GetConvertTradeHistoryParamsBuilder {
179 GetConvertTradeHistoryParamsBuilder::default()
180 .start_time(start_time)
181 .end_time(end_time)
182 }
183}
184#[derive(Clone, Debug, Builder, Default)]
189#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
190pub struct OrderStatusParams {
191 #[builder(setter(into), default)]
195 pub order_id: Option<String>,
196 #[builder(setter(into), default)]
200 pub quote_id: Option<String>,
201}
202
203impl OrderStatusParams {
204 #[must_use]
207 pub fn builder() -> OrderStatusParamsBuilder {
208 OrderStatusParamsBuilder::default()
209 }
210}
211#[derive(Clone, Debug, Builder)]
216#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
217pub struct PlaceLimitOrderParams {
218 #[builder(setter(into))]
222 pub base_asset: String,
223 #[builder(setter(into))]
227 pub quote_asset: String,
228 #[builder(setter(into))]
232 pub limit_price: rust_decimal::Decimal,
233 #[builder(setter(into))]
237 pub side: String,
238 #[builder(setter(into))]
242 pub expired_type: String,
243 #[builder(setter(into), default)]
247 pub base_amount: Option<rust_decimal::Decimal>,
248 #[builder(setter(into), default)]
252 pub quote_amount: Option<rust_decimal::Decimal>,
253 #[builder(setter(into), default)]
257 pub wallet_type: Option<String>,
258 #[builder(setter(into), default)]
262 pub recv_window: Option<i64>,
263}
264
265impl PlaceLimitOrderParams {
266 #[must_use]
277 pub fn builder(
278 base_asset: String,
279 quote_asset: String,
280 limit_price: rust_decimal::Decimal,
281 side: String,
282 expired_type: String,
283 ) -> PlaceLimitOrderParamsBuilder {
284 PlaceLimitOrderParamsBuilder::default()
285 .base_asset(base_asset)
286 .quote_asset(quote_asset)
287 .limit_price(limit_price)
288 .side(side)
289 .expired_type(expired_type)
290 }
291}
292#[derive(Clone, Debug, Builder, Default)]
297#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
298pub struct QueryLimitOpenOrdersParams {
299 #[builder(setter(into), default)]
303 pub recv_window: Option<i64>,
304}
305
306impl QueryLimitOpenOrdersParams {
307 #[must_use]
310 pub fn builder() -> QueryLimitOpenOrdersParamsBuilder {
311 QueryLimitOpenOrdersParamsBuilder::default()
312 }
313}
314#[derive(Clone, Debug, Builder)]
319#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
320pub struct SendQuoteRequestParams {
321 #[builder(setter(into))]
326 pub from_asset: String,
327 #[builder(setter(into))]
332 pub to_asset: String,
333 #[builder(setter(into), default)]
337 pub from_amount: Option<rust_decimal::Decimal>,
338 #[builder(setter(into), default)]
342 pub to_amount: Option<rust_decimal::Decimal>,
343 #[builder(setter(into), default)]
347 pub wallet_type: Option<String>,
348 #[builder(setter(into), default)]
352 pub valid_time: Option<String>,
353 #[builder(setter(into), default)]
357 pub recv_window: Option<i64>,
358}
359
360impl SendQuoteRequestParams {
361 #[must_use]
369 pub fn builder(from_asset: String, to_asset: String) -> SendQuoteRequestParamsBuilder {
370 SendQuoteRequestParamsBuilder::default()
371 .from_asset(from_asset)
372 .to_asset(to_asset)
373 }
374}
375
376#[async_trait]
377impl TradeApi for TradeApiClient {
378 async fn accept_quote(
379 &self,
380 params: AcceptQuoteParams,
381 ) -> anyhow::Result<RestApiResponse<models::AcceptQuoteResponse>> {
382 let AcceptQuoteParams {
383 quote_id,
384 recv_window,
385 } = params;
386
387 let mut query_params = BTreeMap::new();
388 let body_params = BTreeMap::new();
389
390 query_params.insert("quoteId".to_string(), json!(quote_id));
391
392 if let Some(rw) = recv_window {
393 query_params.insert("recvWindow".to_string(), json!(rw));
394 }
395
396 send_request::<models::AcceptQuoteResponse>(
397 &self.configuration,
398 "/sapi/v1/convert/acceptQuote",
399 reqwest::Method::POST,
400 query_params,
401 body_params,
402 if HAS_TIME_UNIT {
403 self.configuration.time_unit
404 } else {
405 None
406 },
407 true,
408 )
409 .await
410 }
411
412 async fn cancel_limit_order(
413 &self,
414 params: CancelLimitOrderParams,
415 ) -> anyhow::Result<RestApiResponse<models::CancelLimitOrderResponse>> {
416 let CancelLimitOrderParams {
417 order_id,
418 recv_window,
419 } = params;
420
421 let mut query_params = BTreeMap::new();
422 let body_params = BTreeMap::new();
423
424 query_params.insert("orderId".to_string(), json!(order_id));
425
426 if let Some(rw) = recv_window {
427 query_params.insert("recvWindow".to_string(), json!(rw));
428 }
429
430 send_request::<models::CancelLimitOrderResponse>(
431 &self.configuration,
432 "/sapi/v1/convert/limit/cancelOrder",
433 reqwest::Method::POST,
434 query_params,
435 body_params,
436 if HAS_TIME_UNIT {
437 self.configuration.time_unit
438 } else {
439 None
440 },
441 true,
442 )
443 .await
444 }
445
446 async fn get_convert_trade_history(
447 &self,
448 params: GetConvertTradeHistoryParams,
449 ) -> anyhow::Result<RestApiResponse<models::GetConvertTradeHistoryResponse>> {
450 let GetConvertTradeHistoryParams {
451 start_time,
452 end_time,
453 limit,
454 recv_window,
455 } = params;
456
457 let mut query_params = BTreeMap::new();
458 let body_params = BTreeMap::new();
459
460 query_params.insert("startTime".to_string(), json!(start_time));
461
462 query_params.insert("endTime".to_string(), json!(end_time));
463
464 if let Some(rw) = limit {
465 query_params.insert("limit".to_string(), json!(rw));
466 }
467
468 if let Some(rw) = recv_window {
469 query_params.insert("recvWindow".to_string(), json!(rw));
470 }
471
472 send_request::<models::GetConvertTradeHistoryResponse>(
473 &self.configuration,
474 "/sapi/v1/convert/tradeFlow",
475 reqwest::Method::GET,
476 query_params,
477 body_params,
478 if HAS_TIME_UNIT {
479 self.configuration.time_unit
480 } else {
481 None
482 },
483 true,
484 )
485 .await
486 }
487
488 async fn order_status(
489 &self,
490 params: OrderStatusParams,
491 ) -> anyhow::Result<RestApiResponse<models::OrderStatusResponse>> {
492 let OrderStatusParams { order_id, quote_id } = params;
493
494 let mut query_params = BTreeMap::new();
495 let body_params = BTreeMap::new();
496
497 if let Some(rw) = order_id {
498 query_params.insert("orderId".to_string(), json!(rw));
499 }
500
501 if let Some(rw) = quote_id {
502 query_params.insert("quoteId".to_string(), json!(rw));
503 }
504
505 send_request::<models::OrderStatusResponse>(
506 &self.configuration,
507 "/sapi/v1/convert/orderStatus",
508 reqwest::Method::GET,
509 query_params,
510 body_params,
511 if HAS_TIME_UNIT {
512 self.configuration.time_unit
513 } else {
514 None
515 },
516 true,
517 )
518 .await
519 }
520
521 async fn place_limit_order(
522 &self,
523 params: PlaceLimitOrderParams,
524 ) -> anyhow::Result<RestApiResponse<models::PlaceLimitOrderResponse>> {
525 let PlaceLimitOrderParams {
526 base_asset,
527 quote_asset,
528 limit_price,
529 side,
530 expired_type,
531 base_amount,
532 quote_amount,
533 wallet_type,
534 recv_window,
535 } = params;
536
537 let mut query_params = BTreeMap::new();
538 let body_params = BTreeMap::new();
539
540 query_params.insert("baseAsset".to_string(), json!(base_asset));
541
542 query_params.insert("quoteAsset".to_string(), json!(quote_asset));
543
544 query_params.insert("limitPrice".to_string(), json!(limit_price));
545
546 if let Some(rw) = base_amount {
547 query_params.insert("baseAmount".to_string(), json!(rw));
548 }
549
550 if let Some(rw) = quote_amount {
551 query_params.insert("quoteAmount".to_string(), json!(rw));
552 }
553
554 query_params.insert("side".to_string(), json!(side));
555
556 if let Some(rw) = wallet_type {
557 query_params.insert("walletType".to_string(), json!(rw));
558 }
559
560 query_params.insert("expiredType".to_string(), json!(expired_type));
561
562 if let Some(rw) = recv_window {
563 query_params.insert("recvWindow".to_string(), json!(rw));
564 }
565
566 send_request::<models::PlaceLimitOrderResponse>(
567 &self.configuration,
568 "/sapi/v1/convert/limit/placeOrder",
569 reqwest::Method::POST,
570 query_params,
571 body_params,
572 if HAS_TIME_UNIT {
573 self.configuration.time_unit
574 } else {
575 None
576 },
577 true,
578 )
579 .await
580 }
581
582 async fn query_limit_open_orders(
583 &self,
584 params: QueryLimitOpenOrdersParams,
585 ) -> anyhow::Result<RestApiResponse<models::QueryLimitOpenOrdersResponse>> {
586 let QueryLimitOpenOrdersParams { recv_window } = params;
587
588 let mut query_params = BTreeMap::new();
589 let body_params = BTreeMap::new();
590
591 if let Some(rw) = recv_window {
592 query_params.insert("recvWindow".to_string(), json!(rw));
593 }
594
595 send_request::<models::QueryLimitOpenOrdersResponse>(
596 &self.configuration,
597 "/sapi/v1/convert/limit/queryOpenOrders",
598 reqwest::Method::GET,
599 query_params,
600 body_params,
601 if HAS_TIME_UNIT {
602 self.configuration.time_unit
603 } else {
604 None
605 },
606 true,
607 )
608 .await
609 }
610
611 async fn send_quote_request(
612 &self,
613 params: SendQuoteRequestParams,
614 ) -> anyhow::Result<RestApiResponse<models::SendQuoteRequestResponse>> {
615 let SendQuoteRequestParams {
616 from_asset,
617 to_asset,
618 from_amount,
619 to_amount,
620 wallet_type,
621 valid_time,
622 recv_window,
623 } = params;
624
625 let mut query_params = BTreeMap::new();
626 let body_params = BTreeMap::new();
627
628 query_params.insert("fromAsset".to_string(), json!(from_asset));
629
630 query_params.insert("toAsset".to_string(), json!(to_asset));
631
632 if let Some(rw) = from_amount {
633 query_params.insert("fromAmount".to_string(), json!(rw));
634 }
635
636 if let Some(rw) = to_amount {
637 query_params.insert("toAmount".to_string(), json!(rw));
638 }
639
640 if let Some(rw) = wallet_type {
641 query_params.insert("walletType".to_string(), json!(rw));
642 }
643
644 if let Some(rw) = valid_time {
645 query_params.insert("validTime".to_string(), json!(rw));
646 }
647
648 if let Some(rw) = recv_window {
649 query_params.insert("recvWindow".to_string(), json!(rw));
650 }
651
652 send_request::<models::SendQuoteRequestResponse>(
653 &self.configuration,
654 "/sapi/v1/convert/getQuote",
655 reqwest::Method::POST,
656 query_params,
657 body_params,
658 if HAS_TIME_UNIT {
659 self.configuration.time_unit
660 } else {
661 None
662 },
663 true,
664 )
665 .await
666 }
667}
668
669#[cfg(all(test, feature = "convert"))]
670mod tests {
671 use super::*;
672 use crate::TOKIO_SHARED_RT;
673 use crate::{errors::ConnectorError, models::DataFuture, models::RestApiRateLimit};
674 use async_trait::async_trait;
675 use std::collections::HashMap;
676
677 struct DummyRestApiResponse<T> {
678 inner: Box<dyn FnOnce() -> DataFuture<Result<T, ConnectorError>> + Send + Sync>,
679 status: u16,
680 headers: HashMap<String, String>,
681 rate_limits: Option<Vec<RestApiRateLimit>>,
682 }
683
684 impl<T> From<DummyRestApiResponse<T>> for RestApiResponse<T> {
685 fn from(dummy: DummyRestApiResponse<T>) -> Self {
686 Self {
687 data_fn: dummy.inner,
688 status: dummy.status,
689 headers: dummy.headers,
690 rate_limits: dummy.rate_limits,
691 }
692 }
693 }
694
695 struct MockTradeApiClient {
696 force_error: bool,
697 }
698
699 #[async_trait]
700 impl TradeApi for MockTradeApiClient {
701 async fn accept_quote(
702 &self,
703 _params: AcceptQuoteParams,
704 ) -> anyhow::Result<RestApiResponse<models::AcceptQuoteResponse>> {
705 if self.force_error {
706 return Err(ConnectorError::ConnectorClientError {
707 msg: "ResponseError".to_string(),
708 code: None,
709 }
710 .into());
711 }
712
713 let resp_json: Value = serde_json::from_str(r#"{"orderId":"933256278426274426","createTime":1623381330472,"orderStatus":"PROCESS"}"#).unwrap();
714 let dummy_response: models::AcceptQuoteResponse =
715 serde_json::from_value(resp_json.clone())
716 .expect("should parse into models::AcceptQuoteResponse");
717
718 let dummy = DummyRestApiResponse {
719 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
720 status: 200,
721 headers: HashMap::new(),
722 rate_limits: None,
723 };
724
725 Ok(dummy.into())
726 }
727
728 async fn cancel_limit_order(
729 &self,
730 _params: CancelLimitOrderParams,
731 ) -> anyhow::Result<RestApiResponse<models::CancelLimitOrderResponse>> {
732 if self.force_error {
733 return Err(ConnectorError::ConnectorClientError {
734 msg: "ResponseError".to_string(),
735 code: None,
736 }
737 .into());
738 }
739
740 let resp_json: Value =
741 serde_json::from_str(r#"{"orderId":1603680255057330400,"status":"CANCELED"}"#)
742 .unwrap();
743 let dummy_response: models::CancelLimitOrderResponse =
744 serde_json::from_value(resp_json.clone())
745 .expect("should parse into models::CancelLimitOrderResponse");
746
747 let dummy = DummyRestApiResponse {
748 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
749 status: 200,
750 headers: HashMap::new(),
751 rate_limits: None,
752 };
753
754 Ok(dummy.into())
755 }
756
757 async fn get_convert_trade_history(
758 &self,
759 _params: GetConvertTradeHistoryParams,
760 ) -> anyhow::Result<RestApiResponse<models::GetConvertTradeHistoryResponse>> {
761 if self.force_error {
762 return Err(ConnectorError::ConnectorClientError {
763 msg: "ResponseError".to_string(),
764 code: None,
765 }
766 .into());
767 }
768
769 let resp_json: Value = serde_json::from_str(r#"{"list":[{"quoteId":"f3b91c525b2644c7bc1e1cd31b6e1aa6","orderId":940708407462087200,"orderStatus":"SUCCESS","fromAsset":"USDT","fromAmount":"20","toAsset":"BNB","toAmount":"0.06154036","ratio":"0.00307702","inverseRatio":"324.99","createTime":1624248872184}],"startTime":1623824139000,"endTime":1626416139000,"limit":100,"moreData":false}"#).unwrap();
770 let dummy_response: models::GetConvertTradeHistoryResponse =
771 serde_json::from_value(resp_json.clone())
772 .expect("should parse into models::GetConvertTradeHistoryResponse");
773
774 let dummy = DummyRestApiResponse {
775 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
776 status: 200,
777 headers: HashMap::new(),
778 rate_limits: None,
779 };
780
781 Ok(dummy.into())
782 }
783
784 async fn order_status(
785 &self,
786 _params: OrderStatusParams,
787 ) -> anyhow::Result<RestApiResponse<models::OrderStatusResponse>> {
788 if self.force_error {
789 return Err(ConnectorError::ConnectorClientError {
790 msg: "ResponseError".to_string(),
791 code: None,
792 }
793 .into());
794 }
795
796 let resp_json: Value = serde_json::from_str(r#"{"orderId":933256278426274400,"orderStatus":"SUCCESS","fromAsset":"BTC","fromAmount":"0.00054414","toAsset":"USDT","toAmount":"20","ratio":"36755","inverseRatio":"0.00002721","createTime":1623381330472}"#).unwrap();
797 let dummy_response: models::OrderStatusResponse =
798 serde_json::from_value(resp_json.clone())
799 .expect("should parse into models::OrderStatusResponse");
800
801 let dummy = DummyRestApiResponse {
802 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
803 status: 200,
804 headers: HashMap::new(),
805 rate_limits: None,
806 };
807
808 Ok(dummy.into())
809 }
810
811 async fn place_limit_order(
812 &self,
813 _params: PlaceLimitOrderParams,
814 ) -> anyhow::Result<RestApiResponse<models::PlaceLimitOrderResponse>> {
815 if self.force_error {
816 return Err(ConnectorError::ConnectorClientError {
817 msg: "ResponseError".to_string(),
818 code: None,
819 }
820 .into());
821 }
822
823 let resp_json: Value =
824 serde_json::from_str(r#"{"orderId":1603680255057330400,"status":"PROCESS"}"#)
825 .unwrap();
826 let dummy_response: models::PlaceLimitOrderResponse =
827 serde_json::from_value(resp_json.clone())
828 .expect("should parse into models::PlaceLimitOrderResponse");
829
830 let dummy = DummyRestApiResponse {
831 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
832 status: 200,
833 headers: HashMap::new(),
834 rate_limits: None,
835 };
836
837 Ok(dummy.into())
838 }
839
840 async fn query_limit_open_orders(
841 &self,
842 _params: QueryLimitOpenOrdersParams,
843 ) -> anyhow::Result<RestApiResponse<models::QueryLimitOpenOrdersResponse>> {
844 if self.force_error {
845 return Err(ConnectorError::ConnectorClientError {
846 msg: "ResponseError".to_string(),
847 code: None,
848 }
849 .into());
850 }
851
852 let resp_json: Value = serde_json::from_str(r#"{"list":[{"quoteId":"18sdf87kh9df","orderId":1150901289839,"orderStatus":"SUCCESS","fromAsset":"BNB","fromAmount":"10","toAsset":"USDT","toAmount":"2317.89","ratio":"231.789","inverseRatio":"0.00431427","createTime":1614089498000,"expiredTimestamp":1614099498000}]}"#).unwrap();
853 let dummy_response: models::QueryLimitOpenOrdersResponse =
854 serde_json::from_value(resp_json.clone())
855 .expect("should parse into models::QueryLimitOpenOrdersResponse");
856
857 let dummy = DummyRestApiResponse {
858 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
859 status: 200,
860 headers: HashMap::new(),
861 rate_limits: None,
862 };
863
864 Ok(dummy.into())
865 }
866
867 async fn send_quote_request(
868 &self,
869 _params: SendQuoteRequestParams,
870 ) -> anyhow::Result<RestApiResponse<models::SendQuoteRequestResponse>> {
871 if self.force_error {
872 return Err(ConnectorError::ConnectorClientError {
873 msg: "ResponseError".to_string(),
874 code: None,
875 }
876 .into());
877 }
878
879 let resp_json: Value = serde_json::from_str(r#"{"quoteId":"12415572564","ratio":"38163.7","inverseRatio":"0.0000262","validTimestamp":1623319461670,"toAmount":"3816.37","fromAmount":"0.1"}"#).unwrap();
880 let dummy_response: models::SendQuoteRequestResponse =
881 serde_json::from_value(resp_json.clone())
882 .expect("should parse into models::SendQuoteRequestResponse");
883
884 let dummy = DummyRestApiResponse {
885 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
886 status: 200,
887 headers: HashMap::new(),
888 rate_limits: None,
889 };
890
891 Ok(dummy.into())
892 }
893 }
894
895 #[test]
896 fn accept_quote_required_params_success() {
897 TOKIO_SHARED_RT.block_on(async {
898 let client = MockTradeApiClient { force_error: false };
899
900 let params = AcceptQuoteParams::builder("1".to_string(),).build().unwrap();
901
902 let resp_json: Value = serde_json::from_str(r#"{"orderId":"933256278426274426","createTime":1623381330472,"orderStatus":"PROCESS"}"#).unwrap();
903 let expected_response : models::AcceptQuoteResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::AcceptQuoteResponse");
904
905 let resp = client.accept_quote(params).await.expect("Expected a response");
906 let data_future = resp.data();
907 let actual_response = data_future.await.unwrap();
908 assert_eq!(actual_response, expected_response);
909 });
910 }
911
912 #[test]
913 fn accept_quote_optional_params_success() {
914 TOKIO_SHARED_RT.block_on(async {
915 let client = MockTradeApiClient { force_error: false };
916
917 let params = AcceptQuoteParams::builder("1".to_string(),).recv_window(5000).build().unwrap();
918
919 let resp_json: Value = serde_json::from_str(r#"{"orderId":"933256278426274426","createTime":1623381330472,"orderStatus":"PROCESS"}"#).unwrap();
920 let expected_response : models::AcceptQuoteResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::AcceptQuoteResponse");
921
922 let resp = client.accept_quote(params).await.expect("Expected a response");
923 let data_future = resp.data();
924 let actual_response = data_future.await.unwrap();
925 assert_eq!(actual_response, expected_response);
926 });
927 }
928
929 #[test]
930 fn accept_quote_response_error() {
931 TOKIO_SHARED_RT.block_on(async {
932 let client = MockTradeApiClient { force_error: true };
933
934 let params = AcceptQuoteParams::builder("1".to_string()).build().unwrap();
935
936 match client.accept_quote(params).await {
937 Ok(_) => panic!("Expected an error"),
938 Err(err) => {
939 assert_eq!(err.to_string(), "Connector client error: ResponseError");
940 }
941 }
942 });
943 }
944
945 #[test]
946 fn cancel_limit_order_required_params_success() {
947 TOKIO_SHARED_RT.block_on(async {
948 let client = MockTradeApiClient { force_error: false };
949
950 let params = CancelLimitOrderParams::builder(1).build().unwrap();
951
952 let resp_json: Value =
953 serde_json::from_str(r#"{"orderId":1603680255057330400,"status":"CANCELED"}"#)
954 .unwrap();
955 let expected_response: models::CancelLimitOrderResponse =
956 serde_json::from_value(resp_json.clone())
957 .expect("should parse into models::CancelLimitOrderResponse");
958
959 let resp = client
960 .cancel_limit_order(params)
961 .await
962 .expect("Expected a response");
963 let data_future = resp.data();
964 let actual_response = data_future.await.unwrap();
965 assert_eq!(actual_response, expected_response);
966 });
967 }
968
969 #[test]
970 fn cancel_limit_order_optional_params_success() {
971 TOKIO_SHARED_RT.block_on(async {
972 let client = MockTradeApiClient { force_error: false };
973
974 let params = CancelLimitOrderParams::builder(1)
975 .recv_window(5000)
976 .build()
977 .unwrap();
978
979 let resp_json: Value =
980 serde_json::from_str(r#"{"orderId":1603680255057330400,"status":"CANCELED"}"#)
981 .unwrap();
982 let expected_response: models::CancelLimitOrderResponse =
983 serde_json::from_value(resp_json.clone())
984 .expect("should parse into models::CancelLimitOrderResponse");
985
986 let resp = client
987 .cancel_limit_order(params)
988 .await
989 .expect("Expected a response");
990 let data_future = resp.data();
991 let actual_response = data_future.await.unwrap();
992 assert_eq!(actual_response, expected_response);
993 });
994 }
995
996 #[test]
997 fn cancel_limit_order_response_error() {
998 TOKIO_SHARED_RT.block_on(async {
999 let client = MockTradeApiClient { force_error: true };
1000
1001 let params = CancelLimitOrderParams::builder(1).build().unwrap();
1002
1003 match client.cancel_limit_order(params).await {
1004 Ok(_) => panic!("Expected an error"),
1005 Err(err) => {
1006 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1007 }
1008 }
1009 });
1010 }
1011
1012 #[test]
1013 fn get_convert_trade_history_required_params_success() {
1014 TOKIO_SHARED_RT.block_on(async {
1015 let client = MockTradeApiClient { force_error: false };
1016
1017 let params = GetConvertTradeHistoryParams::builder(1623319461670,1641782889000,).build().unwrap();
1018
1019 let resp_json: Value = serde_json::from_str(r#"{"list":[{"quoteId":"f3b91c525b2644c7bc1e1cd31b6e1aa6","orderId":940708407462087200,"orderStatus":"SUCCESS","fromAsset":"USDT","fromAmount":"20","toAsset":"BNB","toAmount":"0.06154036","ratio":"0.00307702","inverseRatio":"324.99","createTime":1624248872184}],"startTime":1623824139000,"endTime":1626416139000,"limit":100,"moreData":false}"#).unwrap();
1020 let expected_response : models::GetConvertTradeHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::GetConvertTradeHistoryResponse");
1021
1022 let resp = client.get_convert_trade_history(params).await.expect("Expected a response");
1023 let data_future = resp.data();
1024 let actual_response = data_future.await.unwrap();
1025 assert_eq!(actual_response, expected_response);
1026 });
1027 }
1028
1029 #[test]
1030 fn get_convert_trade_history_optional_params_success() {
1031 TOKIO_SHARED_RT.block_on(async {
1032 let client = MockTradeApiClient { force_error: false };
1033
1034 let params = GetConvertTradeHistoryParams::builder(1623319461670,1641782889000,).limit(100).recv_window(5000).build().unwrap();
1035
1036 let resp_json: Value = serde_json::from_str(r#"{"list":[{"quoteId":"f3b91c525b2644c7bc1e1cd31b6e1aa6","orderId":940708407462087200,"orderStatus":"SUCCESS","fromAsset":"USDT","fromAmount":"20","toAsset":"BNB","toAmount":"0.06154036","ratio":"0.00307702","inverseRatio":"324.99","createTime":1624248872184}],"startTime":1623824139000,"endTime":1626416139000,"limit":100,"moreData":false}"#).unwrap();
1037 let expected_response : models::GetConvertTradeHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::GetConvertTradeHistoryResponse");
1038
1039 let resp = client.get_convert_trade_history(params).await.expect("Expected a response");
1040 let data_future = resp.data();
1041 let actual_response = data_future.await.unwrap();
1042 assert_eq!(actual_response, expected_response);
1043 });
1044 }
1045
1046 #[test]
1047 fn get_convert_trade_history_response_error() {
1048 TOKIO_SHARED_RT.block_on(async {
1049 let client = MockTradeApiClient { force_error: true };
1050
1051 let params = GetConvertTradeHistoryParams::builder(1623319461670, 1641782889000)
1052 .build()
1053 .unwrap();
1054
1055 match client.get_convert_trade_history(params).await {
1056 Ok(_) => panic!("Expected an error"),
1057 Err(err) => {
1058 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1059 }
1060 }
1061 });
1062 }
1063
1064 #[test]
1065 fn order_status_required_params_success() {
1066 TOKIO_SHARED_RT.block_on(async {
1067 let client = MockTradeApiClient { force_error: false };
1068
1069 let params = OrderStatusParams::builder().build().unwrap();
1070
1071 let resp_json: Value = serde_json::from_str(r#"{"orderId":933256278426274400,"orderStatus":"SUCCESS","fromAsset":"BTC","fromAmount":"0.00054414","toAsset":"USDT","toAmount":"20","ratio":"36755","inverseRatio":"0.00002721","createTime":1623381330472}"#).unwrap();
1072 let expected_response : models::OrderStatusResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::OrderStatusResponse");
1073
1074 let resp = client.order_status(params).await.expect("Expected a response");
1075 let data_future = resp.data();
1076 let actual_response = data_future.await.unwrap();
1077 assert_eq!(actual_response, expected_response);
1078 });
1079 }
1080
1081 #[test]
1082 fn order_status_optional_params_success() {
1083 TOKIO_SHARED_RT.block_on(async {
1084 let client = MockTradeApiClient { force_error: false };
1085
1086 let params = OrderStatusParams::builder().order_id("1".to_string()).quote_id("1".to_string()).build().unwrap();
1087
1088 let resp_json: Value = serde_json::from_str(r#"{"orderId":933256278426274400,"orderStatus":"SUCCESS","fromAsset":"BTC","fromAmount":"0.00054414","toAsset":"USDT","toAmount":"20","ratio":"36755","inverseRatio":"0.00002721","createTime":1623381330472}"#).unwrap();
1089 let expected_response : models::OrderStatusResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::OrderStatusResponse");
1090
1091 let resp = client.order_status(params).await.expect("Expected a response");
1092 let data_future = resp.data();
1093 let actual_response = data_future.await.unwrap();
1094 assert_eq!(actual_response, expected_response);
1095 });
1096 }
1097
1098 #[test]
1099 fn order_status_response_error() {
1100 TOKIO_SHARED_RT.block_on(async {
1101 let client = MockTradeApiClient { force_error: true };
1102
1103 let params = OrderStatusParams::builder().build().unwrap();
1104
1105 match client.order_status(params).await {
1106 Ok(_) => panic!("Expected an error"),
1107 Err(err) => {
1108 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1109 }
1110 }
1111 });
1112 }
1113
1114 #[test]
1115 fn place_limit_order_required_params_success() {
1116 TOKIO_SHARED_RT.block_on(async {
1117 let client = MockTradeApiClient { force_error: false };
1118
1119 let params = PlaceLimitOrderParams::builder(
1120 "base_asset_example".to_string(),
1121 "quote_asset_example".to_string(),
1122 dec!(1.0),
1123 "BUY".to_string(),
1124 "expired_type_example".to_string(),
1125 )
1126 .build()
1127 .unwrap();
1128
1129 let resp_json: Value =
1130 serde_json::from_str(r#"{"orderId":1603680255057330400,"status":"PROCESS"}"#)
1131 .unwrap();
1132 let expected_response: models::PlaceLimitOrderResponse =
1133 serde_json::from_value(resp_json.clone())
1134 .expect("should parse into models::PlaceLimitOrderResponse");
1135
1136 let resp = client
1137 .place_limit_order(params)
1138 .await
1139 .expect("Expected a response");
1140 let data_future = resp.data();
1141 let actual_response = data_future.await.unwrap();
1142 assert_eq!(actual_response, expected_response);
1143 });
1144 }
1145
1146 #[test]
1147 fn place_limit_order_optional_params_success() {
1148 TOKIO_SHARED_RT.block_on(async {
1149 let client = MockTradeApiClient { force_error: false };
1150
1151 let params = PlaceLimitOrderParams::builder(
1152 "base_asset_example".to_string(),
1153 "quote_asset_example".to_string(),
1154 dec!(1.0),
1155 "BUY".to_string(),
1156 "expired_type_example".to_string(),
1157 )
1158 .base_amount(dec!(1.0))
1159 .quote_amount(dec!(1.0))
1160 .wallet_type(String::new())
1161 .recv_window(5000)
1162 .build()
1163 .unwrap();
1164
1165 let resp_json: Value =
1166 serde_json::from_str(r#"{"orderId":1603680255057330400,"status":"PROCESS"}"#)
1167 .unwrap();
1168 let expected_response: models::PlaceLimitOrderResponse =
1169 serde_json::from_value(resp_json.clone())
1170 .expect("should parse into models::PlaceLimitOrderResponse");
1171
1172 let resp = client
1173 .place_limit_order(params)
1174 .await
1175 .expect("Expected a response");
1176 let data_future = resp.data();
1177 let actual_response = data_future.await.unwrap();
1178 assert_eq!(actual_response, expected_response);
1179 });
1180 }
1181
1182 #[test]
1183 fn place_limit_order_response_error() {
1184 TOKIO_SHARED_RT.block_on(async {
1185 let client = MockTradeApiClient { force_error: true };
1186
1187 let params = PlaceLimitOrderParams::builder(
1188 "base_asset_example".to_string(),
1189 "quote_asset_example".to_string(),
1190 dec!(1.0),
1191 "BUY".to_string(),
1192 "expired_type_example".to_string(),
1193 )
1194 .build()
1195 .unwrap();
1196
1197 match client.place_limit_order(params).await {
1198 Ok(_) => panic!("Expected an error"),
1199 Err(err) => {
1200 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1201 }
1202 }
1203 });
1204 }
1205
1206 #[test]
1207 fn query_limit_open_orders_required_params_success() {
1208 TOKIO_SHARED_RT.block_on(async {
1209 let client = MockTradeApiClient { force_error: false };
1210
1211 let params = QueryLimitOpenOrdersParams::builder().build().unwrap();
1212
1213 let resp_json: Value = serde_json::from_str(r#"{"list":[{"quoteId":"18sdf87kh9df","orderId":1150901289839,"orderStatus":"SUCCESS","fromAsset":"BNB","fromAmount":"10","toAsset":"USDT","toAmount":"2317.89","ratio":"231.789","inverseRatio":"0.00431427","createTime":1614089498000,"expiredTimestamp":1614099498000}]}"#).unwrap();
1214 let expected_response : models::QueryLimitOpenOrdersResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::QueryLimitOpenOrdersResponse");
1215
1216 let resp = client.query_limit_open_orders(params).await.expect("Expected a response");
1217 let data_future = resp.data();
1218 let actual_response = data_future.await.unwrap();
1219 assert_eq!(actual_response, expected_response);
1220 });
1221 }
1222
1223 #[test]
1224 fn query_limit_open_orders_optional_params_success() {
1225 TOKIO_SHARED_RT.block_on(async {
1226 let client = MockTradeApiClient { force_error: false };
1227
1228 let params = QueryLimitOpenOrdersParams::builder().recv_window(5000).build().unwrap();
1229
1230 let resp_json: Value = serde_json::from_str(r#"{"list":[{"quoteId":"18sdf87kh9df","orderId":1150901289839,"orderStatus":"SUCCESS","fromAsset":"BNB","fromAmount":"10","toAsset":"USDT","toAmount":"2317.89","ratio":"231.789","inverseRatio":"0.00431427","createTime":1614089498000,"expiredTimestamp":1614099498000}]}"#).unwrap();
1231 let expected_response : models::QueryLimitOpenOrdersResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::QueryLimitOpenOrdersResponse");
1232
1233 let resp = client.query_limit_open_orders(params).await.expect("Expected a response");
1234 let data_future = resp.data();
1235 let actual_response = data_future.await.unwrap();
1236 assert_eq!(actual_response, expected_response);
1237 });
1238 }
1239
1240 #[test]
1241 fn query_limit_open_orders_response_error() {
1242 TOKIO_SHARED_RT.block_on(async {
1243 let client = MockTradeApiClient { force_error: true };
1244
1245 let params = QueryLimitOpenOrdersParams::builder().build().unwrap();
1246
1247 match client.query_limit_open_orders(params).await {
1248 Ok(_) => panic!("Expected an error"),
1249 Err(err) => {
1250 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1251 }
1252 }
1253 });
1254 }
1255
1256 #[test]
1257 fn send_quote_request_required_params_success() {
1258 TOKIO_SHARED_RT.block_on(async {
1259 let client = MockTradeApiClient { force_error: false };
1260
1261 let params = SendQuoteRequestParams::builder("from_asset_example".to_string(),"to_asset_example".to_string(),).build().unwrap();
1262
1263 let resp_json: Value = serde_json::from_str(r#"{"quoteId":"12415572564","ratio":"38163.7","inverseRatio":"0.0000262","validTimestamp":1623319461670,"toAmount":"3816.37","fromAmount":"0.1"}"#).unwrap();
1264 let expected_response : models::SendQuoteRequestResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::SendQuoteRequestResponse");
1265
1266 let resp = client.send_quote_request(params).await.expect("Expected a response");
1267 let data_future = resp.data();
1268 let actual_response = data_future.await.unwrap();
1269 assert_eq!(actual_response, expected_response);
1270 });
1271 }
1272
1273 #[test]
1274 fn send_quote_request_optional_params_success() {
1275 TOKIO_SHARED_RT.block_on(async {
1276 let client = MockTradeApiClient { force_error: false };
1277
1278 let params = SendQuoteRequestParams::builder("from_asset_example".to_string(),"to_asset_example".to_string(),).from_amount(dec!(1.0)).to_amount(dec!(1.0)).wallet_type(String::new()).valid_time("10s".to_string()).recv_window(5000).build().unwrap();
1279
1280 let resp_json: Value = serde_json::from_str(r#"{"quoteId":"12415572564","ratio":"38163.7","inverseRatio":"0.0000262","validTimestamp":1623319461670,"toAmount":"3816.37","fromAmount":"0.1"}"#).unwrap();
1281 let expected_response : models::SendQuoteRequestResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::SendQuoteRequestResponse");
1282
1283 let resp = client.send_quote_request(params).await.expect("Expected a response");
1284 let data_future = resp.data();
1285 let actual_response = data_future.await.unwrap();
1286 assert_eq!(actual_response, expected_response);
1287 });
1288 }
1289
1290 #[test]
1291 fn send_quote_request_response_error() {
1292 TOKIO_SHARED_RT.block_on(async {
1293 let client = MockTradeApiClient { force_error: true };
1294
1295 let params = SendQuoteRequestParams::builder(
1296 "from_asset_example".to_string(),
1297 "to_asset_example".to_string(),
1298 )
1299 .build()
1300 .unwrap();
1301
1302 match client.send_quote_request(params).await {
1303 Ok(_) => panic!("Expected an error"),
1304 Err(err) => {
1305 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1306 }
1307 }
1308 });
1309 }
1310}