Skip to main content

binance_sdk/convert/rest_api/apis/
trade_api.rs

1/*
2 * Convert REST API
3 *
4 * Request quotes and execute cryptocurrency conversions via the 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#[allow(non_camel_case_types)]
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub enum PlaceLimitOrderSideEnum {
78    #[serde(rename = "BUY")]
79    Buy,
80    #[serde(rename = "SELL")]
81    Sell,
82}
83
84impl PlaceLimitOrderSideEnum {
85    #[must_use]
86    pub fn as_str(&self) -> &'static str {
87        match self {
88            Self::Buy => "BUY",
89            Self::Sell => "SELL",
90        }
91    }
92}
93
94impl std::str::FromStr for PlaceLimitOrderSideEnum {
95    type Err = Box<dyn std::error::Error + Send + Sync>;
96
97    fn from_str(s: &str) -> Result<Self, Self::Err> {
98        match s {
99            "BUY" => Ok(Self::Buy),
100            "SELL" => Ok(Self::Sell),
101            other => Err(format!("invalid PlaceLimitOrderSideEnum: {}", other).into()),
102        }
103    }
104}
105
106#[allow(non_camel_case_types)]
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub enum PlaceLimitOrderExpiredTypeEnum {
109    #[serde(rename = "1_D")]
110    ExpiredType1D,
111    #[serde(rename = "3_D")]
112    ExpiredType3D,
113    #[serde(rename = "7_D")]
114    ExpiredType7D,
115    #[serde(rename = "30_D")]
116    ExpiredType30D,
117}
118
119impl PlaceLimitOrderExpiredTypeEnum {
120    #[must_use]
121    pub fn as_str(&self) -> &'static str {
122        match self {
123            Self::ExpiredType1D => "1_D",
124            Self::ExpiredType3D => "3_D",
125            Self::ExpiredType7D => "7_D",
126            Self::ExpiredType30D => "30_D",
127        }
128    }
129}
130
131impl std::str::FromStr for PlaceLimitOrderExpiredTypeEnum {
132    type Err = Box<dyn std::error::Error + Send + Sync>;
133
134    fn from_str(s: &str) -> Result<Self, Self::Err> {
135        match s {
136            "1_D" => Ok(Self::ExpiredType1D),
137            "3_D" => Ok(Self::ExpiredType3D),
138            "7_D" => Ok(Self::ExpiredType7D),
139            "30_D" => Ok(Self::ExpiredType30D),
140            other => Err(format!("invalid PlaceLimitOrderExpiredTypeEnum: {}", other).into()),
141        }
142    }
143}
144
145#[allow(non_camel_case_types)]
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub enum PlaceLimitOrderWalletTypeEnum {
148    #[serde(rename = "SPOT")]
149    Spot,
150    #[serde(rename = "FUNDING")]
151    Funding,
152    #[serde(rename = "EARN")]
153    Earn,
154    #[serde(rename = "SPOT_FUNDING")]
155    SpotFunding,
156    #[serde(rename = "FUNDING_EARN")]
157    FundingEarn,
158    #[serde(rename = "SPOT_FUNDING_EARN")]
159    SpotFundingEarn,
160    #[serde(rename = "SPOT_EARN")]
161    SpotEarn,
162}
163
164impl PlaceLimitOrderWalletTypeEnum {
165    #[must_use]
166    pub fn as_str(&self) -> &'static str {
167        match self {
168            Self::Spot => "SPOT",
169            Self::Funding => "FUNDING",
170            Self::Earn => "EARN",
171            Self::SpotFunding => "SPOT_FUNDING",
172            Self::FundingEarn => "FUNDING_EARN",
173            Self::SpotFundingEarn => "SPOT_FUNDING_EARN",
174            Self::SpotEarn => "SPOT_EARN",
175        }
176    }
177}
178
179impl std::str::FromStr for PlaceLimitOrderWalletTypeEnum {
180    type Err = Box<dyn std::error::Error + Send + Sync>;
181
182    fn from_str(s: &str) -> Result<Self, Self::Err> {
183        match s {
184            "SPOT" => Ok(Self::Spot),
185            "FUNDING" => Ok(Self::Funding),
186            "EARN" => Ok(Self::Earn),
187            "SPOT_FUNDING" => Ok(Self::SpotFunding),
188            "FUNDING_EARN" => Ok(Self::FundingEarn),
189            "SPOT_FUNDING_EARN" => Ok(Self::SpotFundingEarn),
190            "SPOT_EARN" => Ok(Self::SpotEarn),
191            other => Err(format!("invalid PlaceLimitOrderWalletTypeEnum: {}", other).into()),
192        }
193    }
194}
195
196#[allow(non_camel_case_types)]
197#[derive(Debug, Clone, Serialize, Deserialize)]
198pub enum SendQuoteRequestWalletTypeEnum {
199    #[serde(rename = "SPOT")]
200    Spot,
201    #[serde(rename = "FUNDING")]
202    Funding,
203    #[serde(rename = "EARN")]
204    Earn,
205    #[serde(rename = "SPOT_FUNDING")]
206    SpotFunding,
207    #[serde(rename = "FUNDING_EARN")]
208    FundingEarn,
209    #[serde(rename = "SPOT_FUNDING_EARN")]
210    SpotFundingEarn,
211    #[serde(rename = "SPOT_EARN")]
212    SpotEarn,
213}
214
215impl SendQuoteRequestWalletTypeEnum {
216    #[must_use]
217    pub fn as_str(&self) -> &'static str {
218        match self {
219            Self::Spot => "SPOT",
220            Self::Funding => "FUNDING",
221            Self::Earn => "EARN",
222            Self::SpotFunding => "SPOT_FUNDING",
223            Self::FundingEarn => "FUNDING_EARN",
224            Self::SpotFundingEarn => "SPOT_FUNDING_EARN",
225            Self::SpotEarn => "SPOT_EARN",
226        }
227    }
228}
229
230impl std::str::FromStr for SendQuoteRequestWalletTypeEnum {
231    type Err = Box<dyn std::error::Error + Send + Sync>;
232
233    fn from_str(s: &str) -> Result<Self, Self::Err> {
234        match s {
235            "SPOT" => Ok(Self::Spot),
236            "FUNDING" => Ok(Self::Funding),
237            "EARN" => Ok(Self::Earn),
238            "SPOT_FUNDING" => Ok(Self::SpotFunding),
239            "FUNDING_EARN" => Ok(Self::FundingEarn),
240            "SPOT_FUNDING_EARN" => Ok(Self::SpotFundingEarn),
241            "SPOT_EARN" => Ok(Self::SpotEarn),
242            other => Err(format!("invalid SendQuoteRequestWalletTypeEnum: {}", other).into()),
243        }
244    }
245}
246
247#[allow(non_camel_case_types)]
248#[derive(Debug, Clone, Serialize, Deserialize)]
249pub enum SendQuoteRequestValidTimeEnum {
250    #[serde(rename = "10s")]
251    ValidTime10s,
252    #[serde(rename = "30s")]
253    ValidTime30s,
254    #[serde(rename = "1m")]
255    ValidTime1m,
256}
257
258impl SendQuoteRequestValidTimeEnum {
259    #[must_use]
260    pub fn as_str(&self) -> &'static str {
261        match self {
262            Self::ValidTime10s => "10s",
263            Self::ValidTime30s => "30s",
264            Self::ValidTime1m => "1m",
265        }
266    }
267}
268
269impl std::str::FromStr for SendQuoteRequestValidTimeEnum {
270    type Err = Box<dyn std::error::Error + Send + Sync>;
271
272    fn from_str(s: &str) -> Result<Self, Self::Err> {
273        match s {
274            "10s" => Ok(Self::ValidTime10s),
275            "30s" => Ok(Self::ValidTime30s),
276            "1m" => Ok(Self::ValidTime1m),
277            other => Err(format!("invalid SendQuoteRequestValidTimeEnum: {}", other).into()),
278        }
279    }
280}
281
282/// Request parameters for the [`accept_quote`] operation.
283///
284/// This struct holds all of the inputs you can pass when calling
285/// [`accept_quote`](#method.accept_quote).
286#[derive(Clone, Debug, Builder, Deserialize)]
287#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
288pub struct AcceptQuoteParams {
289    ///
290    /// The `quote_id` parameter.
291    ///
292    /// This field is **required.
293    #[builder(setter(into))]
294    #[serde(rename = "quoteId")]
295    pub quote_id: String,
296    /// Request validity window in milliseconds
297    ///
298    /// This field is **optional.
299    #[builder(setter(into), default)]
300    #[serde(rename = "recvWindow", default)]
301    pub recv_window: Option<i64>,
302}
303
304impl AcceptQuoteParams {
305    /// Create a builder for [`accept_quote`].
306    ///
307    /// Required parameters:
308    ///
309    /// * `quote_id` — String
310    ///
311    #[must_use]
312    pub fn builder(quote_id: String) -> AcceptQuoteParamsBuilder {
313        AcceptQuoteParamsBuilder::default().quote_id(quote_id)
314    }
315}
316/// Request parameters for the [`cancel_limit_order`] operation.
317///
318/// This struct holds all of the inputs you can pass when calling
319/// [`cancel_limit_order`](#method.cancel_limit_order).
320#[derive(Clone, Debug, Builder, Deserialize)]
321#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
322pub struct CancelLimitOrderParams {
323    /// The orderId from `placeOrder` api
324    ///
325    /// This field is **required.
326    #[builder(setter(into))]
327    #[serde(rename = "orderId")]
328    pub order_id: i64,
329    /// Request validity window in milliseconds
330    ///
331    /// This field is **optional.
332    #[builder(setter(into), default)]
333    #[serde(rename = "recvWindow", default)]
334    pub recv_window: Option<i64>,
335}
336
337impl CancelLimitOrderParams {
338    /// Create a builder for [`cancel_limit_order`].
339    ///
340    /// Required parameters:
341    ///
342    /// * `order_id` — The orderId from `placeOrder` api
343    ///
344    #[must_use]
345    pub fn builder(order_id: i64) -> CancelLimitOrderParamsBuilder {
346        CancelLimitOrderParamsBuilder::default().order_id(order_id)
347    }
348}
349/// Request parameters for the [`get_convert_trade_history`] operation.
350///
351/// This struct holds all of the inputs you can pass when calling
352/// [`get_convert_trade_history`](#method.get_convert_trade_history).
353#[derive(Clone, Debug, Builder, Deserialize)]
354#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
355pub struct GetConvertTradeHistoryParams {
356    ///
357    /// The `start_time` parameter.
358    ///
359    /// This field is **required.
360    #[builder(setter(into))]
361    #[serde(rename = "startTime")]
362    pub start_time: i64,
363    ///
364    /// The `end_time` parameter.
365    ///
366    /// This field is **required.
367    #[builder(setter(into))]
368    #[serde(rename = "endTime")]
369    pub end_time: i64,
370    /// Number of records to return
371    ///
372    /// This field is **optional.
373    #[builder(setter(into), default)]
374    #[serde(rename = "limit", default)]
375    pub limit: Option<i64>,
376    /// Request validity window in milliseconds
377    ///
378    /// This field is **optional.
379    #[builder(setter(into), default)]
380    #[serde(rename = "recvWindow", default)]
381    pub recv_window: Option<i64>,
382}
383
384impl GetConvertTradeHistoryParams {
385    /// Create a builder for [`get_convert_trade_history`].
386    ///
387    /// Required parameters:
388    ///
389    /// * `start_time` — i64
390    /// * `end_time` — i64
391    ///
392    #[must_use]
393    pub fn builder(start_time: i64, end_time: i64) -> GetConvertTradeHistoryParamsBuilder {
394        GetConvertTradeHistoryParamsBuilder::default()
395            .start_time(start_time)
396            .end_time(end_time)
397    }
398}
399/// Request parameters for the [`order_status`] operation.
400///
401/// This struct holds all of the inputs you can pass when calling
402/// [`order_status`](#method.order_status).
403#[derive(Clone, Debug, Builder, Deserialize, Default)]
404#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
405pub struct OrderStatusParams {
406    /// Either orderId or quoteId is required
407    ///
408    /// This field is **optional.
409    #[builder(setter(into), default)]
410    #[serde(rename = "orderId", default)]
411    pub order_id: Option<String>,
412    /// Either orderId or quoteId is required
413    ///
414    /// This field is **optional.
415    #[builder(setter(into), default)]
416    #[serde(rename = "quoteId", default)]
417    pub quote_id: Option<String>,
418}
419
420impl OrderStatusParams {
421    /// Create a builder for [`order_status`].
422    ///
423    #[must_use]
424    pub fn builder() -> OrderStatusParamsBuilder {
425        OrderStatusParamsBuilder::default()
426    }
427}
428/// Request parameters for the [`place_limit_order`] operation.
429///
430/// This struct holds all of the inputs you can pass when calling
431/// [`place_limit_order`](#method.place_limit_order).
432#[derive(Clone, Debug, Builder, Deserialize)]
433#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
434pub struct PlaceLimitOrderParams {
435    /// base asset (use the response `fromIsBase` from `GET /sapi/v1/convert/exchangeInfo` api to check
436    /// which one is baseAsset )
437    ///
438    /// This field is **required.
439    #[builder(setter(into))]
440    #[serde(rename = "baseAsset")]
441    pub base_asset: String,
442    /// quote asset
443    ///
444    /// This field is **required.
445    #[builder(setter(into))]
446    #[serde(rename = "quoteAsset")]
447    pub quote_asset: String,
448    /// Symbol limit price (from baseAsset to quoteAsset)
449    ///
450    /// This field is **required.
451    #[builder(setter(into))]
452    #[serde(rename = "limitPrice")]
453    pub limit_price: rust_decimal::Decimal,
454    /// `BUY` or `SELL`
455    ///
456    /// This field is **required.
457    #[builder(setter(into))]
458    #[serde(rename = "side")]
459    pub side: PlaceLimitOrderSideEnum,
460    /// Order expiry duration. `1_D`, `3_D`, `7_D`, `30_D` (D means day)
461    ///
462    /// This field is **required.
463    #[builder(setter(into))]
464    #[serde(rename = "expiredType")]
465    pub expired_type: PlaceLimitOrderExpiredTypeEnum,
466    /// Base asset amount. (One of `baseAmount` or `quoteAmount` is required)
467    ///
468    /// This field is **optional.
469    #[builder(setter(into), default)]
470    #[serde(rename = "baseAmount", default)]
471    pub base_amount: Option<rust_decimal::Decimal>,
472    /// Quote asset amount. (One of `baseAmount` or `quoteAmount` is required)
473    ///
474    /// This field is **optional.
475    #[builder(setter(into), default)]
476    #[serde(rename = "quoteAmount", default)]
477    pub quote_amount: Option<rust_decimal::Decimal>,
478    /// Wallet to use for payment. Supported values: `SPOT`, `FUNDING`, `EARN`.
479    /// Combined wallets also supported: `SPOT_FUNDING`, `FUNDING_EARN`, `SPOT_FUNDING_EARN`, `SPOT_EARN`. Default is `SPOT`.
480    ///
481    /// This field is **optional.
482    #[builder(setter(into), default)]
483    #[serde(rename = "walletType", default)]
484    pub wallet_type: Option<PlaceLimitOrderWalletTypeEnum>,
485    /// Request validity window in milliseconds
486    ///
487    /// This field is **optional.
488    #[builder(setter(into), default)]
489    #[serde(rename = "recvWindow", default)]
490    pub recv_window: Option<i64>,
491}
492
493impl PlaceLimitOrderParams {
494    /// Create a builder for [`place_limit_order`].
495    ///
496    /// Required parameters:
497    ///
498    /// * `base_asset` — base asset (use the response `fromIsBase` from `GET /sapi/v1/convert/exchangeInfo` api to check which one is baseAsset )
499    /// * `quote_asset` — quote asset
500    /// * `limit_price` — Symbol limit price (from baseAsset to quoteAsset)
501    /// * `side` — `BUY` or `SELL`
502    /// * `expired_type` — Order expiry duration. `1_D`, `3_D`, `7_D`, `30_D` (D means day)
503    ///
504    #[must_use]
505    pub fn builder(
506        base_asset: String,
507        quote_asset: String,
508        limit_price: rust_decimal::Decimal,
509        side: PlaceLimitOrderSideEnum,
510        expired_type: PlaceLimitOrderExpiredTypeEnum,
511    ) -> PlaceLimitOrderParamsBuilder {
512        PlaceLimitOrderParamsBuilder::default()
513            .base_asset(base_asset)
514            .quote_asset(quote_asset)
515            .limit_price(limit_price)
516            .side(side)
517            .expired_type(expired_type)
518    }
519}
520/// Request parameters for the [`query_limit_open_orders`] operation.
521///
522/// This struct holds all of the inputs you can pass when calling
523/// [`query_limit_open_orders`](#method.query_limit_open_orders).
524#[derive(Clone, Debug, Builder, Deserialize, Default)]
525#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
526pub struct QueryLimitOpenOrdersParams {
527    /// Request validity window in milliseconds
528    ///
529    /// This field is **optional.
530    #[builder(setter(into), default)]
531    #[serde(rename = "recvWindow", default)]
532    pub recv_window: Option<i64>,
533}
534
535impl QueryLimitOpenOrdersParams {
536    /// Create a builder for [`query_limit_open_orders`].
537    ///
538    #[must_use]
539    pub fn builder() -> QueryLimitOpenOrdersParamsBuilder {
540        QueryLimitOpenOrdersParamsBuilder::default()
541    }
542}
543/// Request parameters for the [`send_quote_request`] operation.
544///
545/// This struct holds all of the inputs you can pass when calling
546/// [`send_quote_request`](#method.send_quote_request).
547#[derive(Clone, Debug, Builder, Deserialize)]
548#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
549pub struct SendQuoteRequestParams {
550    ///
551    /// The `from_asset` parameter.
552    ///
553    /// This field is **required.
554    #[builder(setter(into))]
555    #[serde(rename = "fromAsset")]
556    pub from_asset: String,
557    ///
558    /// The `to_asset` parameter.
559    ///
560    /// This field is **required.
561    #[builder(setter(into))]
562    #[serde(rename = "toAsset")]
563    pub to_asset: String,
564    /// When specified, it is the amount you will be debited after the conversion
565    ///
566    /// This field is **optional.
567    #[builder(setter(into), default)]
568    #[serde(rename = "fromAmount", default)]
569    pub from_amount: Option<rust_decimal::Decimal>,
570    /// When specified, it is the amount you will be credited after the conversion
571    ///
572    /// This field is **optional.
573    #[builder(setter(into), default)]
574    #[serde(rename = "toAmount", default)]
575    pub to_amount: Option<rust_decimal::Decimal>,
576    /// Wallet to use for payment. Supported values: `SPOT`, `FUNDING`, `EARN`.
577    /// Combined wallets also supported: `SPOT_FUNDING`, `FUNDING_EARN`, `SPOT_FUNDING_EARN`, `SPOT_EARN`. Default is `SPOT`.
578    ///
579    /// This field is **optional.
580    #[builder(setter(into), default)]
581    #[serde(rename = "walletType", default)]
582    pub wallet_type: Option<SendQuoteRequestWalletTypeEnum>,
583    /// Quote valid duration. Supported values: 10s, 30s, 1m. Default is 10s.
584    ///
585    /// This field is **optional.
586    #[builder(setter(into), default)]
587    #[serde(rename = "validTime", default)]
588    pub valid_time: Option<SendQuoteRequestValidTimeEnum>,
589    /// Request validity window in milliseconds
590    ///
591    /// This field is **optional.
592    #[builder(setter(into), default)]
593    #[serde(rename = "recvWindow", default)]
594    pub recv_window: Option<i64>,
595}
596
597impl SendQuoteRequestParams {
598    /// Create a builder for [`send_quote_request`].
599    ///
600    /// Required parameters:
601    ///
602    /// * `from_asset` — String
603    /// * `to_asset` — String
604    ///
605    #[must_use]
606    pub fn builder(from_asset: String, to_asset: String) -> SendQuoteRequestParamsBuilder {
607        SendQuoteRequestParamsBuilder::default()
608            .from_asset(from_asset)
609            .to_asset(to_asset)
610    }
611}
612
613#[async_trait]
614impl TradeApi for TradeApiClient {
615    async fn accept_quote(
616        &self,
617        params: AcceptQuoteParams,
618    ) -> anyhow::Result<RestApiResponse<models::AcceptQuoteResponse>> {
619        let AcceptQuoteParams {
620            quote_id,
621            recv_window,
622        } = params;
623
624        let mut query_params = BTreeMap::new();
625        let body_params = BTreeMap::new();
626
627        query_params.insert("quoteId".to_string(), json!(quote_id));
628
629        if let Some(rw) = recv_window {
630            query_params.insert("recvWindow".to_string(), json!(rw));
631        }
632
633        send_request::<models::AcceptQuoteResponse>(
634            &self.configuration,
635            "/sapi/v1/convert/acceptQuote",
636            reqwest::Method::POST,
637            query_params,
638            body_params,
639            if HAS_TIME_UNIT {
640                self.configuration.time_unit
641            } else {
642                None
643            },
644            true,
645        )
646        .await
647    }
648
649    async fn cancel_limit_order(
650        &self,
651        params: CancelLimitOrderParams,
652    ) -> anyhow::Result<RestApiResponse<models::CancelLimitOrderResponse>> {
653        let CancelLimitOrderParams {
654            order_id,
655            recv_window,
656        } = params;
657
658        let mut query_params = BTreeMap::new();
659        let body_params = BTreeMap::new();
660
661        query_params.insert("orderId".to_string(), json!(order_id));
662
663        if let Some(rw) = recv_window {
664            query_params.insert("recvWindow".to_string(), json!(rw));
665        }
666
667        send_request::<models::CancelLimitOrderResponse>(
668            &self.configuration,
669            "/sapi/v1/convert/limit/cancelOrder",
670            reqwest::Method::POST,
671            query_params,
672            body_params,
673            if HAS_TIME_UNIT {
674                self.configuration.time_unit
675            } else {
676                None
677            },
678            true,
679        )
680        .await
681    }
682
683    async fn get_convert_trade_history(
684        &self,
685        params: GetConvertTradeHistoryParams,
686    ) -> anyhow::Result<RestApiResponse<models::GetConvertTradeHistoryResponse>> {
687        let GetConvertTradeHistoryParams {
688            start_time,
689            end_time,
690            limit,
691            recv_window,
692        } = params;
693
694        let mut query_params = BTreeMap::new();
695        let body_params = BTreeMap::new();
696
697        query_params.insert("startTime".to_string(), json!(start_time));
698
699        query_params.insert("endTime".to_string(), json!(end_time));
700
701        if let Some(rw) = limit {
702            query_params.insert("limit".to_string(), json!(rw));
703        }
704
705        if let Some(rw) = recv_window {
706            query_params.insert("recvWindow".to_string(), json!(rw));
707        }
708
709        send_request::<models::GetConvertTradeHistoryResponse>(
710            &self.configuration,
711            "/sapi/v1/convert/tradeFlow",
712            reqwest::Method::GET,
713            query_params,
714            body_params,
715            if HAS_TIME_UNIT {
716                self.configuration.time_unit
717            } else {
718                None
719            },
720            true,
721        )
722        .await
723    }
724
725    async fn order_status(
726        &self,
727        params: OrderStatusParams,
728    ) -> anyhow::Result<RestApiResponse<models::OrderStatusResponse>> {
729        let OrderStatusParams { order_id, quote_id } = params;
730
731        let mut query_params = BTreeMap::new();
732        let body_params = BTreeMap::new();
733
734        if let Some(rw) = order_id {
735            query_params.insert("orderId".to_string(), json!(rw));
736        }
737
738        if let Some(rw) = quote_id {
739            query_params.insert("quoteId".to_string(), json!(rw));
740        }
741
742        send_request::<models::OrderStatusResponse>(
743            &self.configuration,
744            "/sapi/v1/convert/orderStatus",
745            reqwest::Method::GET,
746            query_params,
747            body_params,
748            if HAS_TIME_UNIT {
749                self.configuration.time_unit
750            } else {
751                None
752            },
753            true,
754        )
755        .await
756    }
757
758    async fn place_limit_order(
759        &self,
760        params: PlaceLimitOrderParams,
761    ) -> anyhow::Result<RestApiResponse<models::PlaceLimitOrderResponse>> {
762        let PlaceLimitOrderParams {
763            base_asset,
764            quote_asset,
765            limit_price,
766            side,
767            expired_type,
768            base_amount,
769            quote_amount,
770            wallet_type,
771            recv_window,
772        } = params;
773
774        let mut query_params = BTreeMap::new();
775        let body_params = BTreeMap::new();
776
777        query_params.insert("baseAsset".to_string(), json!(base_asset));
778
779        query_params.insert("quoteAsset".to_string(), json!(quote_asset));
780
781        query_params.insert("limitPrice".to_string(), json!(limit_price));
782
783        if let Some(rw) = base_amount {
784            query_params.insert("baseAmount".to_string(), json!(rw));
785        }
786
787        if let Some(rw) = quote_amount {
788            query_params.insert("quoteAmount".to_string(), json!(rw));
789        }
790
791        query_params.insert("side".to_string(), json!(side));
792
793        if let Some(rw) = wallet_type {
794            query_params.insert("walletType".to_string(), json!(rw));
795        }
796
797        query_params.insert("expiredType".to_string(), json!(expired_type));
798
799        if let Some(rw) = recv_window {
800            query_params.insert("recvWindow".to_string(), json!(rw));
801        }
802
803        send_request::<models::PlaceLimitOrderResponse>(
804            &self.configuration,
805            "/sapi/v1/convert/limit/placeOrder",
806            reqwest::Method::POST,
807            query_params,
808            body_params,
809            if HAS_TIME_UNIT {
810                self.configuration.time_unit
811            } else {
812                None
813            },
814            true,
815        )
816        .await
817    }
818
819    async fn query_limit_open_orders(
820        &self,
821        params: QueryLimitOpenOrdersParams,
822    ) -> anyhow::Result<RestApiResponse<models::QueryLimitOpenOrdersResponse>> {
823        let QueryLimitOpenOrdersParams { recv_window } = params;
824
825        let mut query_params = BTreeMap::new();
826        let body_params = BTreeMap::new();
827
828        if let Some(rw) = recv_window {
829            query_params.insert("recvWindow".to_string(), json!(rw));
830        }
831
832        send_request::<models::QueryLimitOpenOrdersResponse>(
833            &self.configuration,
834            "/sapi/v1/convert/limit/queryOpenOrders",
835            reqwest::Method::GET,
836            query_params,
837            body_params,
838            if HAS_TIME_UNIT {
839                self.configuration.time_unit
840            } else {
841                None
842            },
843            true,
844        )
845        .await
846    }
847
848    async fn send_quote_request(
849        &self,
850        params: SendQuoteRequestParams,
851    ) -> anyhow::Result<RestApiResponse<models::SendQuoteRequestResponse>> {
852        let SendQuoteRequestParams {
853            from_asset,
854            to_asset,
855            from_amount,
856            to_amount,
857            wallet_type,
858            valid_time,
859            recv_window,
860        } = params;
861
862        let mut query_params = BTreeMap::new();
863        let body_params = BTreeMap::new();
864
865        query_params.insert("fromAsset".to_string(), json!(from_asset));
866
867        query_params.insert("toAsset".to_string(), json!(to_asset));
868
869        if let Some(rw) = from_amount {
870            query_params.insert("fromAmount".to_string(), json!(rw));
871        }
872
873        if let Some(rw) = to_amount {
874            query_params.insert("toAmount".to_string(), json!(rw));
875        }
876
877        if let Some(rw) = wallet_type {
878            query_params.insert("walletType".to_string(), json!(rw));
879        }
880
881        if let Some(rw) = valid_time {
882            query_params.insert("validTime".to_string(), json!(rw));
883        }
884
885        if let Some(rw) = recv_window {
886            query_params.insert("recvWindow".to_string(), json!(rw));
887        }
888
889        send_request::<models::SendQuoteRequestResponse>(
890            &self.configuration,
891            "/sapi/v1/convert/getQuote",
892            reqwest::Method::POST,
893            query_params,
894            body_params,
895            if HAS_TIME_UNIT {
896                self.configuration.time_unit
897            } else {
898                None
899            },
900            true,
901        )
902        .await
903    }
904}
905
906#[cfg(all(test, feature = "convert"))]
907mod tests {
908    use super::*;
909    use crate::TOKIO_SHARED_RT;
910    use crate::{errors::ConnectorError, models::DataFuture, models::RestApiRateLimit};
911    use async_trait::async_trait;
912    use std::collections::HashMap;
913
914    struct DummyRestApiResponse<T> {
915        inner: Box<dyn FnOnce() -> DataFuture<Result<T, ConnectorError>> + Send + Sync>,
916        status: u16,
917        headers: HashMap<String, String>,
918        rate_limits: Option<Vec<RestApiRateLimit>>,
919    }
920
921    impl<T> From<DummyRestApiResponse<T>> for RestApiResponse<T> {
922        fn from(dummy: DummyRestApiResponse<T>) -> Self {
923            Self {
924                data_fn: dummy.inner,
925                status: dummy.status,
926                headers: dummy.headers,
927                rate_limits: dummy.rate_limits,
928            }
929        }
930    }
931
932    struct MockTradeApiClient {
933        force_error: bool,
934    }
935
936    #[async_trait]
937    impl TradeApi for MockTradeApiClient {
938        async fn accept_quote(
939            &self,
940            _params: AcceptQuoteParams,
941        ) -> anyhow::Result<RestApiResponse<models::AcceptQuoteResponse>> {
942            if self.force_error {
943                return Err(ConnectorError::ConnectorClientError {
944                    msg: "ResponseError".to_string(),
945                    code: None,
946                }
947                .into());
948            }
949
950            let resp_json: Value = serde_json::from_str(r#"{"orderId":"933256278426274426","createTime":1623381330472,"orderStatus":"PROCESS"}"#).unwrap_or_else(|_| serde_json::json!({}));
951            let dummy_response: models::AcceptQuoteResponse =
952                serde_json::from_value(resp_json.clone())
953                    .expect("should parse into models::AcceptQuoteResponse");
954
955            let dummy = DummyRestApiResponse {
956                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
957                status: 200,
958                headers: HashMap::new(),
959                rate_limits: None,
960            };
961
962            Ok(dummy.into())
963        }
964
965        async fn cancel_limit_order(
966            &self,
967            _params: CancelLimitOrderParams,
968        ) -> anyhow::Result<RestApiResponse<models::CancelLimitOrderResponse>> {
969            if self.force_error {
970                return Err(ConnectorError::ConnectorClientError {
971                    msg: "ResponseError".to_string(),
972                    code: None,
973                }
974                .into());
975            }
976
977            let resp_json: Value =
978                serde_json::from_str(r#"{"orderId":1603680255057330400,"status":"CANCELED"}"#)
979                    .unwrap_or_else(|_| serde_json::json!({}));
980            let dummy_response: models::CancelLimitOrderResponse =
981                serde_json::from_value(resp_json.clone())
982                    .expect("should parse into models::CancelLimitOrderResponse");
983
984            let dummy = DummyRestApiResponse {
985                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
986                status: 200,
987                headers: HashMap::new(),
988                rate_limits: None,
989            };
990
991            Ok(dummy.into())
992        }
993
994        async fn get_convert_trade_history(
995            &self,
996            _params: GetConvertTradeHistoryParams,
997        ) -> anyhow::Result<RestApiResponse<models::GetConvertTradeHistoryResponse>> {
998            if self.force_error {
999                return Err(ConnectorError::ConnectorClientError {
1000                    msg: "ResponseError".to_string(),
1001                    code: None,
1002                }
1003                .into());
1004            }
1005
1006            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_or_else(|_| serde_json::json!({}));
1007            let dummy_response: models::GetConvertTradeHistoryResponse =
1008                serde_json::from_value(resp_json.clone())
1009                    .expect("should parse into models::GetConvertTradeHistoryResponse");
1010
1011            let dummy = DummyRestApiResponse {
1012                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
1013                status: 200,
1014                headers: HashMap::new(),
1015                rate_limits: None,
1016            };
1017
1018            Ok(dummy.into())
1019        }
1020
1021        async fn order_status(
1022            &self,
1023            _params: OrderStatusParams,
1024        ) -> anyhow::Result<RestApiResponse<models::OrderStatusResponse>> {
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#"{"orderId":933256278426274400,"orderStatus":"SUCCESS","fromAsset":"BTC","fromAmount":"0.00054414","toAsset":"USDT","toAmount":"20","ratio":"36755","inverseRatio":"0.00002721","createTime":1623381330472}"#).unwrap_or_else(|_| serde_json::json!({}));
1034            let dummy_response: models::OrderStatusResponse =
1035                serde_json::from_value(resp_json.clone())
1036                    .expect("should parse into models::OrderStatusResponse");
1037
1038            let dummy = DummyRestApiResponse {
1039                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
1040                status: 200,
1041                headers: HashMap::new(),
1042                rate_limits: None,
1043            };
1044
1045            Ok(dummy.into())
1046        }
1047
1048        async fn place_limit_order(
1049            &self,
1050            _params: PlaceLimitOrderParams,
1051        ) -> anyhow::Result<RestApiResponse<models::PlaceLimitOrderResponse>> {
1052            if self.force_error {
1053                return Err(ConnectorError::ConnectorClientError {
1054                    msg: "ResponseError".to_string(),
1055                    code: None,
1056                }
1057                .into());
1058            }
1059
1060            let resp_json: Value =
1061                serde_json::from_str(r#"{"orderId":1603680255057330400,"status":"PROCESS"}"#)
1062                    .unwrap_or_else(|_| serde_json::json!({}));
1063            let dummy_response: models::PlaceLimitOrderResponse =
1064                serde_json::from_value(resp_json.clone())
1065                    .expect("should parse into models::PlaceLimitOrderResponse");
1066
1067            let dummy = DummyRestApiResponse {
1068                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
1069                status: 200,
1070                headers: HashMap::new(),
1071                rate_limits: None,
1072            };
1073
1074            Ok(dummy.into())
1075        }
1076
1077        async fn query_limit_open_orders(
1078            &self,
1079            _params: QueryLimitOpenOrdersParams,
1080        ) -> anyhow::Result<RestApiResponse<models::QueryLimitOpenOrdersResponse>> {
1081            if self.force_error {
1082                return Err(ConnectorError::ConnectorClientError {
1083                    msg: "ResponseError".to_string(),
1084                    code: None,
1085                }
1086                .into());
1087            }
1088
1089            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_or_else(|_| serde_json::json!({}));
1090            let dummy_response: models::QueryLimitOpenOrdersResponse =
1091                serde_json::from_value(resp_json.clone())
1092                    .expect("should parse into models::QueryLimitOpenOrdersResponse");
1093
1094            let dummy = DummyRestApiResponse {
1095                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
1096                status: 200,
1097                headers: HashMap::new(),
1098                rate_limits: None,
1099            };
1100
1101            Ok(dummy.into())
1102        }
1103
1104        async fn send_quote_request(
1105            &self,
1106            _params: SendQuoteRequestParams,
1107        ) -> anyhow::Result<RestApiResponse<models::SendQuoteRequestResponse>> {
1108            if self.force_error {
1109                return Err(ConnectorError::ConnectorClientError {
1110                    msg: "ResponseError".to_string(),
1111                    code: None,
1112                }
1113                .into());
1114            }
1115
1116            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_or_else(|_| serde_json::json!({}));
1117            let dummy_response: models::SendQuoteRequestResponse =
1118                serde_json::from_value(resp_json.clone())
1119                    .expect("should parse into models::SendQuoteRequestResponse");
1120
1121            let dummy = DummyRestApiResponse {
1122                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
1123                status: 200,
1124                headers: HashMap::new(),
1125                rate_limits: None,
1126            };
1127
1128            Ok(dummy.into())
1129        }
1130    }
1131
1132    #[test]
1133    fn accept_quote_required_params_success() {
1134        TOKIO_SHARED_RT.block_on(async {
1135            let client = MockTradeApiClient { force_error: false };
1136
1137            let params = AcceptQuoteParams::builder("1".to_string(),).build().unwrap();
1138
1139            let resp_json: Value = serde_json::from_str(r#"{"orderId":"933256278426274426","createTime":1623381330472,"orderStatus":"PROCESS"}"#).unwrap_or_else(|_| serde_json::json!({}));
1140            let expected_response : models::AcceptQuoteResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::AcceptQuoteResponse");
1141
1142            let resp = client.accept_quote(params).await.expect("Expected a response");
1143            let data_future = resp.data();
1144            let actual_response = data_future.await.unwrap();
1145            assert_eq!(actual_response, expected_response);
1146        });
1147    }
1148
1149    #[test]
1150    fn accept_quote_optional_params_success() {
1151        TOKIO_SHARED_RT.block_on(async {
1152            let client = MockTradeApiClient { force_error: false };
1153
1154            let params = AcceptQuoteParams::builder("1".to_string(),).recv_window(5000).build().unwrap();
1155
1156            let resp_json: Value = serde_json::from_str(r#"{"orderId":"933256278426274426","createTime":1623381330472,"orderStatus":"PROCESS"}"#).unwrap_or_else(|_| serde_json::json!({}));
1157            let expected_response : models::AcceptQuoteResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::AcceptQuoteResponse");
1158
1159            let resp = client.accept_quote(params).await.expect("Expected a response");
1160            let data_future = resp.data();
1161            let actual_response = data_future.await.unwrap();
1162            assert_eq!(actual_response, expected_response);
1163        });
1164    }
1165
1166    #[test]
1167    fn accept_quote_response_error() {
1168        TOKIO_SHARED_RT.block_on(async {
1169            let client = MockTradeApiClient { force_error: true };
1170
1171            let params = AcceptQuoteParams::builder("1".to_string()).build().unwrap();
1172
1173            match client.accept_quote(params).await {
1174                Ok(_) => panic!("Expected an error"),
1175                Err(err) => {
1176                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
1177                }
1178            }
1179        });
1180    }
1181
1182    #[test]
1183    fn cancel_limit_order_required_params_success() {
1184        TOKIO_SHARED_RT.block_on(async {
1185            let client = MockTradeApiClient { force_error: false };
1186
1187            let params = CancelLimitOrderParams::builder(1603680255057330400)
1188                .build()
1189                .unwrap();
1190
1191            let resp_json: Value =
1192                serde_json::from_str(r#"{"orderId":1603680255057330400,"status":"CANCELED"}"#)
1193                    .unwrap_or_else(|_| serde_json::json!({}));
1194            let expected_response: models::CancelLimitOrderResponse =
1195                serde_json::from_value(resp_json.clone())
1196                    .expect("should parse into models::CancelLimitOrderResponse");
1197
1198            let resp = client
1199                .cancel_limit_order(params)
1200                .await
1201                .expect("Expected a response");
1202            let data_future = resp.data();
1203            let actual_response = data_future.await.unwrap();
1204            assert_eq!(actual_response, expected_response);
1205        });
1206    }
1207
1208    #[test]
1209    fn cancel_limit_order_optional_params_success() {
1210        TOKIO_SHARED_RT.block_on(async {
1211            let client = MockTradeApiClient { force_error: false };
1212
1213            let params = CancelLimitOrderParams::builder(1603680255057330400)
1214                .recv_window(5000)
1215                .build()
1216                .unwrap();
1217
1218            let resp_json: Value =
1219                serde_json::from_str(r#"{"orderId":1603680255057330400,"status":"CANCELED"}"#)
1220                    .unwrap_or_else(|_| serde_json::json!({}));
1221            let expected_response: models::CancelLimitOrderResponse =
1222                serde_json::from_value(resp_json.clone())
1223                    .expect("should parse into models::CancelLimitOrderResponse");
1224
1225            let resp = client
1226                .cancel_limit_order(params)
1227                .await
1228                .expect("Expected a response");
1229            let data_future = resp.data();
1230            let actual_response = data_future.await.unwrap();
1231            assert_eq!(actual_response, expected_response);
1232        });
1233    }
1234
1235    #[test]
1236    fn cancel_limit_order_response_error() {
1237        TOKIO_SHARED_RT.block_on(async {
1238            let client = MockTradeApiClient { force_error: true };
1239
1240            let params = CancelLimitOrderParams::builder(1603680255057330400)
1241                .build()
1242                .unwrap();
1243
1244            match client.cancel_limit_order(params).await {
1245                Ok(_) => panic!("Expected an error"),
1246                Err(err) => {
1247                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
1248                }
1249            }
1250        });
1251    }
1252
1253    #[test]
1254    fn get_convert_trade_history_required_params_success() {
1255        TOKIO_SHARED_RT.block_on(async {
1256            let client = MockTradeApiClient { force_error: false };
1257
1258            let params = GetConvertTradeHistoryParams::builder(1623319461670,1641782889000,).build().unwrap();
1259
1260            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_or_else(|_| serde_json::json!({}));
1261            let expected_response : models::GetConvertTradeHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::GetConvertTradeHistoryResponse");
1262
1263            let resp = client.get_convert_trade_history(params).await.expect("Expected a response");
1264            let data_future = resp.data();
1265            let actual_response = data_future.await.unwrap();
1266            assert_eq!(actual_response, expected_response);
1267        });
1268    }
1269
1270    #[test]
1271    fn get_convert_trade_history_optional_params_success() {
1272        TOKIO_SHARED_RT.block_on(async {
1273            let client = MockTradeApiClient { force_error: false };
1274
1275            let params = GetConvertTradeHistoryParams::builder(1623319461670,1641782889000,).limit(100).recv_window(5000).build().unwrap();
1276
1277            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_or_else(|_| serde_json::json!({}));
1278            let expected_response : models::GetConvertTradeHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::GetConvertTradeHistoryResponse");
1279
1280            let resp = client.get_convert_trade_history(params).await.expect("Expected a response");
1281            let data_future = resp.data();
1282            let actual_response = data_future.await.unwrap();
1283            assert_eq!(actual_response, expected_response);
1284        });
1285    }
1286
1287    #[test]
1288    fn get_convert_trade_history_response_error() {
1289        TOKIO_SHARED_RT.block_on(async {
1290            let client = MockTradeApiClient { force_error: true };
1291
1292            let params = GetConvertTradeHistoryParams::builder(1623319461670, 1641782889000)
1293                .build()
1294                .unwrap();
1295
1296            match client.get_convert_trade_history(params).await {
1297                Ok(_) => panic!("Expected an error"),
1298                Err(err) => {
1299                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
1300                }
1301            }
1302        });
1303    }
1304
1305    #[test]
1306    fn order_status_required_params_success() {
1307        TOKIO_SHARED_RT.block_on(async {
1308            let client = MockTradeApiClient { force_error: false };
1309
1310            let params = OrderStatusParams::builder().build().unwrap();
1311
1312            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_or_else(|_| serde_json::json!({}));
1313            let expected_response : models::OrderStatusResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::OrderStatusResponse");
1314
1315            let resp = client.order_status(params).await.expect("Expected a response");
1316            let data_future = resp.data();
1317            let actual_response = data_future.await.unwrap();
1318            assert_eq!(actual_response, expected_response);
1319        });
1320    }
1321
1322    #[test]
1323    fn order_status_optional_params_success() {
1324        TOKIO_SHARED_RT.block_on(async {
1325            let client = MockTradeApiClient { force_error: false };
1326
1327            let params = OrderStatusParams::builder().order_id("1".to_string()).quote_id("1".to_string()).build().unwrap();
1328
1329            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_or_else(|_| serde_json::json!({}));
1330            let expected_response : models::OrderStatusResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::OrderStatusResponse");
1331
1332            let resp = client.order_status(params).await.expect("Expected a response");
1333            let data_future = resp.data();
1334            let actual_response = data_future.await.unwrap();
1335            assert_eq!(actual_response, expected_response);
1336        });
1337    }
1338
1339    #[test]
1340    fn order_status_response_error() {
1341        TOKIO_SHARED_RT.block_on(async {
1342            let client = MockTradeApiClient { force_error: true };
1343
1344            let params = OrderStatusParams::builder().build().unwrap();
1345
1346            match client.order_status(params).await {
1347                Ok(_) => panic!("Expected an error"),
1348                Err(err) => {
1349                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
1350                }
1351            }
1352        });
1353    }
1354
1355    #[test]
1356    fn place_limit_order_required_params_success() {
1357        TOKIO_SHARED_RT.block_on(async {
1358            let client = MockTradeApiClient { force_error: false };
1359
1360            let params = PlaceLimitOrderParams::builder(
1361                "BTC".to_string(),
1362                "USDT".to_string(),
1363                dec!(1),
1364                PlaceLimitOrderSideEnum::Buy,
1365                PlaceLimitOrderExpiredTypeEnum::ExpiredType1D,
1366            )
1367            .build()
1368            .unwrap();
1369
1370            let resp_json: Value =
1371                serde_json::from_str(r#"{"orderId":1603680255057330400,"status":"PROCESS"}"#)
1372                    .unwrap_or_else(|_| serde_json::json!({}));
1373            let expected_response: models::PlaceLimitOrderResponse =
1374                serde_json::from_value(resp_json.clone())
1375                    .expect("should parse into models::PlaceLimitOrderResponse");
1376
1377            let resp = client
1378                .place_limit_order(params)
1379                .await
1380                .expect("Expected a response");
1381            let data_future = resp.data();
1382            let actual_response = data_future.await.unwrap();
1383            assert_eq!(actual_response, expected_response);
1384        });
1385    }
1386
1387    #[test]
1388    fn place_limit_order_optional_params_success() {
1389        TOKIO_SHARED_RT.block_on(async {
1390            let client = MockTradeApiClient { force_error: false };
1391
1392            let params = PlaceLimitOrderParams::builder(
1393                "BTC".to_string(),
1394                "USDT".to_string(),
1395                dec!(1),
1396                PlaceLimitOrderSideEnum::Buy,
1397                PlaceLimitOrderExpiredTypeEnum::ExpiredType1D,
1398            )
1399            .base_amount(dec!(1))
1400            .quote_amount(dec!(1))
1401            .wallet_type(PlaceLimitOrderWalletTypeEnum::Spot)
1402            .recv_window(5000)
1403            .build()
1404            .unwrap();
1405
1406            let resp_json: Value =
1407                serde_json::from_str(r#"{"orderId":1603680255057330400,"status":"PROCESS"}"#)
1408                    .unwrap_or_else(|_| serde_json::json!({}));
1409            let expected_response: models::PlaceLimitOrderResponse =
1410                serde_json::from_value(resp_json.clone())
1411                    .expect("should parse into models::PlaceLimitOrderResponse");
1412
1413            let resp = client
1414                .place_limit_order(params)
1415                .await
1416                .expect("Expected a response");
1417            let data_future = resp.data();
1418            let actual_response = data_future.await.unwrap();
1419            assert_eq!(actual_response, expected_response);
1420        });
1421    }
1422
1423    #[test]
1424    fn place_limit_order_response_error() {
1425        TOKIO_SHARED_RT.block_on(async {
1426            let client = MockTradeApiClient { force_error: true };
1427
1428            let params = PlaceLimitOrderParams::builder(
1429                "BTC".to_string(),
1430                "USDT".to_string(),
1431                dec!(1),
1432                PlaceLimitOrderSideEnum::Buy,
1433                PlaceLimitOrderExpiredTypeEnum::ExpiredType1D,
1434            )
1435            .build()
1436            .unwrap();
1437
1438            match client.place_limit_order(params).await {
1439                Ok(_) => panic!("Expected an error"),
1440                Err(err) => {
1441                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
1442                }
1443            }
1444        });
1445    }
1446
1447    #[test]
1448    fn query_limit_open_orders_required_params_success() {
1449        TOKIO_SHARED_RT.block_on(async {
1450            let client = MockTradeApiClient { force_error: false };
1451
1452            let params = QueryLimitOpenOrdersParams::builder().build().unwrap();
1453
1454            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_or_else(|_| serde_json::json!({}));
1455            let expected_response : models::QueryLimitOpenOrdersResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::QueryLimitOpenOrdersResponse");
1456
1457            let resp = client.query_limit_open_orders(params).await.expect("Expected a response");
1458            let data_future = resp.data();
1459            let actual_response = data_future.await.unwrap();
1460            assert_eq!(actual_response, expected_response);
1461        });
1462    }
1463
1464    #[test]
1465    fn query_limit_open_orders_optional_params_success() {
1466        TOKIO_SHARED_RT.block_on(async {
1467            let client = MockTradeApiClient { force_error: false };
1468
1469            let params = QueryLimitOpenOrdersParams::builder().recv_window(5000).build().unwrap();
1470
1471            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_or_else(|_| serde_json::json!({}));
1472            let expected_response : models::QueryLimitOpenOrdersResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::QueryLimitOpenOrdersResponse");
1473
1474            let resp = client.query_limit_open_orders(params).await.expect("Expected a response");
1475            let data_future = resp.data();
1476            let actual_response = data_future.await.unwrap();
1477            assert_eq!(actual_response, expected_response);
1478        });
1479    }
1480
1481    #[test]
1482    fn query_limit_open_orders_response_error() {
1483        TOKIO_SHARED_RT.block_on(async {
1484            let client = MockTradeApiClient { force_error: true };
1485
1486            let params = QueryLimitOpenOrdersParams::builder().build().unwrap();
1487
1488            match client.query_limit_open_orders(params).await {
1489                Ok(_) => panic!("Expected an error"),
1490                Err(err) => {
1491                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
1492                }
1493            }
1494        });
1495    }
1496
1497    #[test]
1498    fn send_quote_request_required_params_success() {
1499        TOKIO_SHARED_RT.block_on(async {
1500            let client = MockTradeApiClient { force_error: false };
1501
1502            let params = SendQuoteRequestParams::builder("BTC".to_string(),"USDT".to_string(),).build().unwrap();
1503
1504            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_or_else(|_| serde_json::json!({}));
1505            let expected_response : models::SendQuoteRequestResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::SendQuoteRequestResponse");
1506
1507            let resp = client.send_quote_request(params).await.expect("Expected a response");
1508            let data_future = resp.data();
1509            let actual_response = data_future.await.unwrap();
1510            assert_eq!(actual_response, expected_response);
1511        });
1512    }
1513
1514    #[test]
1515    fn send_quote_request_optional_params_success() {
1516        TOKIO_SHARED_RT.block_on(async {
1517            let client = MockTradeApiClient { force_error: false };
1518
1519            let params = SendQuoteRequestParams::builder("BTC".to_string(),"USDT".to_string(),).from_amount(dec!(1)).to_amount(dec!(1)).wallet_type(SendQuoteRequestWalletTypeEnum::Spot).valid_time(SendQuoteRequestValidTimeEnum::ValidTime10s).recv_window(5000).build().unwrap();
1520
1521            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_or_else(|_| serde_json::json!({}));
1522            let expected_response : models::SendQuoteRequestResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::SendQuoteRequestResponse");
1523
1524            let resp = client.send_quote_request(params).await.expect("Expected a response");
1525            let data_future = resp.data();
1526            let actual_response = data_future.await.unwrap();
1527            assert_eq!(actual_response, expected_response);
1528        });
1529    }
1530
1531    #[test]
1532    fn send_quote_request_response_error() {
1533        TOKIO_SHARED_RT.block_on(async {
1534            let client = MockTradeApiClient { force_error: true };
1535
1536            let params = SendQuoteRequestParams::builder("BTC".to_string(), "USDT".to_string())
1537                .build()
1538                .unwrap();
1539
1540            match client.send_quote_request(params).await {
1541                Ok(_) => panic!("Expected an error"),
1542                Err(err) => {
1543                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
1544                }
1545            }
1546        });
1547    }
1548}