Skip to main content

binance_sdk/convert/rest_api/apis/
trade_api.rs

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