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::margin_trading::rest_api::models;
29
30const HAS_TIME_UNIT: bool = false;
31
32#[async_trait]
33pub trait BorrowRepayApi: Send + Sync {
34 async fn get_future_hourly_interest_rate(
35 &self,
36 params: GetFutureHourlyInterestRateParams,
37 ) -> anyhow::Result<RestApiResponse<Vec<models::GetFutureHourlyInterestRateResponseInner>>>;
38 async fn get_interest_history(
39 &self,
40 params: GetInterestHistoryParams,
41 ) -> anyhow::Result<RestApiResponse<models::GetInterestHistoryResponse>>;
42 async fn margin_account_borrow_repay(
43 &self,
44 params: MarginAccountBorrowRepayParams,
45 ) -> anyhow::Result<RestApiResponse<models::MarginAccountBorrowRepayResponse>>;
46 async fn query_borrow_repay_records_in_margin_account(
47 &self,
48 params: QueryBorrowRepayRecordsInMarginAccountParams,
49 ) -> anyhow::Result<RestApiResponse<models::QueryBorrowRepayRecordsInMarginAccountResponse>>;
50 async fn query_margin_interest_rate_history(
51 &self,
52 params: QueryMarginInterestRateHistoryParams,
53 ) -> anyhow::Result<RestApiResponse<Vec<models::QueryMarginInterestRateHistoryResponseInner>>>;
54 async fn query_max_borrow(
55 &self,
56 params: QueryMaxBorrowParams,
57 ) -> anyhow::Result<RestApiResponse<models::QueryMaxBorrowResponse>>;
58}
59
60#[derive(Debug, Clone)]
61pub struct BorrowRepayApiClient {
62 configuration: ConfigurationRestApi,
63}
64
65impl BorrowRepayApiClient {
66 pub fn new(configuration: ConfigurationRestApi) -> Self {
67 Self { configuration }
68 }
69}
70
71#[derive(Clone, Debug, Builder)]
76#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
77pub struct GetFutureHourlyInterestRateParams {
78 #[builder(setter(into))]
82 pub assets: String,
83 #[builder(setter(into))]
87 pub is_isolated: bool,
88}
89
90impl GetFutureHourlyInterestRateParams {
91 #[must_use]
99 pub fn builder(assets: String, is_isolated: bool) -> GetFutureHourlyInterestRateParamsBuilder {
100 GetFutureHourlyInterestRateParamsBuilder::default()
101 .assets(assets)
102 .is_isolated(is_isolated)
103 }
104}
105#[derive(Clone, Debug, Builder, Default)]
110#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
111pub struct GetInterestHistoryParams {
112 #[builder(setter(into), default)]
117 pub asset: Option<String>,
118 #[builder(setter(into), default)]
122 pub isolated_symbol: Option<String>,
123 #[builder(setter(into), default)]
127 pub start_time: Option<i64>,
128 #[builder(setter(into), default)]
133 pub end_time: Option<i64>,
134 #[builder(setter(into), default)]
138 pub current: Option<i64>,
139 #[builder(setter(into), default)]
143 pub size: Option<i64>,
144 #[builder(setter(into), default)]
148 pub recv_window: Option<i64>,
149}
150
151impl GetInterestHistoryParams {
152 #[must_use]
155 pub fn builder() -> GetInterestHistoryParamsBuilder {
156 GetInterestHistoryParamsBuilder::default()
157 }
158}
159#[derive(Clone, Debug, Builder)]
164#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
165pub struct MarginAccountBorrowRepayParams {
166 #[builder(setter(into))]
171 pub asset: String,
172 #[builder(setter(into))]
176 pub is_isolated: String,
177 #[builder(setter(into))]
182 pub symbol: String,
183 #[builder(setter(into))]
188 pub amount: String,
189 #[builder(setter(into))]
193 pub r#type: String,
194 #[builder(setter(into), default)]
198 pub recv_window: Option<i64>,
199}
200
201impl MarginAccountBorrowRepayParams {
202 #[must_use]
213 pub fn builder(
214 asset: String,
215 is_isolated: String,
216 symbol: String,
217 amount: String,
218 r#type: String,
219 ) -> MarginAccountBorrowRepayParamsBuilder {
220 MarginAccountBorrowRepayParamsBuilder::default()
221 .asset(asset)
222 .is_isolated(is_isolated)
223 .symbol(symbol)
224 .amount(amount)
225 .r#type(r#type)
226 }
227}
228#[derive(Clone, Debug, Builder)]
233#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
234pub struct QueryBorrowRepayRecordsInMarginAccountParams {
235 #[builder(setter(into))]
239 pub r#type: String,
240 #[builder(setter(into), default)]
245 pub asset: Option<String>,
246 #[builder(setter(into), default)]
250 pub isolated_symbol: Option<String>,
251 #[builder(setter(into), default)]
255 pub tx_id: Option<i64>,
256 #[builder(setter(into), default)]
260 pub start_time: Option<i64>,
261 #[builder(setter(into), default)]
266 pub end_time: Option<i64>,
267 #[builder(setter(into), default)]
271 pub current: Option<i64>,
272 #[builder(setter(into), default)]
276 pub size: Option<i64>,
277 #[builder(setter(into), default)]
281 pub recv_window: Option<i64>,
282}
283
284impl QueryBorrowRepayRecordsInMarginAccountParams {
285 #[must_use]
292 pub fn builder(r#type: String) -> QueryBorrowRepayRecordsInMarginAccountParamsBuilder {
293 QueryBorrowRepayRecordsInMarginAccountParamsBuilder::default().r#type(r#type)
294 }
295}
296#[derive(Clone, Debug, Builder)]
301#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
302pub struct QueryMarginInterestRateHistoryParams {
303 #[builder(setter(into))]
308 pub asset: String,
309 #[builder(setter(into), default)]
313 pub vip_level: Option<i64>,
314 #[builder(setter(into), default)]
318 pub start_time: Option<i64>,
319 #[builder(setter(into), default)]
324 pub end_time: Option<i64>,
325 #[builder(setter(into), default)]
329 pub recv_window: Option<i64>,
330}
331
332impl QueryMarginInterestRateHistoryParams {
333 #[must_use]
340 pub fn builder(asset: String) -> QueryMarginInterestRateHistoryParamsBuilder {
341 QueryMarginInterestRateHistoryParamsBuilder::default().asset(asset)
342 }
343}
344#[derive(Clone, Debug, Builder)]
349#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
350pub struct QueryMaxBorrowParams {
351 #[builder(setter(into))]
356 pub asset: String,
357 #[builder(setter(into), default)]
361 pub isolated_symbol: Option<String>,
362 #[builder(setter(into), default)]
366 pub recv_window: Option<i64>,
367}
368
369impl QueryMaxBorrowParams {
370 #[must_use]
377 pub fn builder(asset: String) -> QueryMaxBorrowParamsBuilder {
378 QueryMaxBorrowParamsBuilder::default().asset(asset)
379 }
380}
381
382#[async_trait]
383impl BorrowRepayApi for BorrowRepayApiClient {
384 async fn get_future_hourly_interest_rate(
385 &self,
386 params: GetFutureHourlyInterestRateParams,
387 ) -> anyhow::Result<RestApiResponse<Vec<models::GetFutureHourlyInterestRateResponseInner>>>
388 {
389 let GetFutureHourlyInterestRateParams {
390 assets,
391 is_isolated,
392 } = params;
393
394 let mut query_params = BTreeMap::new();
395 let body_params = BTreeMap::new();
396
397 query_params.insert("assets".to_string(), json!(assets));
398
399 query_params.insert("isIsolated".to_string(), json!(is_isolated));
400
401 send_request::<Vec<models::GetFutureHourlyInterestRateResponseInner>>(
402 &self.configuration,
403 "/sapi/v1/margin/next-hourly-interest-rate",
404 reqwest::Method::GET,
405 query_params,
406 body_params,
407 if HAS_TIME_UNIT {
408 self.configuration.time_unit
409 } else {
410 None
411 },
412 true,
413 )
414 .await
415 }
416
417 async fn get_interest_history(
418 &self,
419 params: GetInterestHistoryParams,
420 ) -> anyhow::Result<RestApiResponse<models::GetInterestHistoryResponse>> {
421 let GetInterestHistoryParams {
422 asset,
423 isolated_symbol,
424 start_time,
425 end_time,
426 current,
427 size,
428 recv_window,
429 } = params;
430
431 let mut query_params = BTreeMap::new();
432 let body_params = BTreeMap::new();
433
434 if let Some(rw) = asset {
435 query_params.insert("asset".to_string(), json!(rw));
436 }
437
438 if let Some(rw) = isolated_symbol {
439 query_params.insert("isolatedSymbol".to_string(), json!(rw));
440 }
441
442 if let Some(rw) = start_time {
443 query_params.insert("startTime".to_string(), json!(rw));
444 }
445
446 if let Some(rw) = end_time {
447 query_params.insert("endTime".to_string(), json!(rw));
448 }
449
450 if let Some(rw) = current {
451 query_params.insert("current".to_string(), json!(rw));
452 }
453
454 if let Some(rw) = size {
455 query_params.insert("size".to_string(), json!(rw));
456 }
457
458 if let Some(rw) = recv_window {
459 query_params.insert("recvWindow".to_string(), json!(rw));
460 }
461
462 send_request::<models::GetInterestHistoryResponse>(
463 &self.configuration,
464 "/sapi/v1/margin/interestHistory",
465 reqwest::Method::GET,
466 query_params,
467 body_params,
468 if HAS_TIME_UNIT {
469 self.configuration.time_unit
470 } else {
471 None
472 },
473 true,
474 )
475 .await
476 }
477
478 async fn margin_account_borrow_repay(
479 &self,
480 params: MarginAccountBorrowRepayParams,
481 ) -> anyhow::Result<RestApiResponse<models::MarginAccountBorrowRepayResponse>> {
482 let MarginAccountBorrowRepayParams {
483 asset,
484 is_isolated,
485 symbol,
486 amount,
487 r#type,
488 recv_window,
489 } = params;
490
491 let mut query_params = BTreeMap::new();
492 let body_params = BTreeMap::new();
493
494 query_params.insert("asset".to_string(), json!(asset));
495
496 query_params.insert("isIsolated".to_string(), json!(is_isolated));
497
498 query_params.insert("symbol".to_string(), json!(symbol));
499
500 query_params.insert("amount".to_string(), json!(amount));
501
502 query_params.insert("type".to_string(), json!(r#type));
503
504 if let Some(rw) = recv_window {
505 query_params.insert("recvWindow".to_string(), json!(rw));
506 }
507
508 send_request::<models::MarginAccountBorrowRepayResponse>(
509 &self.configuration,
510 "/sapi/v1/margin/borrow-repay",
511 reqwest::Method::POST,
512 query_params,
513 body_params,
514 if HAS_TIME_UNIT {
515 self.configuration.time_unit
516 } else {
517 None
518 },
519 true,
520 )
521 .await
522 }
523
524 async fn query_borrow_repay_records_in_margin_account(
525 &self,
526 params: QueryBorrowRepayRecordsInMarginAccountParams,
527 ) -> anyhow::Result<RestApiResponse<models::QueryBorrowRepayRecordsInMarginAccountResponse>>
528 {
529 let QueryBorrowRepayRecordsInMarginAccountParams {
530 r#type,
531 asset,
532 isolated_symbol,
533 tx_id,
534 start_time,
535 end_time,
536 current,
537 size,
538 recv_window,
539 } = params;
540
541 let mut query_params = BTreeMap::new();
542 let body_params = BTreeMap::new();
543
544 if let Some(rw) = asset {
545 query_params.insert("asset".to_string(), json!(rw));
546 }
547
548 if let Some(rw) = isolated_symbol {
549 query_params.insert("isolatedSymbol".to_string(), json!(rw));
550 }
551
552 if let Some(rw) = tx_id {
553 query_params.insert("txId".to_string(), json!(rw));
554 }
555
556 if let Some(rw) = start_time {
557 query_params.insert("startTime".to_string(), json!(rw));
558 }
559
560 if let Some(rw) = end_time {
561 query_params.insert("endTime".to_string(), json!(rw));
562 }
563
564 if let Some(rw) = current {
565 query_params.insert("current".to_string(), json!(rw));
566 }
567
568 if let Some(rw) = size {
569 query_params.insert("size".to_string(), json!(rw));
570 }
571
572 query_params.insert("type".to_string(), json!(r#type));
573
574 if let Some(rw) = recv_window {
575 query_params.insert("recvWindow".to_string(), json!(rw));
576 }
577
578 send_request::<models::QueryBorrowRepayRecordsInMarginAccountResponse>(
579 &self.configuration,
580 "/sapi/v1/margin/borrow-repay",
581 reqwest::Method::GET,
582 query_params,
583 body_params,
584 if HAS_TIME_UNIT {
585 self.configuration.time_unit
586 } else {
587 None
588 },
589 true,
590 )
591 .await
592 }
593
594 async fn query_margin_interest_rate_history(
595 &self,
596 params: QueryMarginInterestRateHistoryParams,
597 ) -> anyhow::Result<RestApiResponse<Vec<models::QueryMarginInterestRateHistoryResponseInner>>>
598 {
599 let QueryMarginInterestRateHistoryParams {
600 asset,
601 vip_level,
602 start_time,
603 end_time,
604 recv_window,
605 } = params;
606
607 let mut query_params = BTreeMap::new();
608 let body_params = BTreeMap::new();
609
610 query_params.insert("asset".to_string(), json!(asset));
611
612 if let Some(rw) = vip_level {
613 query_params.insert("vipLevel".to_string(), json!(rw));
614 }
615
616 if let Some(rw) = start_time {
617 query_params.insert("startTime".to_string(), json!(rw));
618 }
619
620 if let Some(rw) = end_time {
621 query_params.insert("endTime".to_string(), json!(rw));
622 }
623
624 if let Some(rw) = recv_window {
625 query_params.insert("recvWindow".to_string(), json!(rw));
626 }
627
628 send_request::<Vec<models::QueryMarginInterestRateHistoryResponseInner>>(
629 &self.configuration,
630 "/sapi/v1/margin/interestRateHistory",
631 reqwest::Method::GET,
632 query_params,
633 body_params,
634 if HAS_TIME_UNIT {
635 self.configuration.time_unit
636 } else {
637 None
638 },
639 true,
640 )
641 .await
642 }
643
644 async fn query_max_borrow(
645 &self,
646 params: QueryMaxBorrowParams,
647 ) -> anyhow::Result<RestApiResponse<models::QueryMaxBorrowResponse>> {
648 let QueryMaxBorrowParams {
649 asset,
650 isolated_symbol,
651 recv_window,
652 } = params;
653
654 let mut query_params = BTreeMap::new();
655 let body_params = BTreeMap::new();
656
657 query_params.insert("asset".to_string(), json!(asset));
658
659 if let Some(rw) = isolated_symbol {
660 query_params.insert("isolatedSymbol".to_string(), json!(rw));
661 }
662
663 if let Some(rw) = recv_window {
664 query_params.insert("recvWindow".to_string(), json!(rw));
665 }
666
667 send_request::<models::QueryMaxBorrowResponse>(
668 &self.configuration,
669 "/sapi/v1/margin/maxBorrowable",
670 reqwest::Method::GET,
671 query_params,
672 body_params,
673 if HAS_TIME_UNIT {
674 self.configuration.time_unit
675 } else {
676 None
677 },
678 true,
679 )
680 .await
681 }
682}
683
684#[cfg(all(test, feature = "margin_trading"))]
685mod tests {
686 use super::*;
687 use crate::TOKIO_SHARED_RT;
688 use crate::{errors::ConnectorError, models::DataFuture, models::RestApiRateLimit};
689 use async_trait::async_trait;
690 use std::collections::HashMap;
691
692 struct DummyRestApiResponse<T> {
693 inner: Box<dyn FnOnce() -> DataFuture<Result<T, ConnectorError>> + Send + Sync>,
694 status: u16,
695 headers: HashMap<String, String>,
696 rate_limits: Option<Vec<RestApiRateLimit>>,
697 }
698
699 impl<T> From<DummyRestApiResponse<T>> for RestApiResponse<T> {
700 fn from(dummy: DummyRestApiResponse<T>) -> Self {
701 Self {
702 data_fn: dummy.inner,
703 status: dummy.status,
704 headers: dummy.headers,
705 rate_limits: dummy.rate_limits,
706 }
707 }
708 }
709
710 struct MockBorrowRepayApiClient {
711 force_error: bool,
712 }
713
714 #[async_trait]
715 impl BorrowRepayApi for MockBorrowRepayApiClient {
716 async fn get_future_hourly_interest_rate(
717 &self,
718 _params: GetFutureHourlyInterestRateParams,
719 ) -> anyhow::Result<RestApiResponse<Vec<models::GetFutureHourlyInterestRateResponseInner>>>
720 {
721 if self.force_error {
722 return Err(ConnectorError::ConnectorClientError {
723 msg: "ResponseError".to_string(),
724 code: None,
725 }
726 .into());
727 }
728
729 let resp_json: Value = serde_json::from_str(r#"[{"asset":"BTC","nextHourlyInterestRate":"0.00000571"},{"asset":"ETH","nextHourlyInterestRate":"0.00000578"}]"#).unwrap();
730 let dummy_response: Vec<models::GetFutureHourlyInterestRateResponseInner> =
731 serde_json::from_value(resp_json.clone()).expect(
732 "should parse into Vec<models::GetFutureHourlyInterestRateResponseInner>",
733 );
734
735 let dummy = DummyRestApiResponse {
736 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
737 status: 200,
738 headers: HashMap::new(),
739 rate_limits: None,
740 };
741
742 Ok(dummy.into())
743 }
744
745 async fn get_interest_history(
746 &self,
747 _params: GetInterestHistoryParams,
748 ) -> anyhow::Result<RestApiResponse<models::GetInterestHistoryResponse>> {
749 if self.force_error {
750 return Err(ConnectorError::ConnectorClientError {
751 msg: "ResponseError".to_string(),
752 code: None,
753 }
754 .into());
755 }
756
757 let resp_json: Value = serde_json::from_str(r#"{"rows":[{"txId":1352286576452864800,"interestAccuredTime":1672160400000,"asset":"USDT","rawAsset":"USDT","principal":"45.3313","interest":"0.00024995","interestRate":"0.00013233","type":"ON_BORROW","isolatedSymbol":"BNBUSDT"}],"total":1}"#).unwrap();
758 let dummy_response: models::GetInterestHistoryResponse =
759 serde_json::from_value(resp_json.clone())
760 .expect("should parse into models::GetInterestHistoryResponse");
761
762 let dummy = DummyRestApiResponse {
763 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
764 status: 200,
765 headers: HashMap::new(),
766 rate_limits: None,
767 };
768
769 Ok(dummy.into())
770 }
771
772 async fn margin_account_borrow_repay(
773 &self,
774 _params: MarginAccountBorrowRepayParams,
775 ) -> anyhow::Result<RestApiResponse<models::MarginAccountBorrowRepayResponse>> {
776 if self.force_error {
777 return Err(ConnectorError::ConnectorClientError {
778 msg: "ResponseError".to_string(),
779 code: None,
780 }
781 .into());
782 }
783
784 let resp_json: Value = serde_json::from_str(r#"{"tranId":100000001}"#).unwrap();
785 let dummy_response: models::MarginAccountBorrowRepayResponse =
786 serde_json::from_value(resp_json.clone())
787 .expect("should parse into models::MarginAccountBorrowRepayResponse");
788
789 let dummy = DummyRestApiResponse {
790 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
791 status: 200,
792 headers: HashMap::new(),
793 rate_limits: None,
794 };
795
796 Ok(dummy.into())
797 }
798
799 async fn query_borrow_repay_records_in_margin_account(
800 &self,
801 _params: QueryBorrowRepayRecordsInMarginAccountParams,
802 ) -> anyhow::Result<RestApiResponse<models::QueryBorrowRepayRecordsInMarginAccountResponse>>
803 {
804 if self.force_error {
805 return Err(ConnectorError::ConnectorClientError {
806 msg: "ResponseError".to_string(),
807 code: None,
808 }
809 .into());
810 }
811
812 let resp_json: Value = serde_json::from_str(r#"{"rows":[{"type":"AUTO","isolatedSymbol":"BNBUSDT","amount":"14.00000000","asset":"BNB","interest":"0.01866667","principal":"13.98133333","status":"CONFIRMED","timestamp":1563438204000,"txId":2970933056}],"total":1}"#).unwrap();
813 let dummy_response: models::QueryBorrowRepayRecordsInMarginAccountResponse =
814 serde_json::from_value(resp_json.clone()).expect(
815 "should parse into models::QueryBorrowRepayRecordsInMarginAccountResponse",
816 );
817
818 let dummy = DummyRestApiResponse {
819 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
820 status: 200,
821 headers: HashMap::new(),
822 rate_limits: None,
823 };
824
825 Ok(dummy.into())
826 }
827
828 async fn query_margin_interest_rate_history(
829 &self,
830 _params: QueryMarginInterestRateHistoryParams,
831 ) -> anyhow::Result<RestApiResponse<Vec<models::QueryMarginInterestRateHistoryResponseInner>>>
832 {
833 if self.force_error {
834 return Err(ConnectorError::ConnectorClientError {
835 msg: "ResponseError".to_string(),
836 code: None,
837 }
838 .into());
839 }
840
841 let resp_json: Value = serde_json::from_str(r#"[{"asset":"BTC","dailyInterestRate":"0.00025000","timestamp":1611544731000,"vipLevel":1},{"asset":"BTC","dailyInterestRate":"0.00035000","timestamp":1610248118000,"vipLevel":1}]"#).unwrap();
842 let dummy_response: Vec<models::QueryMarginInterestRateHistoryResponseInner> =
843 serde_json::from_value(resp_json.clone()).expect(
844 "should parse into Vec<models::QueryMarginInterestRateHistoryResponseInner>",
845 );
846
847 let dummy = DummyRestApiResponse {
848 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
849 status: 200,
850 headers: HashMap::new(),
851 rate_limits: None,
852 };
853
854 Ok(dummy.into())
855 }
856
857 async fn query_max_borrow(
858 &self,
859 _params: QueryMaxBorrowParams,
860 ) -> anyhow::Result<RestApiResponse<models::QueryMaxBorrowResponse>> {
861 if self.force_error {
862 return Err(ConnectorError::ConnectorClientError {
863 msg: "ResponseError".to_string(),
864 code: None,
865 }
866 .into());
867 }
868
869 let resp_json: Value =
870 serde_json::from_str(r#"{"amount":"1.69248805","borrowLimit":"60"}"#).unwrap();
871 let dummy_response: models::QueryMaxBorrowResponse =
872 serde_json::from_value(resp_json.clone())
873 .expect("should parse into models::QueryMaxBorrowResponse");
874
875 let dummy = DummyRestApiResponse {
876 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
877 status: 200,
878 headers: HashMap::new(),
879 rate_limits: None,
880 };
881
882 Ok(dummy.into())
883 }
884 }
885
886 #[test]
887 fn get_future_hourly_interest_rate_required_params_success() {
888 TOKIO_SHARED_RT.block_on(async {
889 let client = MockBorrowRepayApiClient { force_error: false };
890
891 let params = GetFutureHourlyInterestRateParams::builder("assets_example".to_string(),false).build().unwrap();
892
893 let resp_json: Value = serde_json::from_str(r#"[{"asset":"BTC","nextHourlyInterestRate":"0.00000571"},{"asset":"ETH","nextHourlyInterestRate":"0.00000578"}]"#).unwrap();
894 let expected_response : Vec<models::GetFutureHourlyInterestRateResponseInner> = serde_json::from_value(resp_json.clone()).expect("should parse into Vec<models::GetFutureHourlyInterestRateResponseInner>");
895
896 let resp = client.get_future_hourly_interest_rate(params).await.expect("Expected a response");
897 let data_future = resp.data();
898 let actual_response = data_future.await.unwrap();
899 assert_eq!(actual_response, expected_response);
900 });
901 }
902
903 #[test]
904 fn get_future_hourly_interest_rate_optional_params_success() {
905 TOKIO_SHARED_RT.block_on(async {
906 let client = MockBorrowRepayApiClient { force_error: false };
907
908 let params = GetFutureHourlyInterestRateParams::builder("assets_example".to_string(),false).build().unwrap();
909
910 let resp_json: Value = serde_json::from_str(r#"[{"asset":"BTC","nextHourlyInterestRate":"0.00000571"},{"asset":"ETH","nextHourlyInterestRate":"0.00000578"}]"#).unwrap();
911 let expected_response : Vec<models::GetFutureHourlyInterestRateResponseInner> = serde_json::from_value(resp_json.clone()).expect("should parse into Vec<models::GetFutureHourlyInterestRateResponseInner>");
912
913 let resp = client.get_future_hourly_interest_rate(params).await.expect("Expected a response");
914 let data_future = resp.data();
915 let actual_response = data_future.await.unwrap();
916 assert_eq!(actual_response, expected_response);
917 });
918 }
919
920 #[test]
921 fn get_future_hourly_interest_rate_response_error() {
922 TOKIO_SHARED_RT.block_on(async {
923 let client = MockBorrowRepayApiClient { force_error: true };
924
925 let params =
926 GetFutureHourlyInterestRateParams::builder("assets_example".to_string(), false)
927 .build()
928 .unwrap();
929
930 match client.get_future_hourly_interest_rate(params).await {
931 Ok(_) => panic!("Expected an error"),
932 Err(err) => {
933 assert_eq!(err.to_string(), "Connector client error: ResponseError");
934 }
935 }
936 });
937 }
938
939 #[test]
940 fn get_interest_history_required_params_success() {
941 TOKIO_SHARED_RT.block_on(async {
942 let client = MockBorrowRepayApiClient { force_error: false };
943
944 let params = GetInterestHistoryParams::builder().build().unwrap();
945
946 let resp_json: Value = serde_json::from_str(r#"{"rows":[{"txId":1352286576452864800,"interestAccuredTime":1672160400000,"asset":"USDT","rawAsset":"USDT","principal":"45.3313","interest":"0.00024995","interestRate":"0.00013233","type":"ON_BORROW","isolatedSymbol":"BNBUSDT"}],"total":1}"#).unwrap();
947 let expected_response : models::GetInterestHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::GetInterestHistoryResponse");
948
949 let resp = client.get_interest_history(params).await.expect("Expected a response");
950 let data_future = resp.data();
951 let actual_response = data_future.await.unwrap();
952 assert_eq!(actual_response, expected_response);
953 });
954 }
955
956 #[test]
957 fn get_interest_history_optional_params_success() {
958 TOKIO_SHARED_RT.block_on(async {
959 let client = MockBorrowRepayApiClient { force_error: false };
960
961 let params = GetInterestHistoryParams::builder().asset("asset_example".to_string()).isolated_symbol("isolated_symbol_example".to_string()).start_time(1623319461670).end_time(1641782889000).current(1).size(10).recv_window(5000).build().unwrap();
962
963 let resp_json: Value = serde_json::from_str(r#"{"rows":[{"txId":1352286576452864800,"interestAccuredTime":1672160400000,"asset":"USDT","rawAsset":"USDT","principal":"45.3313","interest":"0.00024995","interestRate":"0.00013233","type":"ON_BORROW","isolatedSymbol":"BNBUSDT"}],"total":1}"#).unwrap();
964 let expected_response : models::GetInterestHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::GetInterestHistoryResponse");
965
966 let resp = client.get_interest_history(params).await.expect("Expected a response");
967 let data_future = resp.data();
968 let actual_response = data_future.await.unwrap();
969 assert_eq!(actual_response, expected_response);
970 });
971 }
972
973 #[test]
974 fn get_interest_history_response_error() {
975 TOKIO_SHARED_RT.block_on(async {
976 let client = MockBorrowRepayApiClient { force_error: true };
977
978 let params = GetInterestHistoryParams::builder().build().unwrap();
979
980 match client.get_interest_history(params).await {
981 Ok(_) => panic!("Expected an error"),
982 Err(err) => {
983 assert_eq!(err.to_string(), "Connector client error: ResponseError");
984 }
985 }
986 });
987 }
988
989 #[test]
990 fn margin_account_borrow_repay_required_params_success() {
991 TOKIO_SHARED_RT.block_on(async {
992 let client = MockBorrowRepayApiClient { force_error: false };
993
994 let params = MarginAccountBorrowRepayParams::builder(
995 "asset_example".to_string(),
996 "FALSE".to_string(),
997 "symbol_example".to_string(),
998 "amount_example".to_string(),
999 "r#type_example".to_string(),
1000 )
1001 .build()
1002 .unwrap();
1003
1004 let resp_json: Value = serde_json::from_str(r#"{"tranId":100000001}"#).unwrap();
1005 let expected_response: models::MarginAccountBorrowRepayResponse =
1006 serde_json::from_value(resp_json.clone())
1007 .expect("should parse into models::MarginAccountBorrowRepayResponse");
1008
1009 let resp = client
1010 .margin_account_borrow_repay(params)
1011 .await
1012 .expect("Expected a response");
1013 let data_future = resp.data();
1014 let actual_response = data_future.await.unwrap();
1015 assert_eq!(actual_response, expected_response);
1016 });
1017 }
1018
1019 #[test]
1020 fn margin_account_borrow_repay_optional_params_success() {
1021 TOKIO_SHARED_RT.block_on(async {
1022 let client = MockBorrowRepayApiClient { force_error: false };
1023
1024 let params = MarginAccountBorrowRepayParams::builder(
1025 "asset_example".to_string(),
1026 "FALSE".to_string(),
1027 "symbol_example".to_string(),
1028 "amount_example".to_string(),
1029 "r#type_example".to_string(),
1030 )
1031 .recv_window(5000)
1032 .build()
1033 .unwrap();
1034
1035 let resp_json: Value = serde_json::from_str(r#"{"tranId":100000001}"#).unwrap();
1036 let expected_response: models::MarginAccountBorrowRepayResponse =
1037 serde_json::from_value(resp_json.clone())
1038 .expect("should parse into models::MarginAccountBorrowRepayResponse");
1039
1040 let resp = client
1041 .margin_account_borrow_repay(params)
1042 .await
1043 .expect("Expected a response");
1044 let data_future = resp.data();
1045 let actual_response = data_future.await.unwrap();
1046 assert_eq!(actual_response, expected_response);
1047 });
1048 }
1049
1050 #[test]
1051 fn margin_account_borrow_repay_response_error() {
1052 TOKIO_SHARED_RT.block_on(async {
1053 let client = MockBorrowRepayApiClient { force_error: true };
1054
1055 let params = MarginAccountBorrowRepayParams::builder(
1056 "asset_example".to_string(),
1057 "FALSE".to_string(),
1058 "symbol_example".to_string(),
1059 "amount_example".to_string(),
1060 "r#type_example".to_string(),
1061 )
1062 .build()
1063 .unwrap();
1064
1065 match client.margin_account_borrow_repay(params).await {
1066 Ok(_) => panic!("Expected an error"),
1067 Err(err) => {
1068 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1069 }
1070 }
1071 });
1072 }
1073
1074 #[test]
1075 fn query_borrow_repay_records_in_margin_account_required_params_success() {
1076 TOKIO_SHARED_RT.block_on(async {
1077 let client = MockBorrowRepayApiClient { force_error: false };
1078
1079 let params = QueryBorrowRepayRecordsInMarginAccountParams::builder("r#type_example".to_string(),).build().unwrap();
1080
1081 let resp_json: Value = serde_json::from_str(r#"{"rows":[{"type":"AUTO","isolatedSymbol":"BNBUSDT","amount":"14.00000000","asset":"BNB","interest":"0.01866667","principal":"13.98133333","status":"CONFIRMED","timestamp":1563438204000,"txId":2970933056}],"total":1}"#).unwrap();
1082 let expected_response : models::QueryBorrowRepayRecordsInMarginAccountResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::QueryBorrowRepayRecordsInMarginAccountResponse");
1083
1084 let resp = client.query_borrow_repay_records_in_margin_account(params).await.expect("Expected a response");
1085 let data_future = resp.data();
1086 let actual_response = data_future.await.unwrap();
1087 assert_eq!(actual_response, expected_response);
1088 });
1089 }
1090
1091 #[test]
1092 fn query_borrow_repay_records_in_margin_account_optional_params_success() {
1093 TOKIO_SHARED_RT.block_on(async {
1094 let client = MockBorrowRepayApiClient { force_error: false };
1095
1096 let params = QueryBorrowRepayRecordsInMarginAccountParams::builder("r#type_example".to_string(),).asset("asset_example".to_string()).isolated_symbol("isolated_symbol_example".to_string()).tx_id(1).start_time(1623319461670).end_time(1641782889000).current(1).size(10).recv_window(5000).build().unwrap();
1097
1098 let resp_json: Value = serde_json::from_str(r#"{"rows":[{"type":"AUTO","isolatedSymbol":"BNBUSDT","amount":"14.00000000","asset":"BNB","interest":"0.01866667","principal":"13.98133333","status":"CONFIRMED","timestamp":1563438204000,"txId":2970933056}],"total":1}"#).unwrap();
1099 let expected_response : models::QueryBorrowRepayRecordsInMarginAccountResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::QueryBorrowRepayRecordsInMarginAccountResponse");
1100
1101 let resp = client.query_borrow_repay_records_in_margin_account(params).await.expect("Expected a response");
1102 let data_future = resp.data();
1103 let actual_response = data_future.await.unwrap();
1104 assert_eq!(actual_response, expected_response);
1105 });
1106 }
1107
1108 #[test]
1109 fn query_borrow_repay_records_in_margin_account_response_error() {
1110 TOKIO_SHARED_RT.block_on(async {
1111 let client = MockBorrowRepayApiClient { force_error: true };
1112
1113 let params =
1114 QueryBorrowRepayRecordsInMarginAccountParams::builder("r#type_example".to_string())
1115 .build()
1116 .unwrap();
1117
1118 match client
1119 .query_borrow_repay_records_in_margin_account(params)
1120 .await
1121 {
1122 Ok(_) => panic!("Expected an error"),
1123 Err(err) => {
1124 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1125 }
1126 }
1127 });
1128 }
1129
1130 #[test]
1131 fn query_margin_interest_rate_history_required_params_success() {
1132 TOKIO_SHARED_RT.block_on(async {
1133 let client = MockBorrowRepayApiClient { force_error: false };
1134
1135 let params = QueryMarginInterestRateHistoryParams::builder("asset_example".to_string(),).build().unwrap();
1136
1137 let resp_json: Value = serde_json::from_str(r#"[{"asset":"BTC","dailyInterestRate":"0.00025000","timestamp":1611544731000,"vipLevel":1},{"asset":"BTC","dailyInterestRate":"0.00035000","timestamp":1610248118000,"vipLevel":1}]"#).unwrap();
1138 let expected_response : Vec<models::QueryMarginInterestRateHistoryResponseInner> = serde_json::from_value(resp_json.clone()).expect("should parse into Vec<models::QueryMarginInterestRateHistoryResponseInner>");
1139
1140 let resp = client.query_margin_interest_rate_history(params).await.expect("Expected a response");
1141 let data_future = resp.data();
1142 let actual_response = data_future.await.unwrap();
1143 assert_eq!(actual_response, expected_response);
1144 });
1145 }
1146
1147 #[test]
1148 fn query_margin_interest_rate_history_optional_params_success() {
1149 TOKIO_SHARED_RT.block_on(async {
1150 let client = MockBorrowRepayApiClient { force_error: false };
1151
1152 let params = QueryMarginInterestRateHistoryParams::builder("asset_example".to_string(),).vip_level(1).start_time(1623319461670).end_time(1641782889000).recv_window(5000).build().unwrap();
1153
1154 let resp_json: Value = serde_json::from_str(r#"[{"asset":"BTC","dailyInterestRate":"0.00025000","timestamp":1611544731000,"vipLevel":1},{"asset":"BTC","dailyInterestRate":"0.00035000","timestamp":1610248118000,"vipLevel":1}]"#).unwrap();
1155 let expected_response : Vec<models::QueryMarginInterestRateHistoryResponseInner> = serde_json::from_value(resp_json.clone()).expect("should parse into Vec<models::QueryMarginInterestRateHistoryResponseInner>");
1156
1157 let resp = client.query_margin_interest_rate_history(params).await.expect("Expected a response");
1158 let data_future = resp.data();
1159 let actual_response = data_future.await.unwrap();
1160 assert_eq!(actual_response, expected_response);
1161 });
1162 }
1163
1164 #[test]
1165 fn query_margin_interest_rate_history_response_error() {
1166 TOKIO_SHARED_RT.block_on(async {
1167 let client = MockBorrowRepayApiClient { force_error: true };
1168
1169 let params = QueryMarginInterestRateHistoryParams::builder("asset_example".to_string())
1170 .build()
1171 .unwrap();
1172
1173 match client.query_margin_interest_rate_history(params).await {
1174 Ok(_) => panic!("Expected an error"),
1175 Err(err) => {
1176 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1177 }
1178 }
1179 });
1180 }
1181
1182 #[test]
1183 fn query_max_borrow_required_params_success() {
1184 TOKIO_SHARED_RT.block_on(async {
1185 let client = MockBorrowRepayApiClient { force_error: false };
1186
1187 let params = QueryMaxBorrowParams::builder("asset_example".to_string())
1188 .build()
1189 .unwrap();
1190
1191 let resp_json: Value =
1192 serde_json::from_str(r#"{"amount":"1.69248805","borrowLimit":"60"}"#).unwrap();
1193 let expected_response: models::QueryMaxBorrowResponse =
1194 serde_json::from_value(resp_json.clone())
1195 .expect("should parse into models::QueryMaxBorrowResponse");
1196
1197 let resp = client
1198 .query_max_borrow(params)
1199 .await
1200 .expect("Expected a response");
1201 let data_future = resp.data();
1202 let actual_response = data_future.await.unwrap();
1203 assert_eq!(actual_response, expected_response);
1204 });
1205 }
1206
1207 #[test]
1208 fn query_max_borrow_optional_params_success() {
1209 TOKIO_SHARED_RT.block_on(async {
1210 let client = MockBorrowRepayApiClient { force_error: false };
1211
1212 let params = QueryMaxBorrowParams::builder("asset_example".to_string())
1213 .isolated_symbol("isolated_symbol_example".to_string())
1214 .recv_window(5000)
1215 .build()
1216 .unwrap();
1217
1218 let resp_json: Value =
1219 serde_json::from_str(r#"{"amount":"1.69248805","borrowLimit":"60"}"#).unwrap();
1220 let expected_response: models::QueryMaxBorrowResponse =
1221 serde_json::from_value(resp_json.clone())
1222 .expect("should parse into models::QueryMaxBorrowResponse");
1223
1224 let resp = client
1225 .query_max_borrow(params)
1226 .await
1227 .expect("Expected a response");
1228 let data_future = resp.data();
1229 let actual_response = data_future.await.unwrap();
1230 assert_eq!(actual_response, expected_response);
1231 });
1232 }
1233
1234 #[test]
1235 fn query_max_borrow_response_error() {
1236 TOKIO_SHARED_RT.block_on(async {
1237 let client = MockBorrowRepayApiClient { force_error: true };
1238
1239 let params = QueryMaxBorrowParams::builder("asset_example".to_string())
1240 .build()
1241 .unwrap();
1242
1243 match client.query_max_borrow(params).await {
1244 Ok(_) => panic!("Expected an error"),
1245 Err(err) => {
1246 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1247 }
1248 }
1249 });
1250 }
1251}