Skip to main content

binance_sdk/spot/rest_api/apis/
general_api.rs

1/*
2 * Spot REST API
3 *
4 * Access market data, manage accounts, and trade on Binance Spot.
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::spot::rest_api::models;
29
30const HAS_TIME_UNIT: bool = true;
31
32#[async_trait]
33pub trait GeneralApi: Send + Sync {
34    async fn exchange_info(
35        &self,
36        params: ExchangeInfoParams,
37    ) -> anyhow::Result<RestApiResponse<models::ExchangeInfoResponse>>;
38    async fn execution_rules(
39        &self,
40        params: ExecutionRulesParams,
41    ) -> anyhow::Result<RestApiResponse<models::ExecutionRulesResponse>>;
42    async fn ping(&self) -> anyhow::Result<RestApiResponse<Value>>;
43    async fn time(&self) -> anyhow::Result<RestApiResponse<models::TimeResponse>>;
44}
45
46#[derive(Debug, Clone)]
47pub struct GeneralApiClient {
48    configuration: ConfigurationRestApi,
49}
50
51impl GeneralApiClient {
52    pub fn new(configuration: ConfigurationRestApi) -> Self {
53        Self { configuration }
54    }
55}
56
57#[allow(non_camel_case_types)]
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub enum ExchangeInfoPermissionsEnum {
60    #[serde(rename = "SPOT")]
61    Spot,
62    #[serde(rename = "MARGIN")]
63    Margin,
64    #[serde(rename = "LEVERAGED")]
65    Leveraged,
66    #[serde(rename = "TRD_GRP_002")]
67    TrdGrp002,
68    #[serde(rename = "TRD_GRP_003")]
69    TrdGrp003,
70    #[serde(rename = "TRD_GRP_004")]
71    TrdGrp004,
72    #[serde(rename = "TRD_GRP_005")]
73    TrdGrp005,
74    #[serde(rename = "TRD_GRP_006")]
75    TrdGrp006,
76    #[serde(rename = "TRD_GRP_007")]
77    TrdGrp007,
78    #[serde(rename = "TRD_GRP_008")]
79    TrdGrp008,
80    #[serde(rename = "TRD_GRP_009")]
81    TrdGrp009,
82    #[serde(rename = "TRD_GRP_010")]
83    TrdGrp010,
84    #[serde(rename = "TRD_GRP_011")]
85    TrdGrp011,
86    #[serde(rename = "TRD_GRP_012")]
87    TrdGrp012,
88    #[serde(rename = "TRD_GRP_013")]
89    TrdGrp013,
90    #[serde(rename = "TRD_GRP_014")]
91    TrdGrp014,
92    #[serde(rename = "TRD_GRP_015")]
93    TrdGrp015,
94    #[serde(rename = "TRD_GRP_016")]
95    TrdGrp016,
96    #[serde(rename = "TRD_GRP_017")]
97    TrdGrp017,
98    #[serde(rename = "TRD_GRP_018")]
99    TrdGrp018,
100    #[serde(rename = "TRD_GRP_019")]
101    TrdGrp019,
102    #[serde(rename = "TRD_GRP_020")]
103    TrdGrp020,
104    #[serde(rename = "TRD_GRP_021")]
105    TrdGrp021,
106    #[serde(rename = "TRD_GRP_022")]
107    TrdGrp022,
108    #[serde(rename = "TRD_GRP_023")]
109    TrdGrp023,
110    #[serde(rename = "TRD_GRP_024")]
111    TrdGrp024,
112    #[serde(rename = "TRD_GRP_025")]
113    TrdGrp025,
114}
115
116impl ExchangeInfoPermissionsEnum {
117    #[must_use]
118    pub fn as_str(&self) -> &'static str {
119        match self {
120            Self::Spot => "SPOT",
121            Self::Margin => "MARGIN",
122            Self::Leveraged => "LEVERAGED",
123            Self::TrdGrp002 => "TRD_GRP_002",
124            Self::TrdGrp003 => "TRD_GRP_003",
125            Self::TrdGrp004 => "TRD_GRP_004",
126            Self::TrdGrp005 => "TRD_GRP_005",
127            Self::TrdGrp006 => "TRD_GRP_006",
128            Self::TrdGrp007 => "TRD_GRP_007",
129            Self::TrdGrp008 => "TRD_GRP_008",
130            Self::TrdGrp009 => "TRD_GRP_009",
131            Self::TrdGrp010 => "TRD_GRP_010",
132            Self::TrdGrp011 => "TRD_GRP_011",
133            Self::TrdGrp012 => "TRD_GRP_012",
134            Self::TrdGrp013 => "TRD_GRP_013",
135            Self::TrdGrp014 => "TRD_GRP_014",
136            Self::TrdGrp015 => "TRD_GRP_015",
137            Self::TrdGrp016 => "TRD_GRP_016",
138            Self::TrdGrp017 => "TRD_GRP_017",
139            Self::TrdGrp018 => "TRD_GRP_018",
140            Self::TrdGrp019 => "TRD_GRP_019",
141            Self::TrdGrp020 => "TRD_GRP_020",
142            Self::TrdGrp021 => "TRD_GRP_021",
143            Self::TrdGrp022 => "TRD_GRP_022",
144            Self::TrdGrp023 => "TRD_GRP_023",
145            Self::TrdGrp024 => "TRD_GRP_024",
146            Self::TrdGrp025 => "TRD_GRP_025",
147        }
148    }
149}
150
151impl std::str::FromStr for ExchangeInfoPermissionsEnum {
152    type Err = Box<dyn std::error::Error + Send + Sync>;
153
154    fn from_str(s: &str) -> Result<Self, Self::Err> {
155        match s {
156            "SPOT" => Ok(Self::Spot),
157            "MARGIN" => Ok(Self::Margin),
158            "LEVERAGED" => Ok(Self::Leveraged),
159            "TRD_GRP_002" => Ok(Self::TrdGrp002),
160            "TRD_GRP_003" => Ok(Self::TrdGrp003),
161            "TRD_GRP_004" => Ok(Self::TrdGrp004),
162            "TRD_GRP_005" => Ok(Self::TrdGrp005),
163            "TRD_GRP_006" => Ok(Self::TrdGrp006),
164            "TRD_GRP_007" => Ok(Self::TrdGrp007),
165            "TRD_GRP_008" => Ok(Self::TrdGrp008),
166            "TRD_GRP_009" => Ok(Self::TrdGrp009),
167            "TRD_GRP_010" => Ok(Self::TrdGrp010),
168            "TRD_GRP_011" => Ok(Self::TrdGrp011),
169            "TRD_GRP_012" => Ok(Self::TrdGrp012),
170            "TRD_GRP_013" => Ok(Self::TrdGrp013),
171            "TRD_GRP_014" => Ok(Self::TrdGrp014),
172            "TRD_GRP_015" => Ok(Self::TrdGrp015),
173            "TRD_GRP_016" => Ok(Self::TrdGrp016),
174            "TRD_GRP_017" => Ok(Self::TrdGrp017),
175            "TRD_GRP_018" => Ok(Self::TrdGrp018),
176            "TRD_GRP_019" => Ok(Self::TrdGrp019),
177            "TRD_GRP_020" => Ok(Self::TrdGrp020),
178            "TRD_GRP_021" => Ok(Self::TrdGrp021),
179            "TRD_GRP_022" => Ok(Self::TrdGrp022),
180            "TRD_GRP_023" => Ok(Self::TrdGrp023),
181            "TRD_GRP_024" => Ok(Self::TrdGrp024),
182            "TRD_GRP_025" => Ok(Self::TrdGrp025),
183            other => Err(format!("invalid ExchangeInfoPermissionsEnum: {}", other).into()),
184        }
185    }
186}
187
188#[allow(non_camel_case_types)]
189#[derive(Debug, Clone, Serialize, Deserialize)]
190pub enum ExchangeInfoSymbolStatusEnum {
191    #[serde(rename = "TRADING")]
192    Trading,
193    #[serde(rename = "HALT")]
194    Halt,
195    #[serde(rename = "BREAK")]
196    Break,
197}
198
199impl ExchangeInfoSymbolStatusEnum {
200    #[must_use]
201    pub fn as_str(&self) -> &'static str {
202        match self {
203            Self::Trading => "TRADING",
204            Self::Halt => "HALT",
205            Self::Break => "BREAK",
206        }
207    }
208}
209
210impl std::str::FromStr for ExchangeInfoSymbolStatusEnum {
211    type Err = Box<dyn std::error::Error + Send + Sync>;
212
213    fn from_str(s: &str) -> Result<Self, Self::Err> {
214        match s {
215            "TRADING" => Ok(Self::Trading),
216            "HALT" => Ok(Self::Halt),
217            "BREAK" => Ok(Self::Break),
218            other => Err(format!("invalid ExchangeInfoSymbolStatusEnum: {}", other).into()),
219        }
220    }
221}
222
223#[allow(non_camel_case_types)]
224#[derive(Debug, Clone, Serialize, Deserialize)]
225pub enum ExecutionRulesSymbolStatusEnum {
226    #[serde(rename = "TRADING")]
227    Trading,
228    #[serde(rename = "HALT")]
229    Halt,
230    #[serde(rename = "BREAK")]
231    Break,
232}
233
234impl ExecutionRulesSymbolStatusEnum {
235    #[must_use]
236    pub fn as_str(&self) -> &'static str {
237        match self {
238            Self::Trading => "TRADING",
239            Self::Halt => "HALT",
240            Self::Break => "BREAK",
241        }
242    }
243}
244
245impl std::str::FromStr for ExecutionRulesSymbolStatusEnum {
246    type Err = Box<dyn std::error::Error + Send + Sync>;
247
248    fn from_str(s: &str) -> Result<Self, Self::Err> {
249        match s {
250            "TRADING" => Ok(Self::Trading),
251            "HALT" => Ok(Self::Halt),
252            "BREAK" => Ok(Self::Break),
253            other => Err(format!("invalid ExecutionRulesSymbolStatusEnum: {}", other).into()),
254        }
255    }
256}
257
258/// Request parameters for the [`exchange_info`] operation.
259///
260/// This struct holds all of the inputs you can pass when calling
261/// [`exchange_info`](#method.exchange_info).
262#[derive(Clone, Debug, Builder, Deserialize, Default)]
263#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
264pub struct ExchangeInfoParams {
265    /// Example: curl -X GET "<https://api.binance.com/api/v3/exchangeInfo?symbol=BNBBTC>"
266    ///
267    /// This field is **optional.
268    #[builder(setter(into), default)]
269    #[serde(rename = "symbol", default)]
270    pub symbol: Option<String>,
271    /// Examples: curl -X GET "<https://api.binance.com/api/v3/exchangeInfo?symbols=%5B%22BNBBTC%22,%22BTCUSDT%22%5D>" or curl -g -X GET '<https://api.binance.com/api/v3/exchangeInfo?symbols>=["BTCUSDT","BNBBTC"]'
272    ///
273    /// This field is **optional.
274    #[builder(setter(into), default)]
275    #[serde(rename = "symbols", default)]
276    pub symbols: Option<Vec<String>>,
277    /// Examples: curl -X GET "<https://api.binance.com/api/v3/exchangeInfo?permissions=SPOT>"
278    ///
279    /// curl -X GET "<https://api.binance.com/api/v3/exchangeInfo?permissions=%5B%22MARGIN%22%2C%22LEVERAGED%22%5D>"
280    /// or
281    /// curl -g -X GET '<https://api.binance.com/api/v3/exchangeInfo?permissions>=["MARGIN","LEVERAGED"]'
282    ///
283    /// This field is **optional.
284    #[builder(setter(into), default)]
285    #[serde(rename = "permissions", default)]
286    pub permissions: Option<ExchangeInfoPermissionsEnum>,
287    /// Controls whether the content of the `permissionSets` field is populated or not.
288    ///
289    /// This field is **optional.
290    #[builder(setter(into), default)]
291    #[serde(rename = "showPermissionSets", default)]
292    pub show_permission_sets: Option<bool>,
293    /// Filters for symbols that have this `tradingStatus`. Cannot be used in combination with `symbols` or `symbol`.
294    ///
295    /// This field is **optional.
296    #[builder(setter(into), default)]
297    #[serde(rename = "symbolStatus", default)]
298    pub symbol_status: Option<ExchangeInfoSymbolStatusEnum>,
299}
300
301impl ExchangeInfoParams {
302    /// Create a builder for [`exchange_info`].
303    ///
304    #[must_use]
305    pub fn builder() -> ExchangeInfoParamsBuilder {
306        ExchangeInfoParamsBuilder::default()
307    }
308}
309/// Request parameters for the [`execution_rules`] operation.
310///
311/// This struct holds all of the inputs you can pass when calling
312/// [`execution_rules`](#method.execution_rules).
313#[derive(Clone, Debug, Builder, Deserialize, Default)]
314#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
315pub struct ExecutionRulesParams {
316    /// Query for specified symbol.
317    ///
318    /// This field is **optional.
319    #[builder(setter(into), default)]
320    #[serde(rename = "symbol", default)]
321    pub symbol: Option<String>,
322    /// Query for multiple symbols.
323    ///
324    /// This field is **optional.
325    #[builder(setter(into), default)]
326    #[serde(rename = "symbols", default)]
327    pub symbols: Option<Vec<String>>,
328    /// Query for all symbols with the specified status.
329    ///
330    /// This field is **optional.
331    #[builder(setter(into), default)]
332    #[serde(rename = "symbolStatus", default)]
333    pub symbol_status: Option<ExecutionRulesSymbolStatusEnum>,
334}
335
336impl ExecutionRulesParams {
337    /// Create a builder for [`execution_rules`].
338    ///
339    #[must_use]
340    pub fn builder() -> ExecutionRulesParamsBuilder {
341        ExecutionRulesParamsBuilder::default()
342    }
343}
344
345#[async_trait]
346impl GeneralApi for GeneralApiClient {
347    async fn exchange_info(
348        &self,
349        params: ExchangeInfoParams,
350    ) -> anyhow::Result<RestApiResponse<models::ExchangeInfoResponse>> {
351        let ExchangeInfoParams {
352            symbol,
353            symbols,
354            permissions,
355            show_permission_sets,
356            symbol_status,
357        } = params;
358
359        let mut query_params = BTreeMap::new();
360        let body_params = BTreeMap::new();
361
362        if let Some(rw) = symbol {
363            query_params.insert("symbol".to_string(), json!(rw));
364        }
365
366        if let Some(rw) = symbols {
367            query_params.insert("symbols".to_string(), json!(rw));
368        }
369
370        if let Some(rw) = permissions {
371            query_params.insert("permissions".to_string(), json!(rw));
372        }
373
374        if let Some(rw) = show_permission_sets {
375            query_params.insert("showPermissionSets".to_string(), json!(rw));
376        }
377
378        if let Some(rw) = symbol_status {
379            query_params.insert("symbolStatus".to_string(), json!(rw));
380        }
381
382        send_request::<models::ExchangeInfoResponse>(
383            &self.configuration,
384            "/api/v3/exchangeInfo",
385            reqwest::Method::GET,
386            query_params,
387            body_params,
388            if HAS_TIME_UNIT {
389                self.configuration.time_unit
390            } else {
391                None
392            },
393            false,
394        )
395        .await
396    }
397
398    async fn execution_rules(
399        &self,
400        params: ExecutionRulesParams,
401    ) -> anyhow::Result<RestApiResponse<models::ExecutionRulesResponse>> {
402        let ExecutionRulesParams {
403            symbol,
404            symbols,
405            symbol_status,
406        } = params;
407
408        let mut query_params = BTreeMap::new();
409        let body_params = BTreeMap::new();
410
411        if let Some(rw) = symbol {
412            query_params.insert("symbol".to_string(), json!(rw));
413        }
414
415        if let Some(rw) = symbols {
416            query_params.insert("symbols".to_string(), json!(rw));
417        }
418
419        if let Some(rw) = symbol_status {
420            query_params.insert("symbolStatus".to_string(), json!(rw));
421        }
422
423        send_request::<models::ExecutionRulesResponse>(
424            &self.configuration,
425            "/api/v3/executionRules",
426            reqwest::Method::GET,
427            query_params,
428            body_params,
429            if HAS_TIME_UNIT {
430                self.configuration.time_unit
431            } else {
432                None
433            },
434            false,
435        )
436        .await
437    }
438
439    async fn ping(&self) -> anyhow::Result<RestApiResponse<Value>> {
440        let query_params = BTreeMap::new();
441        let body_params = BTreeMap::new();
442
443        send_request::<Value>(
444            &self.configuration,
445            "/api/v3/ping",
446            reqwest::Method::GET,
447            query_params,
448            body_params,
449            if HAS_TIME_UNIT {
450                self.configuration.time_unit
451            } else {
452                None
453            },
454            false,
455        )
456        .await
457    }
458
459    async fn time(&self) -> anyhow::Result<RestApiResponse<models::TimeResponse>> {
460        let query_params = BTreeMap::new();
461        let body_params = BTreeMap::new();
462
463        send_request::<models::TimeResponse>(
464            &self.configuration,
465            "/api/v3/time",
466            reqwest::Method::GET,
467            query_params,
468            body_params,
469            if HAS_TIME_UNIT {
470                self.configuration.time_unit
471            } else {
472                None
473            },
474            false,
475        )
476        .await
477    }
478}
479
480#[cfg(all(test, feature = "spot"))]
481mod tests {
482    use super::*;
483    use crate::TOKIO_SHARED_RT;
484    use crate::{errors::ConnectorError, models::DataFuture, models::RestApiRateLimit};
485    use async_trait::async_trait;
486    use std::collections::HashMap;
487
488    struct DummyRestApiResponse<T> {
489        inner: Box<dyn FnOnce() -> DataFuture<Result<T, ConnectorError>> + Send + Sync>,
490        status: u16,
491        headers: HashMap<String, String>,
492        rate_limits: Option<Vec<RestApiRateLimit>>,
493    }
494
495    impl<T> From<DummyRestApiResponse<T>> for RestApiResponse<T> {
496        fn from(dummy: DummyRestApiResponse<T>) -> Self {
497            Self {
498                data_fn: dummy.inner,
499                status: dummy.status,
500                headers: dummy.headers,
501                rate_limits: dummy.rate_limits,
502            }
503        }
504    }
505
506    struct MockGeneralApiClient {
507        force_error: bool,
508    }
509
510    #[async_trait]
511    impl GeneralApi for MockGeneralApiClient {
512        async fn exchange_info(
513            &self,
514            _params: ExchangeInfoParams,
515        ) -> anyhow::Result<RestApiResponse<models::ExchangeInfoResponse>> {
516            if self.force_error {
517                return Err(ConnectorError::ConnectorClientError {
518                    msg: "ResponseError".to_string(),
519                    code: None,
520                }
521                .into());
522            }
523
524            let resp_json: Value = serde_json::from_str(r#"{"timezone":"UTC","serverTime":1565246363776,"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000,"count":321}],"exchangeFilters":[{"filterType":"EXCHANGE_MAX_NUM_ORDERS","maxNumOrders":1000}],"symbols":[{"symbol":"ETHBTC","status":"TRADING","baseAsset":"ETH","baseAssetPrecision":8,"quoteAsset":"BTC","quotePrecision":8,"quoteAssetPrecision":8,"baseCommissionPrecision":8,"quoteCommissionPrecision":8,"orderTypes":["LIMIT"],"icebergAllowed":true,"ocoAllowed":true,"otoAllowed":true,"opoAllowed":true,"quoteOrderQtyMarketAllowed":true,"allowTrailingStop":false,"cancelReplaceAllowed":false,"amendAllowed":false,"pegInstructionsAllowed":true,"isSpotTradingAllowed":true,"isMarginTradingAllowed":true,"filters":[{"filterType":"PRICE_FILTER","priceExponent":8,"minPrice":"0.00000100","maxPrice":"100000.00000000","tickSize":"0.00000100"}],"permissions":["undefined"],"permissionSets":[["SPOT"]],"defaultSelfTradePreventionMode":"NONE","allowedSelfTradePreventionModes":["NONE"]}],"sors":[{"baseAsset":"BTC","symbols":["BTCUSDT"]}]}"#).unwrap_or_else(|_| serde_json::json!({}));
525            let dummy_response: models::ExchangeInfoResponse =
526                serde_json::from_value(resp_json.clone())
527                    .expect("should parse into models::ExchangeInfoResponse");
528
529            let dummy = DummyRestApiResponse {
530                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
531                status: 200,
532                headers: HashMap::new(),
533                rate_limits: None,
534            };
535
536            Ok(dummy.into())
537        }
538
539        async fn execution_rules(
540            &self,
541            _params: ExecutionRulesParams,
542        ) -> anyhow::Result<RestApiResponse<models::ExecutionRulesResponse>> {
543            if self.force_error {
544                return Err(ConnectorError::ConnectorClientError {
545                    msg: "ResponseError".to_string(),
546                    code: None,
547                }
548                .into());
549            }
550
551            let resp_json: Value = serde_json::from_str(r#"{"symbolRules":[{"symbol":"BAZUSD","rules":[{"ruleType":"PRICE_RANGE","bidLimitMultUp":"1.0001","bidLimitMultDown":"0.9999","askLimitMultUp":"1.0001","askLimitMultDown":"0.9999"}]}]}"#).unwrap_or_else(|_| serde_json::json!({}));
552            let dummy_response: models::ExecutionRulesResponse =
553                serde_json::from_value(resp_json.clone())
554                    .expect("should parse into models::ExecutionRulesResponse");
555
556            let dummy = DummyRestApiResponse {
557                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
558                status: 200,
559                headers: HashMap::new(),
560                rate_limits: None,
561            };
562
563            Ok(dummy.into())
564        }
565
566        async fn ping(&self) -> anyhow::Result<RestApiResponse<Value>> {
567            if self.force_error {
568                return Err(ConnectorError::ConnectorClientError {
569                    msg: "ResponseError".to_string(),
570                    code: None,
571                }
572                .into());
573            }
574
575            let dummy_response = Value::Null;
576
577            let dummy = DummyRestApiResponse {
578                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
579                status: 200,
580                headers: HashMap::new(),
581                rate_limits: None,
582            };
583
584            Ok(dummy.into())
585        }
586
587        async fn time(&self) -> anyhow::Result<RestApiResponse<models::TimeResponse>> {
588            if self.force_error {
589                return Err(ConnectorError::ConnectorClientError {
590                    msg: "ResponseError".to_string(),
591                    code: None,
592                }
593                .into());
594            }
595
596            let resp_json: Value = serde_json::from_str(r#"{"serverTime":1499827319559}"#)
597                .unwrap_or_else(|_| serde_json::json!({}));
598            let dummy_response: models::TimeResponse = serde_json::from_value(resp_json.clone())
599                .expect("should parse into models::TimeResponse");
600
601            let dummy = DummyRestApiResponse {
602                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
603                status: 200,
604                headers: HashMap::new(),
605                rate_limits: None,
606            };
607
608            Ok(dummy.into())
609        }
610    }
611
612    #[test]
613    fn exchange_info_required_params_success() {
614        TOKIO_SHARED_RT.block_on(async {
615            let client = MockGeneralApiClient { force_error: false };
616
617            let params = ExchangeInfoParams::builder().build().unwrap();
618
619            let resp_json: Value = serde_json::from_str(r#"{"timezone":"UTC","serverTime":1565246363776,"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000,"count":321}],"exchangeFilters":[{"filterType":"EXCHANGE_MAX_NUM_ORDERS","maxNumOrders":1000}],"symbols":[{"symbol":"ETHBTC","status":"TRADING","baseAsset":"ETH","baseAssetPrecision":8,"quoteAsset":"BTC","quotePrecision":8,"quoteAssetPrecision":8,"baseCommissionPrecision":8,"quoteCommissionPrecision":8,"orderTypes":["LIMIT"],"icebergAllowed":true,"ocoAllowed":true,"otoAllowed":true,"opoAllowed":true,"quoteOrderQtyMarketAllowed":true,"allowTrailingStop":false,"cancelReplaceAllowed":false,"amendAllowed":false,"pegInstructionsAllowed":true,"isSpotTradingAllowed":true,"isMarginTradingAllowed":true,"filters":[{"filterType":"PRICE_FILTER","priceExponent":8,"minPrice":"0.00000100","maxPrice":"100000.00000000","tickSize":"0.00000100"}],"permissions":["undefined"],"permissionSets":[["SPOT"]],"defaultSelfTradePreventionMode":"NONE","allowedSelfTradePreventionModes":["NONE"]}],"sors":[{"baseAsset":"BTC","symbols":["BTCUSDT"]}]}"#).unwrap_or_else(|_| serde_json::json!({}));
620            let expected_response : models::ExchangeInfoResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::ExchangeInfoResponse");
621
622            let resp = client.exchange_info(params).await.expect("Expected a response");
623            let data_future = resp.data();
624            let actual_response = data_future.await.unwrap();
625            assert_eq!(actual_response, expected_response);
626        });
627    }
628
629    #[test]
630    fn exchange_info_optional_params_success() {
631        TOKIO_SHARED_RT.block_on(async {
632            let client = MockGeneralApiClient { force_error: false };
633
634            let params = ExchangeInfoParams::builder().symbol("ETHBTC".to_string()).symbols(["BTCUSDT".to_string(),].to_vec()).permissions(ExchangeInfoPermissionsEnum::Spot).show_permission_sets(false).symbol_status(ExchangeInfoSymbolStatusEnum::Trading).build().unwrap();
635
636            let resp_json: Value = serde_json::from_str(r#"{"timezone":"UTC","serverTime":1565246363776,"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000,"count":321}],"exchangeFilters":[{"filterType":"EXCHANGE_MAX_NUM_ORDERS","maxNumOrders":1000}],"symbols":[{"symbol":"ETHBTC","status":"TRADING","baseAsset":"ETH","baseAssetPrecision":8,"quoteAsset":"BTC","quotePrecision":8,"quoteAssetPrecision":8,"baseCommissionPrecision":8,"quoteCommissionPrecision":8,"orderTypes":["LIMIT"],"icebergAllowed":true,"ocoAllowed":true,"otoAllowed":true,"opoAllowed":true,"quoteOrderQtyMarketAllowed":true,"allowTrailingStop":false,"cancelReplaceAllowed":false,"amendAllowed":false,"pegInstructionsAllowed":true,"isSpotTradingAllowed":true,"isMarginTradingAllowed":true,"filters":[{"filterType":"PRICE_FILTER","priceExponent":8,"minPrice":"0.00000100","maxPrice":"100000.00000000","tickSize":"0.00000100"}],"permissions":["undefined"],"permissionSets":[["SPOT"]],"defaultSelfTradePreventionMode":"NONE","allowedSelfTradePreventionModes":["NONE"]}],"sors":[{"baseAsset":"BTC","symbols":["BTCUSDT"]}]}"#).unwrap_or_else(|_| serde_json::json!({}));
637            let expected_response : models::ExchangeInfoResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::ExchangeInfoResponse");
638
639            let resp = client.exchange_info(params).await.expect("Expected a response");
640            let data_future = resp.data();
641            let actual_response = data_future.await.unwrap();
642            assert_eq!(actual_response, expected_response);
643        });
644    }
645
646    #[test]
647    fn exchange_info_response_error() {
648        TOKIO_SHARED_RT.block_on(async {
649            let client = MockGeneralApiClient { force_error: true };
650
651            let params = ExchangeInfoParams::builder().build().unwrap();
652
653            match client.exchange_info(params).await {
654                Ok(_) => panic!("Expected an error"),
655                Err(err) => {
656                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
657                }
658            }
659        });
660    }
661
662    #[test]
663    fn execution_rules_required_params_success() {
664        TOKIO_SHARED_RT.block_on(async {
665            let client = MockGeneralApiClient { force_error: false };
666
667            let params = ExecutionRulesParams::builder().build().unwrap();
668
669            let resp_json: Value = serde_json::from_str(r#"{"symbolRules":[{"symbol":"BAZUSD","rules":[{"ruleType":"PRICE_RANGE","bidLimitMultUp":"1.0001","bidLimitMultDown":"0.9999","askLimitMultUp":"1.0001","askLimitMultDown":"0.9999"}]}]}"#).unwrap_or_else(|_| serde_json::json!({}));
670            let expected_response : models::ExecutionRulesResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::ExecutionRulesResponse");
671
672            let resp = client.execution_rules(params).await.expect("Expected a response");
673            let data_future = resp.data();
674            let actual_response = data_future.await.unwrap();
675            assert_eq!(actual_response, expected_response);
676        });
677    }
678
679    #[test]
680    fn execution_rules_optional_params_success() {
681        TOKIO_SHARED_RT.block_on(async {
682            let client = MockGeneralApiClient { force_error: false };
683
684            let params = ExecutionRulesParams::builder().symbol("BAZUSD".to_string()).symbols(["BAZUSD".to_string(),].to_vec()).symbol_status(ExecutionRulesSymbolStatusEnum::Trading).build().unwrap();
685
686            let resp_json: Value = serde_json::from_str(r#"{"symbolRules":[{"symbol":"BAZUSD","rules":[{"ruleType":"PRICE_RANGE","bidLimitMultUp":"1.0001","bidLimitMultDown":"0.9999","askLimitMultUp":"1.0001","askLimitMultDown":"0.9999"}]}]}"#).unwrap_or_else(|_| serde_json::json!({}));
687            let expected_response : models::ExecutionRulesResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::ExecutionRulesResponse");
688
689            let resp = client.execution_rules(params).await.expect("Expected a response");
690            let data_future = resp.data();
691            let actual_response = data_future.await.unwrap();
692            assert_eq!(actual_response, expected_response);
693        });
694    }
695
696    #[test]
697    fn execution_rules_response_error() {
698        TOKIO_SHARED_RT.block_on(async {
699            let client = MockGeneralApiClient { force_error: true };
700
701            let params = ExecutionRulesParams::builder().build().unwrap();
702
703            match client.execution_rules(params).await {
704                Ok(_) => panic!("Expected an error"),
705                Err(err) => {
706                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
707                }
708            }
709        });
710    }
711
712    #[test]
713    fn ping_required_params_success() {
714        TOKIO_SHARED_RT.block_on(async {
715            let client = MockGeneralApiClient { force_error: false };
716
717            let expected_response = Value::Null;
718
719            let resp = client.ping().await.expect("Expected a response");
720            let data_future = resp.data();
721            let actual_response = data_future.await.unwrap();
722            assert_eq!(actual_response, expected_response);
723        });
724    }
725
726    #[test]
727    fn ping_optional_params_success() {
728        TOKIO_SHARED_RT.block_on(async {
729            let client = MockGeneralApiClient { force_error: false };
730
731            let expected_response = Value::Null;
732
733            let resp = client.ping().await.expect("Expected a response");
734            let data_future = resp.data();
735            let actual_response = data_future.await.unwrap();
736            assert_eq!(actual_response, expected_response);
737        });
738    }
739
740    #[test]
741    fn ping_response_error() {
742        TOKIO_SHARED_RT.block_on(async {
743            let client = MockGeneralApiClient { force_error: true };
744
745            match client.ping().await {
746                Ok(_) => panic!("Expected an error"),
747                Err(err) => {
748                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
749                }
750            }
751        });
752    }
753
754    #[test]
755    fn time_required_params_success() {
756        TOKIO_SHARED_RT.block_on(async {
757            let client = MockGeneralApiClient { force_error: false };
758
759            let resp_json: Value = serde_json::from_str(r#"{"serverTime":1499827319559}"#)
760                .unwrap_or_else(|_| serde_json::json!({}));
761            let expected_response: models::TimeResponse = serde_json::from_value(resp_json.clone())
762                .expect("should parse into models::TimeResponse");
763
764            let resp = client.time().await.expect("Expected a response");
765            let data_future = resp.data();
766            let actual_response = data_future.await.unwrap();
767            assert_eq!(actual_response, expected_response);
768        });
769    }
770
771    #[test]
772    fn time_optional_params_success() {
773        TOKIO_SHARED_RT.block_on(async {
774            let client = MockGeneralApiClient { force_error: false };
775
776            let resp_json: Value = serde_json::from_str(r#"{"serverTime":1499827319559}"#)
777                .unwrap_or_else(|_| serde_json::json!({}));
778            let expected_response: models::TimeResponse = serde_json::from_value(resp_json.clone())
779                .expect("should parse into models::TimeResponse");
780
781            let resp = client.time().await.expect("Expected a response");
782            let data_future = resp.data();
783            let actual_response = data_future.await.unwrap();
784            assert_eq!(actual_response, expected_response);
785        });
786    }
787
788    #[test]
789    fn time_response_error() {
790        TOKIO_SHARED_RT.block_on(async {
791            let client = MockGeneralApiClient { force_error: true };
792
793            match client.time().await {
794                Ok(_) => panic!("Expected an error"),
795                Err(err) => {
796                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
797                }
798            }
799        });
800    }
801}