Skip to main content

binance_sdk/margin_trading/rest_api/apis/
transfer_api.rs

1/*
2 * Margin REST API
3 *
4 * Access account information, borrow and repay assets, and trade with Binance Margin.
5 *
6 * The version of the OpenAPI document: 1.0.0
7 *
8 *
9 * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
10 * https://openapi-generator.tech
11 * Do not edit the class manually.
12 */
13
14#![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 TransferApi: Send + Sync {
34    async fn get_cross_margin_transfer_history(
35        &self,
36        params: GetCrossMarginTransferHistoryParams,
37    ) -> anyhow::Result<RestApiResponse<models::GetCrossMarginTransferHistoryResponse>>;
38    async fn query_max_transfer_out_amount(
39        &self,
40        params: QueryMaxTransferOutAmountParams,
41    ) -> anyhow::Result<RestApiResponse<models::QueryMaxTransferOutAmountResponse>>;
42}
43
44#[derive(Debug, Clone)]
45pub struct TransferApiClient {
46    configuration: ConfigurationRestApi,
47}
48
49impl TransferApiClient {
50    pub fn new(configuration: ConfigurationRestApi) -> Self {
51        Self { configuration }
52    }
53}
54
55#[allow(non_camel_case_types)]
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub enum GetCrossMarginTransferHistoryTypeEnum {
58    #[serde(rename = "ROLL_IN")]
59    RollIn,
60    #[serde(rename = "ROLL_OUT")]
61    RollOut,
62}
63
64impl GetCrossMarginTransferHistoryTypeEnum {
65    #[must_use]
66    pub fn as_str(&self) -> &'static str {
67        match self {
68            Self::RollIn => "ROLL_IN",
69            Self::RollOut => "ROLL_OUT",
70        }
71    }
72}
73
74impl std::str::FromStr for GetCrossMarginTransferHistoryTypeEnum {
75    type Err = Box<dyn std::error::Error + Send + Sync>;
76
77    fn from_str(s: &str) -> Result<Self, Self::Err> {
78        match s {
79            "ROLL_IN" => Ok(Self::RollIn),
80            "ROLL_OUT" => Ok(Self::RollOut),
81            other => {
82                Err(format!("invalid GetCrossMarginTransferHistoryTypeEnum: {}", other).into())
83            }
84        }
85    }
86}
87
88/// Request parameters for the [`get_cross_margin_transfer_history`] operation.
89///
90/// This struct holds all of the inputs you can pass when calling
91/// [`get_cross_margin_transfer_history`](#method.get_cross_margin_transfer_history).
92#[derive(Clone, Debug, Builder, Deserialize, Default)]
93#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
94pub struct GetCrossMarginTransferHistoryParams {
95    ///
96    /// The `asset` parameter.
97    ///
98    /// This field is **optional.
99    #[builder(setter(into), default)]
100    #[serde(rename = "asset", default)]
101    pub asset: Option<String>,
102    ///
103    /// The `r#type` parameter.
104    ///
105    /// This field is **optional.
106    #[builder(setter(into), default)]
107    #[serde(rename = "type", default)]
108    pub r#type: Option<GetCrossMarginTransferHistoryTypeEnum>,
109    ///
110    /// The `start_time` parameter.
111    ///
112    /// This field is **optional.
113    #[builder(setter(into), default)]
114    #[serde(rename = "startTime", default)]
115    pub start_time: Option<i64>,
116    ///
117    /// The `end_time` parameter.
118    ///
119    /// This field is **optional.
120    #[builder(setter(into), default)]
121    #[serde(rename = "endTime", default)]
122    pub end_time: Option<i64>,
123    ///
124    /// The `current` parameter.
125    ///
126    /// This field is **optional.
127    #[builder(setter(into), default)]
128    #[serde(rename = "current", default)]
129    pub current: Option<i64>,
130    ///
131    /// The `size` parameter.
132    ///
133    /// This field is **optional.
134    #[builder(setter(into), default)]
135    #[serde(rename = "size", default)]
136    pub size: Option<i64>,
137    ///
138    /// The `isolated_symbol` parameter.
139    ///
140    /// This field is **optional.
141    #[builder(setter(into), default)]
142    #[serde(rename = "isolatedSymbol", default)]
143    pub isolated_symbol: Option<String>,
144    ///
145    /// The `recv_window` parameter.
146    ///
147    /// This field is **optional.
148    #[builder(setter(into), default)]
149    #[serde(rename = "recvWindow", default)]
150    pub recv_window: Option<i64>,
151}
152
153impl GetCrossMarginTransferHistoryParams {
154    /// Create a builder for [`get_cross_margin_transfer_history`].
155    ///
156    #[must_use]
157    pub fn builder() -> GetCrossMarginTransferHistoryParamsBuilder {
158        GetCrossMarginTransferHistoryParamsBuilder::default()
159    }
160}
161/// Request parameters for the [`query_max_transfer_out_amount`] operation.
162///
163/// This struct holds all of the inputs you can pass when calling
164/// [`query_max_transfer_out_amount`](#method.query_max_transfer_out_amount).
165#[derive(Clone, Debug, Builder, Deserialize)]
166#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
167pub struct QueryMaxTransferOutAmountParams {
168    ///
169    /// The `asset` parameter.
170    ///
171    /// This field is **required.
172    #[builder(setter(into))]
173    #[serde(rename = "asset")]
174    pub asset: String,
175    ///
176    /// The `isolated_symbol` parameter.
177    ///
178    /// This field is **optional.
179    #[builder(setter(into), default)]
180    #[serde(rename = "isolatedSymbol", default)]
181    pub isolated_symbol: Option<String>,
182    ///
183    /// The `recv_window` parameter.
184    ///
185    /// This field is **optional.
186    #[builder(setter(into), default)]
187    #[serde(rename = "recvWindow", default)]
188    pub recv_window: Option<i64>,
189}
190
191impl QueryMaxTransferOutAmountParams {
192    /// Create a builder for [`query_max_transfer_out_amount`].
193    ///
194    /// Required parameters:
195    ///
196    /// * `asset` — String
197    ///
198    #[must_use]
199    pub fn builder(asset: String) -> QueryMaxTransferOutAmountParamsBuilder {
200        QueryMaxTransferOutAmountParamsBuilder::default().asset(asset)
201    }
202}
203
204#[async_trait]
205impl TransferApi for TransferApiClient {
206    async fn get_cross_margin_transfer_history(
207        &self,
208        params: GetCrossMarginTransferHistoryParams,
209    ) -> anyhow::Result<RestApiResponse<models::GetCrossMarginTransferHistoryResponse>> {
210        let GetCrossMarginTransferHistoryParams {
211            asset,
212            r#type,
213            start_time,
214            end_time,
215            current,
216            size,
217            isolated_symbol,
218            recv_window,
219        } = params;
220
221        let mut query_params = BTreeMap::new();
222        let body_params = BTreeMap::new();
223
224        if let Some(rw) = asset {
225            query_params.insert("asset".to_string(), json!(rw));
226        }
227
228        if let Some(rw) = r#type {
229            query_params.insert("type".to_string(), json!(rw));
230        }
231
232        if let Some(rw) = start_time {
233            query_params.insert("startTime".to_string(), json!(rw));
234        }
235
236        if let Some(rw) = end_time {
237            query_params.insert("endTime".to_string(), json!(rw));
238        }
239
240        if let Some(rw) = current {
241            query_params.insert("current".to_string(), json!(rw));
242        }
243
244        if let Some(rw) = size {
245            query_params.insert("size".to_string(), json!(rw));
246        }
247
248        if let Some(rw) = isolated_symbol {
249            query_params.insert("isolatedSymbol".to_string(), json!(rw));
250        }
251
252        if let Some(rw) = recv_window {
253            query_params.insert("recvWindow".to_string(), json!(rw));
254        }
255
256        send_request::<models::GetCrossMarginTransferHistoryResponse>(
257            &self.configuration,
258            "/sapi/v1/margin/transfer",
259            reqwest::Method::GET,
260            query_params,
261            body_params,
262            if HAS_TIME_UNIT {
263                self.configuration.time_unit
264            } else {
265                None
266            },
267            true,
268        )
269        .await
270    }
271
272    async fn query_max_transfer_out_amount(
273        &self,
274        params: QueryMaxTransferOutAmountParams,
275    ) -> anyhow::Result<RestApiResponse<models::QueryMaxTransferOutAmountResponse>> {
276        let QueryMaxTransferOutAmountParams {
277            asset,
278            isolated_symbol,
279            recv_window,
280        } = params;
281
282        let mut query_params = BTreeMap::new();
283        let body_params = BTreeMap::new();
284
285        query_params.insert("asset".to_string(), json!(asset));
286
287        if let Some(rw) = isolated_symbol {
288            query_params.insert("isolatedSymbol".to_string(), json!(rw));
289        }
290
291        if let Some(rw) = recv_window {
292            query_params.insert("recvWindow".to_string(), json!(rw));
293        }
294
295        send_request::<models::QueryMaxTransferOutAmountResponse>(
296            &self.configuration,
297            "/sapi/v1/margin/maxTransferable",
298            reqwest::Method::GET,
299            query_params,
300            body_params,
301            if HAS_TIME_UNIT {
302                self.configuration.time_unit
303            } else {
304                None
305            },
306            true,
307        )
308        .await
309    }
310}
311
312#[cfg(all(test, feature = "margin_trading"))]
313mod tests {
314    use super::*;
315    use crate::TOKIO_SHARED_RT;
316    use crate::{errors::ConnectorError, models::DataFuture, models::RestApiRateLimit};
317    use async_trait::async_trait;
318    use std::collections::HashMap;
319
320    struct DummyRestApiResponse<T> {
321        inner: Box<dyn FnOnce() -> DataFuture<Result<T, ConnectorError>> + Send + Sync>,
322        status: u16,
323        headers: HashMap<String, String>,
324        rate_limits: Option<Vec<RestApiRateLimit>>,
325    }
326
327    impl<T> From<DummyRestApiResponse<T>> for RestApiResponse<T> {
328        fn from(dummy: DummyRestApiResponse<T>) -> Self {
329            Self {
330                data_fn: dummy.inner,
331                status: dummy.status,
332                headers: dummy.headers,
333                rate_limits: dummy.rate_limits,
334            }
335        }
336    }
337
338    struct MockTransferApiClient {
339        force_error: bool,
340    }
341
342    #[async_trait]
343    impl TransferApi for MockTransferApiClient {
344        async fn get_cross_margin_transfer_history(
345            &self,
346            _params: GetCrossMarginTransferHistoryParams,
347        ) -> anyhow::Result<RestApiResponse<models::GetCrossMarginTransferHistoryResponse>>
348        {
349            if self.force_error {
350                return Err(ConnectorError::ConnectorClientError {
351                    msg: "ResponseError".to_string(),
352                    code: None,
353                }
354                .into());
355            }
356
357            let resp_json: Value = serde_json::from_str(r#"{"rows":[{"amount":"0.10000000","asset":"BNB","status":"CONFIRMED","timestamp":1566898617,"txId":5240372201,"type":"ROLL_IN","transFrom":"SPOT","transTo":"ISOLATED_MARGIN","fromSymbol":"BNBUSDT","toSymbol":"BTCUSDT"}],"total":3}"#).unwrap_or_else(|_| serde_json::json!({}));
358            let dummy_response: models::GetCrossMarginTransferHistoryResponse =
359                serde_json::from_value(resp_json.clone())
360                    .expect("should parse into models::GetCrossMarginTransferHistoryResponse");
361
362            let dummy = DummyRestApiResponse {
363                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
364                status: 200,
365                headers: HashMap::new(),
366                rate_limits: None,
367            };
368
369            Ok(dummy.into())
370        }
371
372        async fn query_max_transfer_out_amount(
373            &self,
374            _params: QueryMaxTransferOutAmountParams,
375        ) -> anyhow::Result<RestApiResponse<models::QueryMaxTransferOutAmountResponse>> {
376            if self.force_error {
377                return Err(ConnectorError::ConnectorClientError {
378                    msg: "ResponseError".to_string(),
379                    code: None,
380                }
381                .into());
382            }
383
384            let resp_json: Value = serde_json::from_str(r#"{"amount":"3.59498107"}"#)
385                .unwrap_or_else(|_| serde_json::json!({}));
386            let dummy_response: models::QueryMaxTransferOutAmountResponse =
387                serde_json::from_value(resp_json.clone())
388                    .expect("should parse into models::QueryMaxTransferOutAmountResponse");
389
390            let dummy = DummyRestApiResponse {
391                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
392                status: 200,
393                headers: HashMap::new(),
394                rate_limits: None,
395            };
396
397            Ok(dummy.into())
398        }
399    }
400
401    #[test]
402    fn get_cross_margin_transfer_history_required_params_success() {
403        TOKIO_SHARED_RT.block_on(async {
404            let client = MockTransferApiClient { force_error: false };
405
406            let params = GetCrossMarginTransferHistoryParams::builder().build().unwrap();
407
408            let resp_json: Value = serde_json::from_str(r#"{"rows":[{"amount":"0.10000000","asset":"BNB","status":"CONFIRMED","timestamp":1566898617,"txId":5240372201,"type":"ROLL_IN","transFrom":"SPOT","transTo":"ISOLATED_MARGIN","fromSymbol":"BNBUSDT","toSymbol":"BTCUSDT"}],"total":3}"#).unwrap_or_else(|_| serde_json::json!({}));
409            let expected_response : models::GetCrossMarginTransferHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::GetCrossMarginTransferHistoryResponse");
410
411            let resp = client.get_cross_margin_transfer_history(params).await.expect("Expected a response");
412            let data_future = resp.data();
413            let actual_response = data_future.await.unwrap();
414            assert_eq!(actual_response, expected_response);
415        });
416    }
417
418    #[test]
419    fn get_cross_margin_transfer_history_optional_params_success() {
420        TOKIO_SHARED_RT.block_on(async {
421            let client = MockTransferApiClient { force_error: false };
422
423            let params = GetCrossMarginTransferHistoryParams::builder().asset("BNB".to_string()).r#type(GetCrossMarginTransferHistoryTypeEnum::RollIn).start_time(1623319461670).end_time(1641782889000).current(1).size(10).isolated_symbol("BNBUSDT".to_string()).recv_window(5000).build().unwrap();
424
425            let resp_json: Value = serde_json::from_str(r#"{"rows":[{"amount":"0.10000000","asset":"BNB","status":"CONFIRMED","timestamp":1566898617,"txId":5240372201,"type":"ROLL_IN","transFrom":"SPOT","transTo":"ISOLATED_MARGIN","fromSymbol":"BNBUSDT","toSymbol":"BTCUSDT"}],"total":3}"#).unwrap_or_else(|_| serde_json::json!({}));
426            let expected_response : models::GetCrossMarginTransferHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::GetCrossMarginTransferHistoryResponse");
427
428            let resp = client.get_cross_margin_transfer_history(params).await.expect("Expected a response");
429            let data_future = resp.data();
430            let actual_response = data_future.await.unwrap();
431            assert_eq!(actual_response, expected_response);
432        });
433    }
434
435    #[test]
436    fn get_cross_margin_transfer_history_response_error() {
437        TOKIO_SHARED_RT.block_on(async {
438            let client = MockTransferApiClient { force_error: true };
439
440            let params = GetCrossMarginTransferHistoryParams::builder()
441                .build()
442                .unwrap();
443
444            match client.get_cross_margin_transfer_history(params).await {
445                Ok(_) => panic!("Expected an error"),
446                Err(err) => {
447                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
448                }
449            }
450        });
451    }
452
453    #[test]
454    fn query_max_transfer_out_amount_required_params_success() {
455        TOKIO_SHARED_RT.block_on(async {
456            let client = MockTransferApiClient { force_error: false };
457
458            let params = QueryMaxTransferOutAmountParams::builder("BTC".to_string())
459                .build()
460                .unwrap();
461
462            let resp_json: Value = serde_json::from_str(r#"{"amount":"3.59498107"}"#)
463                .unwrap_or_else(|_| serde_json::json!({}));
464            let expected_response: models::QueryMaxTransferOutAmountResponse =
465                serde_json::from_value(resp_json.clone())
466                    .expect("should parse into models::QueryMaxTransferOutAmountResponse");
467
468            let resp = client
469                .query_max_transfer_out_amount(params)
470                .await
471                .expect("Expected a response");
472            let data_future = resp.data();
473            let actual_response = data_future.await.unwrap();
474            assert_eq!(actual_response, expected_response);
475        });
476    }
477
478    #[test]
479    fn query_max_transfer_out_amount_optional_params_success() {
480        TOKIO_SHARED_RT.block_on(async {
481            let client = MockTransferApiClient { force_error: false };
482
483            let params = QueryMaxTransferOutAmountParams::builder("BTC".to_string())
484                .isolated_symbol("BTCUSDT".to_string())
485                .recv_window(5000)
486                .build()
487                .unwrap();
488
489            let resp_json: Value = serde_json::from_str(r#"{"amount":"3.59498107"}"#)
490                .unwrap_or_else(|_| serde_json::json!({}));
491            let expected_response: models::QueryMaxTransferOutAmountResponse =
492                serde_json::from_value(resp_json.clone())
493                    .expect("should parse into models::QueryMaxTransferOutAmountResponse");
494
495            let resp = client
496                .query_max_transfer_out_amount(params)
497                .await
498                .expect("Expected a response");
499            let data_future = resp.data();
500            let actual_response = data_future.await.unwrap();
501            assert_eq!(actual_response, expected_response);
502        });
503    }
504
505    #[test]
506    fn query_max_transfer_out_amount_response_error() {
507        TOKIO_SHARED_RT.block_on(async {
508            let client = MockTransferApiClient { force_error: true };
509
510            let params = QueryMaxTransferOutAmountParams::builder("BTC".to_string())
511                .build()
512                .unwrap();
513
514            match client.query_max_transfer_out_amount(params).await {
515                Ok(_) => panic!("Expected an error"),
516                Err(err) => {
517                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
518                }
519            }
520        });
521    }
522}