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#[allow(non_camel_case_types)]
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub enum GetFutureHourlyInterestRateIsIsolatedEnum {
74 #[serde(rename = "TRUE")]
75 True,
76 #[serde(rename = "FALSE")]
77 False,
78}
79
80impl GetFutureHourlyInterestRateIsIsolatedEnum {
81 #[must_use]
82 pub fn as_str(&self) -> &'static str {
83 match self {
84 Self::True => "TRUE",
85 Self::False => "FALSE",
86 }
87 }
88}
89
90impl std::str::FromStr for GetFutureHourlyInterestRateIsIsolatedEnum {
91 type Err = Box<dyn std::error::Error + Send + Sync>;
92
93 fn from_str(s: &str) -> Result<Self, Self::Err> {
94 match s {
95 "TRUE" => Ok(Self::True),
96 "FALSE" => Ok(Self::False),
97 other => Err(format!(
98 "invalid GetFutureHourlyInterestRateIsIsolatedEnum: {}",
99 other
100 )
101 .into()),
102 }
103 }
104}
105
106#[allow(non_camel_case_types)]
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub enum MarginAccountBorrowRepayIsIsolatedEnum {
109 #[serde(rename = "TRUE")]
110 True,
111 #[serde(rename = "FALSE")]
112 False,
113}
114
115impl MarginAccountBorrowRepayIsIsolatedEnum {
116 #[must_use]
117 pub fn as_str(&self) -> &'static str {
118 match self {
119 Self::True => "TRUE",
120 Self::False => "FALSE",
121 }
122 }
123}
124
125impl std::str::FromStr for MarginAccountBorrowRepayIsIsolatedEnum {
126 type Err = Box<dyn std::error::Error + Send + Sync>;
127
128 fn from_str(s: &str) -> Result<Self, Self::Err> {
129 match s {
130 "TRUE" => Ok(Self::True),
131 "FALSE" => Ok(Self::False),
132 other => {
133 Err(format!("invalid MarginAccountBorrowRepayIsIsolatedEnum: {}", other).into())
134 }
135 }
136 }
137}
138
139#[allow(non_camel_case_types)]
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub enum MarginAccountBorrowRepayTypeEnum {
142 #[serde(rename = "BORROW")]
143 Borrow,
144 #[serde(rename = "REPAY")]
145 Repay,
146}
147
148impl MarginAccountBorrowRepayTypeEnum {
149 #[must_use]
150 pub fn as_str(&self) -> &'static str {
151 match self {
152 Self::Borrow => "BORROW",
153 Self::Repay => "REPAY",
154 }
155 }
156}
157
158impl std::str::FromStr for MarginAccountBorrowRepayTypeEnum {
159 type Err = Box<dyn std::error::Error + Send + Sync>;
160
161 fn from_str(s: &str) -> Result<Self, Self::Err> {
162 match s {
163 "BORROW" => Ok(Self::Borrow),
164 "REPAY" => Ok(Self::Repay),
165 other => Err(format!("invalid MarginAccountBorrowRepayTypeEnum: {}", other).into()),
166 }
167 }
168}
169
170#[allow(non_camel_case_types)]
171#[derive(Debug, Clone, Serialize, Deserialize)]
172pub enum QueryBorrowRepayRecordsInMarginAccountTypeEnum {
173 #[serde(rename = "BORROW")]
174 Borrow,
175 #[serde(rename = "REPAY")]
176 Repay,
177}
178
179impl QueryBorrowRepayRecordsInMarginAccountTypeEnum {
180 #[must_use]
181 pub fn as_str(&self) -> &'static str {
182 match self {
183 Self::Borrow => "BORROW",
184 Self::Repay => "REPAY",
185 }
186 }
187}
188
189impl std::str::FromStr for QueryBorrowRepayRecordsInMarginAccountTypeEnum {
190 type Err = Box<dyn std::error::Error + Send + Sync>;
191
192 fn from_str(s: &str) -> Result<Self, Self::Err> {
193 match s {
194 "BORROW" => Ok(Self::Borrow),
195 "REPAY" => Ok(Self::Repay),
196 other => Err(format!(
197 "invalid QueryBorrowRepayRecordsInMarginAccountTypeEnum: {}",
198 other
199 )
200 .into()),
201 }
202 }
203}
204
205#[derive(Clone, Debug, Builder, Deserialize)]
210#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
211pub struct GetFutureHourlyInterestRateParams {
212 #[builder(setter(into))]
217 #[serde(rename = "assets")]
218 pub assets: String,
219 #[builder(setter(into))]
224 #[serde(rename = "isIsolated")]
225 pub is_isolated: GetFutureHourlyInterestRateIsIsolatedEnum,
226}
227
228impl GetFutureHourlyInterestRateParams {
229 #[must_use]
237 pub fn builder(
238 assets: String,
239 is_isolated: GetFutureHourlyInterestRateIsIsolatedEnum,
240 ) -> GetFutureHourlyInterestRateParamsBuilder {
241 GetFutureHourlyInterestRateParamsBuilder::default()
242 .assets(assets)
243 .is_isolated(is_isolated)
244 }
245}
246#[derive(Clone, Debug, Builder, Deserialize, Default)]
251#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
252pub struct GetInterestHistoryParams {
253 #[builder(setter(into), default)]
258 #[serde(rename = "asset", default)]
259 pub asset: Option<String>,
260 #[builder(setter(into), default)]
265 #[serde(rename = "isolatedSymbol", default)]
266 pub isolated_symbol: Option<String>,
267 #[builder(setter(into), default)]
271 #[serde(rename = "startTime", default)]
272 pub start_time: Option<i64>,
273 #[builder(setter(into), default)]
278 #[serde(rename = "endTime", default)]
279 pub end_time: Option<i64>,
280 #[builder(setter(into), default)]
285 #[serde(rename = "current", default)]
286 pub current: Option<i64>,
287 #[builder(setter(into), default)]
292 #[serde(rename = "size", default)]
293 pub size: Option<i64>,
294 #[builder(setter(into), default)]
299 #[serde(rename = "recvWindow", default)]
300 pub recv_window: Option<i64>,
301}
302
303impl GetInterestHistoryParams {
304 #[must_use]
307 pub fn builder() -> GetInterestHistoryParamsBuilder {
308 GetInterestHistoryParamsBuilder::default()
309 }
310}
311#[derive(Clone, Debug, Builder, Deserialize)]
316#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
317pub struct MarginAccountBorrowRepayParams {
318 #[builder(setter(into))]
323 #[serde(rename = "asset")]
324 pub asset: String,
325 #[builder(setter(into))]
329 #[serde(rename = "isIsolated")]
330 pub is_isolated: MarginAccountBorrowRepayIsIsolatedEnum,
331 #[builder(setter(into))]
336 #[serde(rename = "amount")]
337 pub amount: String,
338 #[builder(setter(into))]
343 #[serde(rename = "type")]
344 pub r#type: MarginAccountBorrowRepayTypeEnum,
345 #[builder(setter(into), default)]
349 #[serde(rename = "symbol", default)]
350 pub symbol: Option<String>,
351 #[builder(setter(into), default)]
356 #[serde(rename = "recvWindow", default)]
357 pub recv_window: Option<i64>,
358}
359
360impl MarginAccountBorrowRepayParams {
361 #[must_use]
371 pub fn builder(
372 asset: String,
373 is_isolated: MarginAccountBorrowRepayIsIsolatedEnum,
374 amount: String,
375 r#type: MarginAccountBorrowRepayTypeEnum,
376 ) -> MarginAccountBorrowRepayParamsBuilder {
377 MarginAccountBorrowRepayParamsBuilder::default()
378 .asset(asset)
379 .is_isolated(is_isolated)
380 .amount(amount)
381 .r#type(r#type)
382 }
383}
384#[derive(Clone, Debug, Builder, Deserialize)]
389#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
390pub struct QueryBorrowRepayRecordsInMarginAccountParams {
391 #[builder(setter(into))]
396 #[serde(rename = "type")]
397 pub r#type: QueryBorrowRepayRecordsInMarginAccountTypeEnum,
398 #[builder(setter(into), default)]
403 #[serde(rename = "asset", default)]
404 pub asset: Option<String>,
405 #[builder(setter(into), default)]
410 #[serde(rename = "isolatedSymbol", default)]
411 pub isolated_symbol: Option<String>,
412 #[builder(setter(into), default)]
417 #[serde(rename = "txId", default)]
418 pub tx_id: Option<i64>,
419 #[builder(setter(into), default)]
424 #[serde(rename = "startTime", default)]
425 pub start_time: Option<i64>,
426 #[builder(setter(into), default)]
431 #[serde(rename = "endTime", default)]
432 pub end_time: Option<i64>,
433 #[builder(setter(into), default)]
438 #[serde(rename = "current", default)]
439 pub current: Option<i64>,
440 #[builder(setter(into), default)]
445 #[serde(rename = "size", default)]
446 pub size: Option<i64>,
447 #[builder(setter(into), default)]
452 #[serde(rename = "recvWindow", default)]
453 pub recv_window: Option<i64>,
454}
455
456impl QueryBorrowRepayRecordsInMarginAccountParams {
457 #[must_use]
464 pub fn builder(
465 r#type: QueryBorrowRepayRecordsInMarginAccountTypeEnum,
466 ) -> QueryBorrowRepayRecordsInMarginAccountParamsBuilder {
467 QueryBorrowRepayRecordsInMarginAccountParamsBuilder::default().r#type(r#type)
468 }
469}
470#[derive(Clone, Debug, Builder, Deserialize)]
475#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
476pub struct QueryMarginInterestRateHistoryParams {
477 #[builder(setter(into))]
482 #[serde(rename = "asset")]
483 pub asset: String,
484 #[builder(setter(into), default)]
489 #[serde(rename = "vipLevel", default)]
490 pub vip_level: Option<i64>,
491 #[builder(setter(into), default)]
496 #[serde(rename = "startTime", default)]
497 pub start_time: Option<i64>,
498 #[builder(setter(into), default)]
503 #[serde(rename = "endTime", default)]
504 pub end_time: Option<i64>,
505 #[builder(setter(into), default)]
510 #[serde(rename = "recvWindow", default)]
511 pub recv_window: Option<i64>,
512}
513
514impl QueryMarginInterestRateHistoryParams {
515 #[must_use]
522 pub fn builder(asset: String) -> QueryMarginInterestRateHistoryParamsBuilder {
523 QueryMarginInterestRateHistoryParamsBuilder::default().asset(asset)
524 }
525}
526#[derive(Clone, Debug, Builder, Deserialize)]
531#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
532pub struct QueryMaxBorrowParams {
533 #[builder(setter(into))]
538 #[serde(rename = "asset")]
539 pub asset: String,
540 #[builder(setter(into), default)]
545 #[serde(rename = "isolatedSymbol", default)]
546 pub isolated_symbol: Option<String>,
547 #[builder(setter(into), default)]
552 #[serde(rename = "recvWindow", default)]
553 pub recv_window: Option<i64>,
554}
555
556impl QueryMaxBorrowParams {
557 #[must_use]
564 pub fn builder(asset: String) -> QueryMaxBorrowParamsBuilder {
565 QueryMaxBorrowParamsBuilder::default().asset(asset)
566 }
567}
568
569#[async_trait]
570impl BorrowRepayApi for BorrowRepayApiClient {
571 async fn get_future_hourly_interest_rate(
572 &self,
573 params: GetFutureHourlyInterestRateParams,
574 ) -> anyhow::Result<RestApiResponse<Vec<models::GetFutureHourlyInterestRateResponseInner>>>
575 {
576 let GetFutureHourlyInterestRateParams {
577 assets,
578 is_isolated,
579 } = params;
580
581 let mut query_params = BTreeMap::new();
582 let body_params = BTreeMap::new();
583
584 query_params.insert("assets".to_string(), json!(assets));
585
586 query_params.insert("isIsolated".to_string(), json!(is_isolated));
587
588 send_request::<Vec<models::GetFutureHourlyInterestRateResponseInner>>(
589 &self.configuration,
590 "/sapi/v1/margin/next-hourly-interest-rate",
591 reqwest::Method::GET,
592 query_params,
593 body_params,
594 if HAS_TIME_UNIT {
595 self.configuration.time_unit
596 } else {
597 None
598 },
599 true,
600 )
601 .await
602 }
603
604 async fn get_interest_history(
605 &self,
606 params: GetInterestHistoryParams,
607 ) -> anyhow::Result<RestApiResponse<models::GetInterestHistoryResponse>> {
608 let GetInterestHistoryParams {
609 asset,
610 isolated_symbol,
611 start_time,
612 end_time,
613 current,
614 size,
615 recv_window,
616 } = params;
617
618 let mut query_params = BTreeMap::new();
619 let body_params = BTreeMap::new();
620
621 if let Some(rw) = asset {
622 query_params.insert("asset".to_string(), json!(rw));
623 }
624
625 if let Some(rw) = isolated_symbol {
626 query_params.insert("isolatedSymbol".to_string(), json!(rw));
627 }
628
629 if let Some(rw) = start_time {
630 query_params.insert("startTime".to_string(), json!(rw));
631 }
632
633 if let Some(rw) = end_time {
634 query_params.insert("endTime".to_string(), json!(rw));
635 }
636
637 if let Some(rw) = current {
638 query_params.insert("current".to_string(), json!(rw));
639 }
640
641 if let Some(rw) = size {
642 query_params.insert("size".to_string(), json!(rw));
643 }
644
645 if let Some(rw) = recv_window {
646 query_params.insert("recvWindow".to_string(), json!(rw));
647 }
648
649 send_request::<models::GetInterestHistoryResponse>(
650 &self.configuration,
651 "/sapi/v1/margin/interestHistory",
652 reqwest::Method::GET,
653 query_params,
654 body_params,
655 if HAS_TIME_UNIT {
656 self.configuration.time_unit
657 } else {
658 None
659 },
660 true,
661 )
662 .await
663 }
664
665 async fn margin_account_borrow_repay(
666 &self,
667 params: MarginAccountBorrowRepayParams,
668 ) -> anyhow::Result<RestApiResponse<models::MarginAccountBorrowRepayResponse>> {
669 let MarginAccountBorrowRepayParams {
670 asset,
671 is_isolated,
672 amount,
673 r#type,
674 symbol,
675 recv_window,
676 } = params;
677
678 let mut query_params = BTreeMap::new();
679 let body_params = BTreeMap::new();
680
681 query_params.insert("asset".to_string(), json!(asset));
682
683 query_params.insert("isIsolated".to_string(), json!(is_isolated));
684
685 if let Some(rw) = symbol {
686 query_params.insert("symbol".to_string(), json!(rw));
687 }
688
689 query_params.insert("amount".to_string(), json!(amount));
690
691 query_params.insert("type".to_string(), json!(r#type));
692
693 if let Some(rw) = recv_window {
694 query_params.insert("recvWindow".to_string(), json!(rw));
695 }
696
697 send_request::<models::MarginAccountBorrowRepayResponse>(
698 &self.configuration,
699 "/sapi/v1/margin/borrow-repay",
700 reqwest::Method::POST,
701 query_params,
702 body_params,
703 if HAS_TIME_UNIT {
704 self.configuration.time_unit
705 } else {
706 None
707 },
708 true,
709 )
710 .await
711 }
712
713 async fn query_borrow_repay_records_in_margin_account(
714 &self,
715 params: QueryBorrowRepayRecordsInMarginAccountParams,
716 ) -> anyhow::Result<RestApiResponse<models::QueryBorrowRepayRecordsInMarginAccountResponse>>
717 {
718 let QueryBorrowRepayRecordsInMarginAccountParams {
719 r#type,
720 asset,
721 isolated_symbol,
722 tx_id,
723 start_time,
724 end_time,
725 current,
726 size,
727 recv_window,
728 } = params;
729
730 let mut query_params = BTreeMap::new();
731 let body_params = BTreeMap::new();
732
733 if let Some(rw) = asset {
734 query_params.insert("asset".to_string(), json!(rw));
735 }
736
737 if let Some(rw) = isolated_symbol {
738 query_params.insert("isolatedSymbol".to_string(), json!(rw));
739 }
740
741 if let Some(rw) = tx_id {
742 query_params.insert("txId".to_string(), json!(rw));
743 }
744
745 if let Some(rw) = start_time {
746 query_params.insert("startTime".to_string(), json!(rw));
747 }
748
749 if let Some(rw) = end_time {
750 query_params.insert("endTime".to_string(), json!(rw));
751 }
752
753 if let Some(rw) = current {
754 query_params.insert("current".to_string(), json!(rw));
755 }
756
757 if let Some(rw) = size {
758 query_params.insert("size".to_string(), json!(rw));
759 }
760
761 query_params.insert("type".to_string(), json!(r#type));
762
763 if let Some(rw) = recv_window {
764 query_params.insert("recvWindow".to_string(), json!(rw));
765 }
766
767 send_request::<models::QueryBorrowRepayRecordsInMarginAccountResponse>(
768 &self.configuration,
769 "/sapi/v1/margin/borrow-repay",
770 reqwest::Method::GET,
771 query_params,
772 body_params,
773 if HAS_TIME_UNIT {
774 self.configuration.time_unit
775 } else {
776 None
777 },
778 true,
779 )
780 .await
781 }
782
783 async fn query_margin_interest_rate_history(
784 &self,
785 params: QueryMarginInterestRateHistoryParams,
786 ) -> anyhow::Result<RestApiResponse<Vec<models::QueryMarginInterestRateHistoryResponseInner>>>
787 {
788 let QueryMarginInterestRateHistoryParams {
789 asset,
790 vip_level,
791 start_time,
792 end_time,
793 recv_window,
794 } = params;
795
796 let mut query_params = BTreeMap::new();
797 let body_params = BTreeMap::new();
798
799 query_params.insert("asset".to_string(), json!(asset));
800
801 if let Some(rw) = vip_level {
802 query_params.insert("vipLevel".to_string(), json!(rw));
803 }
804
805 if let Some(rw) = start_time {
806 query_params.insert("startTime".to_string(), json!(rw));
807 }
808
809 if let Some(rw) = end_time {
810 query_params.insert("endTime".to_string(), json!(rw));
811 }
812
813 if let Some(rw) = recv_window {
814 query_params.insert("recvWindow".to_string(), json!(rw));
815 }
816
817 send_request::<Vec<models::QueryMarginInterestRateHistoryResponseInner>>(
818 &self.configuration,
819 "/sapi/v1/margin/interestRateHistory",
820 reqwest::Method::GET,
821 query_params,
822 body_params,
823 if HAS_TIME_UNIT {
824 self.configuration.time_unit
825 } else {
826 None
827 },
828 true,
829 )
830 .await
831 }
832
833 async fn query_max_borrow(
834 &self,
835 params: QueryMaxBorrowParams,
836 ) -> anyhow::Result<RestApiResponse<models::QueryMaxBorrowResponse>> {
837 let QueryMaxBorrowParams {
838 asset,
839 isolated_symbol,
840 recv_window,
841 } = params;
842
843 let mut query_params = BTreeMap::new();
844 let body_params = BTreeMap::new();
845
846 query_params.insert("asset".to_string(), json!(asset));
847
848 if let Some(rw) = isolated_symbol {
849 query_params.insert("isolatedSymbol".to_string(), json!(rw));
850 }
851
852 if let Some(rw) = recv_window {
853 query_params.insert("recvWindow".to_string(), json!(rw));
854 }
855
856 send_request::<models::QueryMaxBorrowResponse>(
857 &self.configuration,
858 "/sapi/v1/margin/maxBorrowable",
859 reqwest::Method::GET,
860 query_params,
861 body_params,
862 if HAS_TIME_UNIT {
863 self.configuration.time_unit
864 } else {
865 None
866 },
867 true,
868 )
869 .await
870 }
871}
872
873#[cfg(all(test, feature = "margin_trading"))]
874mod tests {
875 use super::*;
876 use crate::TOKIO_SHARED_RT;
877 use crate::{errors::ConnectorError, models::DataFuture, models::RestApiRateLimit};
878 use async_trait::async_trait;
879 use std::collections::HashMap;
880
881 struct DummyRestApiResponse<T> {
882 inner: Box<dyn FnOnce() -> DataFuture<Result<T, ConnectorError>> + Send + Sync>,
883 status: u16,
884 headers: HashMap<String, String>,
885 rate_limits: Option<Vec<RestApiRateLimit>>,
886 }
887
888 impl<T> From<DummyRestApiResponse<T>> for RestApiResponse<T> {
889 fn from(dummy: DummyRestApiResponse<T>) -> Self {
890 Self {
891 data_fn: dummy.inner,
892 status: dummy.status,
893 headers: dummy.headers,
894 rate_limits: dummy.rate_limits,
895 }
896 }
897 }
898
899 struct MockBorrowRepayApiClient {
900 force_error: bool,
901 }
902
903 #[async_trait]
904 impl BorrowRepayApi for MockBorrowRepayApiClient {
905 async fn get_future_hourly_interest_rate(
906 &self,
907 _params: GetFutureHourlyInterestRateParams,
908 ) -> anyhow::Result<RestApiResponse<Vec<models::GetFutureHourlyInterestRateResponseInner>>>
909 {
910 if self.force_error {
911 return Err(ConnectorError::ConnectorClientError {
912 msg: "ResponseError".to_string(),
913 code: None,
914 }
915 .into());
916 }
917
918 let resp_json: Value =
919 serde_json::from_str(r#"[{"asset":"BTC","nextHourlyInterestRate":"0.00000571"}]"#)
920 .unwrap_or_else(|_| serde_json::json!({}));
921 let dummy_response: Vec<models::GetFutureHourlyInterestRateResponseInner> =
922 serde_json::from_value(resp_json.clone()).expect(
923 "should parse into Vec<models::GetFutureHourlyInterestRateResponseInner>",
924 );
925
926 let dummy = DummyRestApiResponse {
927 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
928 status: 200,
929 headers: HashMap::new(),
930 rate_limits: None,
931 };
932
933 Ok(dummy.into())
934 }
935
936 async fn get_interest_history(
937 &self,
938 _params: GetInterestHistoryParams,
939 ) -> anyhow::Result<RestApiResponse<models::GetInterestHistoryResponse>> {
940 if self.force_error {
941 return Err(ConnectorError::ConnectorClientError {
942 msg: "ResponseError".to_string(),
943 code: None,
944 }
945 .into());
946 }
947
948 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_or_else(|_| serde_json::json!({}));
949 let dummy_response: models::GetInterestHistoryResponse =
950 serde_json::from_value(resp_json.clone())
951 .expect("should parse into models::GetInterestHistoryResponse");
952
953 let dummy = DummyRestApiResponse {
954 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
955 status: 200,
956 headers: HashMap::new(),
957 rate_limits: None,
958 };
959
960 Ok(dummy.into())
961 }
962
963 async fn margin_account_borrow_repay(
964 &self,
965 _params: MarginAccountBorrowRepayParams,
966 ) -> anyhow::Result<RestApiResponse<models::MarginAccountBorrowRepayResponse>> {
967 if self.force_error {
968 return Err(ConnectorError::ConnectorClientError {
969 msg: "ResponseError".to_string(),
970 code: None,
971 }
972 .into());
973 }
974
975 let resp_json: Value = serde_json::from_str(r#"{"tranId":100000001}"#)
976 .unwrap_or_else(|_| serde_json::json!({}));
977 let dummy_response: models::MarginAccountBorrowRepayResponse =
978 serde_json::from_value(resp_json.clone())
979 .expect("should parse into models::MarginAccountBorrowRepayResponse");
980
981 let dummy = DummyRestApiResponse {
982 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
983 status: 200,
984 headers: HashMap::new(),
985 rate_limits: None,
986 };
987
988 Ok(dummy.into())
989 }
990
991 async fn query_borrow_repay_records_in_margin_account(
992 &self,
993 _params: QueryBorrowRepayRecordsInMarginAccountParams,
994 ) -> anyhow::Result<RestApiResponse<models::QueryBorrowRepayRecordsInMarginAccountResponse>>
995 {
996 if self.force_error {
997 return Err(ConnectorError::ConnectorClientError {
998 msg: "ResponseError".to_string(),
999 code: None,
1000 }
1001 .into());
1002 }
1003
1004 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_or_else(|_| serde_json::json!({}));
1005 let dummy_response: models::QueryBorrowRepayRecordsInMarginAccountResponse =
1006 serde_json::from_value(resp_json.clone()).expect(
1007 "should parse into models::QueryBorrowRepayRecordsInMarginAccountResponse",
1008 );
1009
1010 let dummy = DummyRestApiResponse {
1011 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
1012 status: 200,
1013 headers: HashMap::new(),
1014 rate_limits: None,
1015 };
1016
1017 Ok(dummy.into())
1018 }
1019
1020 async fn query_margin_interest_rate_history(
1021 &self,
1022 _params: QueryMarginInterestRateHistoryParams,
1023 ) -> anyhow::Result<RestApiResponse<Vec<models::QueryMarginInterestRateHistoryResponseInner>>>
1024 {
1025 if self.force_error {
1026 return Err(ConnectorError::ConnectorClientError {
1027 msg: "ResponseError".to_string(),
1028 code: None,
1029 }
1030 .into());
1031 }
1032
1033 let resp_json: Value = serde_json::from_str(r#"[{"asset":"BTC","dailyInterestRate":"0.00025000","timestamp":1611544731000,"vipLevel":1}]"#).unwrap_or_else(|_| serde_json::json!({}));
1034 let dummy_response: Vec<models::QueryMarginInterestRateHistoryResponseInner> =
1035 serde_json::from_value(resp_json.clone()).expect(
1036 "should parse into Vec<models::QueryMarginInterestRateHistoryResponseInner>",
1037 );
1038
1039 let dummy = DummyRestApiResponse {
1040 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
1041 status: 200,
1042 headers: HashMap::new(),
1043 rate_limits: None,
1044 };
1045
1046 Ok(dummy.into())
1047 }
1048
1049 async fn query_max_borrow(
1050 &self,
1051 _params: QueryMaxBorrowParams,
1052 ) -> anyhow::Result<RestApiResponse<models::QueryMaxBorrowResponse>> {
1053 if self.force_error {
1054 return Err(ConnectorError::ConnectorClientError {
1055 msg: "ResponseError".to_string(),
1056 code: None,
1057 }
1058 .into());
1059 }
1060
1061 let resp_json: Value =
1062 serde_json::from_str(r#"{"amount":"1.69248805","borrowLimit":"60"}"#)
1063 .unwrap_or_else(|_| serde_json::json!({}));
1064 let dummy_response: models::QueryMaxBorrowResponse =
1065 serde_json::from_value(resp_json.clone())
1066 .expect("should parse into models::QueryMaxBorrowResponse");
1067
1068 let dummy = DummyRestApiResponse {
1069 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
1070 status: 200,
1071 headers: HashMap::new(),
1072 rate_limits: None,
1073 };
1074
1075 Ok(dummy.into())
1076 }
1077 }
1078
1079 #[test]
1080 fn get_future_hourly_interest_rate_required_params_success() {
1081 TOKIO_SHARED_RT.block_on(async {
1082 let client = MockBorrowRepayApiClient { force_error: false };
1083
1084 let params = GetFutureHourlyInterestRateParams::builder(
1085 "BTC,ETH".to_string(),
1086 GetFutureHourlyInterestRateIsIsolatedEnum::True,
1087 )
1088 .build()
1089 .unwrap();
1090
1091 let resp_json: Value =
1092 serde_json::from_str(r#"[{"asset":"BTC","nextHourlyInterestRate":"0.00000571"}]"#)
1093 .unwrap_or_else(|_| serde_json::json!({}));
1094 let expected_response: Vec<models::GetFutureHourlyInterestRateResponseInner> =
1095 serde_json::from_value(resp_json.clone()).expect(
1096 "should parse into Vec<models::GetFutureHourlyInterestRateResponseInner>",
1097 );
1098
1099 let resp = client
1100 .get_future_hourly_interest_rate(params)
1101 .await
1102 .expect("Expected a response");
1103 let data_future = resp.data();
1104 let actual_response = data_future.await.unwrap();
1105 assert_eq!(actual_response, expected_response);
1106 });
1107 }
1108
1109 #[test]
1110 fn get_future_hourly_interest_rate_optional_params_success() {
1111 TOKIO_SHARED_RT.block_on(async {
1112 let client = MockBorrowRepayApiClient { force_error: false };
1113
1114 let params = GetFutureHourlyInterestRateParams::builder(
1115 "BTC,ETH".to_string(),
1116 GetFutureHourlyInterestRateIsIsolatedEnum::True,
1117 )
1118 .build()
1119 .unwrap();
1120
1121 let resp_json: Value =
1122 serde_json::from_str(r#"[{"asset":"BTC","nextHourlyInterestRate":"0.00000571"}]"#)
1123 .unwrap_or_else(|_| serde_json::json!({}));
1124 let expected_response: Vec<models::GetFutureHourlyInterestRateResponseInner> =
1125 serde_json::from_value(resp_json.clone()).expect(
1126 "should parse into Vec<models::GetFutureHourlyInterestRateResponseInner>",
1127 );
1128
1129 let resp = client
1130 .get_future_hourly_interest_rate(params)
1131 .await
1132 .expect("Expected a response");
1133 let data_future = resp.data();
1134 let actual_response = data_future.await.unwrap();
1135 assert_eq!(actual_response, expected_response);
1136 });
1137 }
1138
1139 #[test]
1140 fn get_future_hourly_interest_rate_response_error() {
1141 TOKIO_SHARED_RT.block_on(async {
1142 let client = MockBorrowRepayApiClient { force_error: true };
1143
1144 let params = GetFutureHourlyInterestRateParams::builder(
1145 "BTC,ETH".to_string(),
1146 GetFutureHourlyInterestRateIsIsolatedEnum::True,
1147 )
1148 .build()
1149 .unwrap();
1150
1151 match client.get_future_hourly_interest_rate(params).await {
1152 Ok(_) => panic!("Expected an error"),
1153 Err(err) => {
1154 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1155 }
1156 }
1157 });
1158 }
1159
1160 #[test]
1161 fn get_interest_history_required_params_success() {
1162 TOKIO_SHARED_RT.block_on(async {
1163 let client = MockBorrowRepayApiClient { force_error: false };
1164
1165 let params = GetInterestHistoryParams::builder().build().unwrap();
1166
1167 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_or_else(|_| serde_json::json!({}));
1168 let expected_response : models::GetInterestHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::GetInterestHistoryResponse");
1169
1170 let resp = client.get_interest_history(params).await.expect("Expected a response");
1171 let data_future = resp.data();
1172 let actual_response = data_future.await.unwrap();
1173 assert_eq!(actual_response, expected_response);
1174 });
1175 }
1176
1177 #[test]
1178 fn get_interest_history_optional_params_success() {
1179 TOKIO_SHARED_RT.block_on(async {
1180 let client = MockBorrowRepayApiClient { force_error: false };
1181
1182 let params = GetInterestHistoryParams::builder().asset("USDT".to_string()).isolated_symbol("BNBUSDT".to_string()).start_time(1623319461670).end_time(1641782889000).current(1).size(10).recv_window(5000).build().unwrap();
1183
1184 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_or_else(|_| serde_json::json!({}));
1185 let expected_response : models::GetInterestHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::GetInterestHistoryResponse");
1186
1187 let resp = client.get_interest_history(params).await.expect("Expected a response");
1188 let data_future = resp.data();
1189 let actual_response = data_future.await.unwrap();
1190 assert_eq!(actual_response, expected_response);
1191 });
1192 }
1193
1194 #[test]
1195 fn get_interest_history_response_error() {
1196 TOKIO_SHARED_RT.block_on(async {
1197 let client = MockBorrowRepayApiClient { force_error: true };
1198
1199 let params = GetInterestHistoryParams::builder().build().unwrap();
1200
1201 match client.get_interest_history(params).await {
1202 Ok(_) => panic!("Expected an error"),
1203 Err(err) => {
1204 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1205 }
1206 }
1207 });
1208 }
1209
1210 #[test]
1211 fn margin_account_borrow_repay_required_params_success() {
1212 TOKIO_SHARED_RT.block_on(async {
1213 let client = MockBorrowRepayApiClient { force_error: false };
1214
1215 let params = MarginAccountBorrowRepayParams::builder(
1216 "USDT".to_string(),
1217 MarginAccountBorrowRepayIsIsolatedEnum::True,
1218 "1.0".to_string(),
1219 MarginAccountBorrowRepayTypeEnum::Borrow,
1220 )
1221 .build()
1222 .unwrap();
1223
1224 let resp_json: Value = serde_json::from_str(r#"{"tranId":100000001}"#)
1225 .unwrap_or_else(|_| serde_json::json!({}));
1226 let expected_response: models::MarginAccountBorrowRepayResponse =
1227 serde_json::from_value(resp_json.clone())
1228 .expect("should parse into models::MarginAccountBorrowRepayResponse");
1229
1230 let resp = client
1231 .margin_account_borrow_repay(params)
1232 .await
1233 .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 margin_account_borrow_repay_optional_params_success() {
1242 TOKIO_SHARED_RT.block_on(async {
1243 let client = MockBorrowRepayApiClient { force_error: false };
1244
1245 let params = MarginAccountBorrowRepayParams::builder(
1246 "USDT".to_string(),
1247 MarginAccountBorrowRepayIsIsolatedEnum::True,
1248 "1.0".to_string(),
1249 MarginAccountBorrowRepayTypeEnum::Borrow,
1250 )
1251 .symbol("BTCUSDT".to_string())
1252 .recv_window(5000)
1253 .build()
1254 .unwrap();
1255
1256 let resp_json: Value = serde_json::from_str(r#"{"tranId":100000001}"#)
1257 .unwrap_or_else(|_| serde_json::json!({}));
1258 let expected_response: models::MarginAccountBorrowRepayResponse =
1259 serde_json::from_value(resp_json.clone())
1260 .expect("should parse into models::MarginAccountBorrowRepayResponse");
1261
1262 let resp = client
1263 .margin_account_borrow_repay(params)
1264 .await
1265 .expect("Expected a response");
1266 let data_future = resp.data();
1267 let actual_response = data_future.await.unwrap();
1268 assert_eq!(actual_response, expected_response);
1269 });
1270 }
1271
1272 #[test]
1273 fn margin_account_borrow_repay_response_error() {
1274 TOKIO_SHARED_RT.block_on(async {
1275 let client = MockBorrowRepayApiClient { force_error: true };
1276
1277 let params = MarginAccountBorrowRepayParams::builder(
1278 "USDT".to_string(),
1279 MarginAccountBorrowRepayIsIsolatedEnum::True,
1280 "1.0".to_string(),
1281 MarginAccountBorrowRepayTypeEnum::Borrow,
1282 )
1283 .build()
1284 .unwrap();
1285
1286 match client.margin_account_borrow_repay(params).await {
1287 Ok(_) => panic!("Expected an error"),
1288 Err(err) => {
1289 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1290 }
1291 }
1292 });
1293 }
1294
1295 #[test]
1296 fn query_borrow_repay_records_in_margin_account_required_params_success() {
1297 TOKIO_SHARED_RT.block_on(async {
1298 let client = MockBorrowRepayApiClient { force_error: false };
1299
1300 let params = QueryBorrowRepayRecordsInMarginAccountParams::builder(QueryBorrowRepayRecordsInMarginAccountTypeEnum::Borrow,).build().unwrap();
1301
1302 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_or_else(|_| serde_json::json!({}));
1303 let expected_response : models::QueryBorrowRepayRecordsInMarginAccountResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::QueryBorrowRepayRecordsInMarginAccountResponse");
1304
1305 let resp = client.query_borrow_repay_records_in_margin_account(params).await.expect("Expected a response");
1306 let data_future = resp.data();
1307 let actual_response = data_future.await.unwrap();
1308 assert_eq!(actual_response, expected_response);
1309 });
1310 }
1311
1312 #[test]
1313 fn query_borrow_repay_records_in_margin_account_optional_params_success() {
1314 TOKIO_SHARED_RT.block_on(async {
1315 let client = MockBorrowRepayApiClient { force_error: false };
1316
1317 let params = QueryBorrowRepayRecordsInMarginAccountParams::builder(QueryBorrowRepayRecordsInMarginAccountTypeEnum::Borrow,).asset("BNB".to_string()).isolated_symbol("BNBUSDT".to_string()).tx_id(1).start_time(1623319461670).end_time(1641782889000).current(1).size(10).recv_window(5000).build().unwrap();
1318
1319 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_or_else(|_| serde_json::json!({}));
1320 let expected_response : models::QueryBorrowRepayRecordsInMarginAccountResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::QueryBorrowRepayRecordsInMarginAccountResponse");
1321
1322 let resp = client.query_borrow_repay_records_in_margin_account(params).await.expect("Expected a response");
1323 let data_future = resp.data();
1324 let actual_response = data_future.await.unwrap();
1325 assert_eq!(actual_response, expected_response);
1326 });
1327 }
1328
1329 #[test]
1330 fn query_borrow_repay_records_in_margin_account_response_error() {
1331 TOKIO_SHARED_RT.block_on(async {
1332 let client = MockBorrowRepayApiClient { force_error: true };
1333
1334 let params = QueryBorrowRepayRecordsInMarginAccountParams::builder(
1335 QueryBorrowRepayRecordsInMarginAccountTypeEnum::Borrow,
1336 )
1337 .build()
1338 .unwrap();
1339
1340 match client
1341 .query_borrow_repay_records_in_margin_account(params)
1342 .await
1343 {
1344 Ok(_) => panic!("Expected an error"),
1345 Err(err) => {
1346 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1347 }
1348 }
1349 });
1350 }
1351
1352 #[test]
1353 fn query_margin_interest_rate_history_required_params_success() {
1354 TOKIO_SHARED_RT.block_on(async {
1355 let client = MockBorrowRepayApiClient { force_error: false };
1356
1357 let params = QueryMarginInterestRateHistoryParams::builder("BTC".to_string(),).build().unwrap();
1358
1359 let resp_json: Value = serde_json::from_str(r#"[{"asset":"BTC","dailyInterestRate":"0.00025000","timestamp":1611544731000,"vipLevel":1}]"#).unwrap_or_else(|_| serde_json::json!({}));
1360 let expected_response : Vec<models::QueryMarginInterestRateHistoryResponseInner> = serde_json::from_value(resp_json.clone()).expect("should parse into Vec<models::QueryMarginInterestRateHistoryResponseInner>");
1361
1362 let resp = client.query_margin_interest_rate_history(params).await.expect("Expected a response");
1363 let data_future = resp.data();
1364 let actual_response = data_future.await.unwrap();
1365 assert_eq!(actual_response, expected_response);
1366 });
1367 }
1368
1369 #[test]
1370 fn query_margin_interest_rate_history_optional_params_success() {
1371 TOKIO_SHARED_RT.block_on(async {
1372 let client = MockBorrowRepayApiClient { force_error: false };
1373
1374 let params = QueryMarginInterestRateHistoryParams::builder("BTC".to_string(),).vip_level(1).start_time(1623319461670).end_time(1641782889000).recv_window(5000).build().unwrap();
1375
1376 let resp_json: Value = serde_json::from_str(r#"[{"asset":"BTC","dailyInterestRate":"0.00025000","timestamp":1611544731000,"vipLevel":1}]"#).unwrap_or_else(|_| serde_json::json!({}));
1377 let expected_response : Vec<models::QueryMarginInterestRateHistoryResponseInner> = serde_json::from_value(resp_json.clone()).expect("should parse into Vec<models::QueryMarginInterestRateHistoryResponseInner>");
1378
1379 let resp = client.query_margin_interest_rate_history(params).await.expect("Expected a response");
1380 let data_future = resp.data();
1381 let actual_response = data_future.await.unwrap();
1382 assert_eq!(actual_response, expected_response);
1383 });
1384 }
1385
1386 #[test]
1387 fn query_margin_interest_rate_history_response_error() {
1388 TOKIO_SHARED_RT.block_on(async {
1389 let client = MockBorrowRepayApiClient { force_error: true };
1390
1391 let params = QueryMarginInterestRateHistoryParams::builder("BTC".to_string())
1392 .build()
1393 .unwrap();
1394
1395 match client.query_margin_interest_rate_history(params).await {
1396 Ok(_) => panic!("Expected an error"),
1397 Err(err) => {
1398 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1399 }
1400 }
1401 });
1402 }
1403
1404 #[test]
1405 fn query_max_borrow_required_params_success() {
1406 TOKIO_SHARED_RT.block_on(async {
1407 let client = MockBorrowRepayApiClient { force_error: false };
1408
1409 let params = QueryMaxBorrowParams::builder("BTC".to_string())
1410 .build()
1411 .unwrap();
1412
1413 let resp_json: Value =
1414 serde_json::from_str(r#"{"amount":"1.69248805","borrowLimit":"60"}"#)
1415 .unwrap_or_else(|_| serde_json::json!({}));
1416 let expected_response: models::QueryMaxBorrowResponse =
1417 serde_json::from_value(resp_json.clone())
1418 .expect("should parse into models::QueryMaxBorrowResponse");
1419
1420 let resp = client
1421 .query_max_borrow(params)
1422 .await
1423 .expect("Expected a response");
1424 let data_future = resp.data();
1425 let actual_response = data_future.await.unwrap();
1426 assert_eq!(actual_response, expected_response);
1427 });
1428 }
1429
1430 #[test]
1431 fn query_max_borrow_optional_params_success() {
1432 TOKIO_SHARED_RT.block_on(async {
1433 let client = MockBorrowRepayApiClient { force_error: false };
1434
1435 let params = QueryMaxBorrowParams::builder("BTC".to_string())
1436 .isolated_symbol("BTCUSDT".to_string())
1437 .recv_window(5000)
1438 .build()
1439 .unwrap();
1440
1441 let resp_json: Value =
1442 serde_json::from_str(r#"{"amount":"1.69248805","borrowLimit":"60"}"#)
1443 .unwrap_or_else(|_| serde_json::json!({}));
1444 let expected_response: models::QueryMaxBorrowResponse =
1445 serde_json::from_value(resp_json.clone())
1446 .expect("should parse into models::QueryMaxBorrowResponse");
1447
1448 let resp = client
1449 .query_max_borrow(params)
1450 .await
1451 .expect("Expected a response");
1452 let data_future = resp.data();
1453 let actual_response = data_future.await.unwrap();
1454 assert_eq!(actual_response, expected_response);
1455 });
1456 }
1457
1458 #[test]
1459 fn query_max_borrow_response_error() {
1460 TOKIO_SHARED_RT.block_on(async {
1461 let client = MockBorrowRepayApiClient { force_error: true };
1462
1463 let params = QueryMaxBorrowParams::builder("BTC".to_string())
1464 .build()
1465 .unwrap();
1466
1467 match client.query_max_borrow(params).await {
1468 Ok(_) => panic!("Expected an error"),
1469 Err(err) => {
1470 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1471 }
1472 }
1473 });
1474 }
1475}