Skip to main content

binance_sdk/spot/rest_api/apis/
general_api.rs

1/*
2 * Binance Spot REST API
3 *
4 * OpenAPI Specifications for the Binance Spot REST API
5 *
6 * API documents:
7 * - [Github rest-api documentation file](https://github.com/binance/binance-spot-api-docs/blob/master/rest-api.md)
8 * - [General API information for rest-api on website](https://developers.binance.com/docs/binance-spot-api-docs/rest-api/general-api-information)
9 *
10 *
11 * The version of the OpenAPI document: 1.0.0
12 *
13 *
14 * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
15 * https://openapi-generator.tech
16 * Do not edit the class manually.
17 */
18
19#![allow(unused_imports)]
20use async_trait::async_trait;
21use derive_builder::Builder;
22use reqwest;
23use rust_decimal::prelude::*;
24use serde::{Deserialize, Serialize};
25use serde_json::{Value, json};
26use std::collections::BTreeMap;
27
28use crate::common::{
29    config::ConfigurationRestApi,
30    models::{ParamBuildError, RestApiResponse},
31    utils::send_request,
32};
33use crate::spot::rest_api::models;
34
35const HAS_TIME_UNIT: bool = true;
36
37#[async_trait]
38pub trait GeneralApi: Send + Sync {
39    async fn exchange_info(
40        &self,
41        params: ExchangeInfoParams,
42    ) -> anyhow::Result<RestApiResponse<models::ExchangeInfoResponse>>;
43    async fn execution_rules(
44        &self,
45        params: ExecutionRulesParams,
46    ) -> anyhow::Result<RestApiResponse<models::ExecutionRulesResponse>>;
47    async fn ping(&self) -> anyhow::Result<RestApiResponse<Value>>;
48    async fn time(&self) -> anyhow::Result<RestApiResponse<models::TimeResponse>>;
49}
50
51#[derive(Debug, Clone)]
52pub struct GeneralApiClient {
53    configuration: ConfigurationRestApi,
54}
55
56impl GeneralApiClient {
57    pub fn new(configuration: ConfigurationRestApi) -> Self {
58        Self { configuration }
59    }
60}
61
62#[allow(non_camel_case_types)]
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub enum ExchangeInfoSymbolStatusEnum {
65    #[serde(rename = "TRADING")]
66    Trading,
67    #[serde(rename = "END_OF_DAY")]
68    EndOfDay,
69    #[serde(rename = "HALT")]
70    Halt,
71    #[serde(rename = "BREAK")]
72    Break,
73    #[serde(rename = "NON_REPRESENTABLE")]
74    NonRepresentable,
75}
76
77impl ExchangeInfoSymbolStatusEnum {
78    #[must_use]
79    pub fn as_str(&self) -> &'static str {
80        match self {
81            Self::Trading => "TRADING",
82            Self::EndOfDay => "END_OF_DAY",
83            Self::Halt => "HALT",
84            Self::Break => "BREAK",
85            Self::NonRepresentable => "NON_REPRESENTABLE",
86        }
87    }
88}
89
90impl std::str::FromStr for ExchangeInfoSymbolStatusEnum {
91    type Err = Box<dyn std::error::Error + Send + Sync>;
92
93    fn from_str(s: &str) -> Result<Self, Self::Err> {
94        match s {
95            "TRADING" => Ok(Self::Trading),
96            "END_OF_DAY" => Ok(Self::EndOfDay),
97            "HALT" => Ok(Self::Halt),
98            "BREAK" => Ok(Self::Break),
99            "NON_REPRESENTABLE" => Ok(Self::NonRepresentable),
100            other => Err(format!("invalid ExchangeInfoSymbolStatusEnum: {}", other).into()),
101        }
102    }
103}
104
105#[allow(non_camel_case_types)]
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub enum ExecutionRulesSymbolStatusEnum {
108    #[serde(rename = "TRADING")]
109    Trading,
110    #[serde(rename = "END_OF_DAY")]
111    EndOfDay,
112    #[serde(rename = "HALT")]
113    Halt,
114    #[serde(rename = "BREAK")]
115    Break,
116    #[serde(rename = "NON_REPRESENTABLE")]
117    NonRepresentable,
118}
119
120impl ExecutionRulesSymbolStatusEnum {
121    #[must_use]
122    pub fn as_str(&self) -> &'static str {
123        match self {
124            Self::Trading => "TRADING",
125            Self::EndOfDay => "END_OF_DAY",
126            Self::Halt => "HALT",
127            Self::Break => "BREAK",
128            Self::NonRepresentable => "NON_REPRESENTABLE",
129        }
130    }
131}
132
133impl std::str::FromStr for ExecutionRulesSymbolStatusEnum {
134    type Err = Box<dyn std::error::Error + Send + Sync>;
135
136    fn from_str(s: &str) -> Result<Self, Self::Err> {
137        match s {
138            "TRADING" => Ok(Self::Trading),
139            "END_OF_DAY" => Ok(Self::EndOfDay),
140            "HALT" => Ok(Self::Halt),
141            "BREAK" => Ok(Self::Break),
142            "NON_REPRESENTABLE" => Ok(Self::NonRepresentable),
143            other => Err(format!("invalid ExecutionRulesSymbolStatusEnum: {}", other).into()),
144        }
145    }
146}
147
148/// Request parameters for the [`exchange_info`] operation.
149///
150/// This struct holds all of the inputs you can pass when calling
151/// [`exchange_info`](#method.exchange_info).
152#[derive(Clone, Debug, Builder, Default)]
153#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
154pub struct ExchangeInfoParams {
155    /// Symbol to query
156    ///
157    /// This field is **optional.
158    #[builder(setter(into), default)]
159    pub symbol: Option<String>,
160    /// List of symbols to query
161    ///
162    /// This field is **optional.
163    #[builder(setter(into), default)]
164    pub symbols: Option<Vec<String>>,
165    /// List of permissions to query
166    ///
167    /// This field is **optional.
168    #[builder(setter(into), default)]
169    pub permissions: Option<Vec<String>>,
170    /// Controls whether the content of the `permissionSets` field is populated or not. Defaults to `true`
171    ///
172    /// This field is **optional.
173    #[builder(setter(into), default)]
174    pub show_permission_sets: Option<bool>,
175    ///
176    /// The `symbol_status` parameter.
177    ///
178    /// This field is **optional.
179    #[builder(setter(into), default)]
180    pub symbol_status: Option<ExchangeInfoSymbolStatusEnum>,
181}
182
183impl ExchangeInfoParams {
184    /// Create a builder for [`exchange_info`].
185    ///
186    #[must_use]
187    pub fn builder() -> ExchangeInfoParamsBuilder {
188        ExchangeInfoParamsBuilder::default()
189    }
190}
191/// Request parameters for the [`execution_rules`] operation.
192///
193/// This struct holds all of the inputs you can pass when calling
194/// [`execution_rules`](#method.execution_rules).
195#[derive(Clone, Debug, Builder, Default)]
196#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
197pub struct ExecutionRulesParams {
198    /// Symbol to query
199    ///
200    /// This field is **optional.
201    #[builder(setter(into), default)]
202    pub symbol: Option<String>,
203    /// List of symbols to query
204    ///
205    /// This field is **optional.
206    #[builder(setter(into), default)]
207    pub symbols: Option<Vec<String>>,
208    ///
209    /// The `symbol_status` parameter.
210    ///
211    /// This field is **optional.
212    #[builder(setter(into), default)]
213    pub symbol_status: Option<ExecutionRulesSymbolStatusEnum>,
214}
215
216impl ExecutionRulesParams {
217    /// Create a builder for [`execution_rules`].
218    ///
219    #[must_use]
220    pub fn builder() -> ExecutionRulesParamsBuilder {
221        ExecutionRulesParamsBuilder::default()
222    }
223}
224
225#[async_trait]
226impl GeneralApi for GeneralApiClient {
227    async fn exchange_info(
228        &self,
229        params: ExchangeInfoParams,
230    ) -> anyhow::Result<RestApiResponse<models::ExchangeInfoResponse>> {
231        let ExchangeInfoParams {
232            symbol,
233            symbols,
234            permissions,
235            show_permission_sets,
236            symbol_status,
237        } = params;
238
239        let mut query_params = BTreeMap::new();
240        let body_params = BTreeMap::new();
241
242        if let Some(rw) = symbol {
243            query_params.insert("symbol".to_string(), json!(rw));
244        }
245
246        if let Some(rw) = symbols {
247            query_params.insert("symbols".to_string(), json!(rw));
248        }
249
250        if let Some(rw) = permissions {
251            query_params.insert("permissions".to_string(), json!(rw));
252        }
253
254        if let Some(rw) = show_permission_sets {
255            query_params.insert("showPermissionSets".to_string(), json!(rw));
256        }
257
258        if let Some(rw) = symbol_status {
259            query_params.insert("symbolStatus".to_string(), json!(rw));
260        }
261
262        send_request::<models::ExchangeInfoResponse>(
263            &self.configuration,
264            "/api/v3/exchangeInfo",
265            reqwest::Method::GET,
266            query_params,
267            body_params,
268            if HAS_TIME_UNIT {
269                self.configuration.time_unit
270            } else {
271                None
272            },
273            false,
274        )
275        .await
276    }
277
278    async fn execution_rules(
279        &self,
280        params: ExecutionRulesParams,
281    ) -> anyhow::Result<RestApiResponse<models::ExecutionRulesResponse>> {
282        let ExecutionRulesParams {
283            symbol,
284            symbols,
285            symbol_status,
286        } = params;
287
288        let mut query_params = BTreeMap::new();
289        let body_params = BTreeMap::new();
290
291        if let Some(rw) = symbol {
292            query_params.insert("symbol".to_string(), json!(rw));
293        }
294
295        if let Some(rw) = symbols {
296            query_params.insert("symbols".to_string(), json!(rw));
297        }
298
299        if let Some(rw) = symbol_status {
300            query_params.insert("symbolStatus".to_string(), json!(rw));
301        }
302
303        send_request::<models::ExecutionRulesResponse>(
304            &self.configuration,
305            "/api/v3/executionRules",
306            reqwest::Method::GET,
307            query_params,
308            body_params,
309            if HAS_TIME_UNIT {
310                self.configuration.time_unit
311            } else {
312                None
313            },
314            false,
315        )
316        .await
317    }
318
319    async fn ping(&self) -> anyhow::Result<RestApiResponse<Value>> {
320        let query_params = BTreeMap::new();
321        let body_params = BTreeMap::new();
322
323        send_request::<Value>(
324            &self.configuration,
325            "/api/v3/ping",
326            reqwest::Method::GET,
327            query_params,
328            body_params,
329            if HAS_TIME_UNIT {
330                self.configuration.time_unit
331            } else {
332                None
333            },
334            false,
335        )
336        .await
337    }
338
339    async fn time(&self) -> anyhow::Result<RestApiResponse<models::TimeResponse>> {
340        let query_params = BTreeMap::new();
341        let body_params = BTreeMap::new();
342
343        send_request::<models::TimeResponse>(
344            &self.configuration,
345            "/api/v3/time",
346            reqwest::Method::GET,
347            query_params,
348            body_params,
349            if HAS_TIME_UNIT {
350                self.configuration.time_unit
351            } else {
352                None
353            },
354            false,
355        )
356        .await
357    }
358}
359
360#[cfg(all(test, feature = "spot"))]
361mod tests {
362    use super::*;
363    use crate::TOKIO_SHARED_RT;
364    use crate::{errors::ConnectorError, models::DataFuture, models::RestApiRateLimit};
365    use async_trait::async_trait;
366    use std::collections::HashMap;
367
368    struct DummyRestApiResponse<T> {
369        inner: Box<dyn FnOnce() -> DataFuture<Result<T, ConnectorError>> + Send + Sync>,
370        status: u16,
371        headers: HashMap<String, String>,
372        rate_limits: Option<Vec<RestApiRateLimit>>,
373    }
374
375    impl<T> From<DummyRestApiResponse<T>> for RestApiResponse<T> {
376        fn from(dummy: DummyRestApiResponse<T>) -> Self {
377            Self {
378                data_fn: dummy.inner,
379                status: dummy.status,
380                headers: dummy.headers,
381                rate_limits: dummy.rate_limits,
382            }
383        }
384    }
385
386    struct MockGeneralApiClient {
387        force_error: bool,
388    }
389
390    #[async_trait]
391    impl GeneralApi for MockGeneralApiClient {
392        async fn exchange_info(
393            &self,
394            _params: ExchangeInfoParams,
395        ) -> anyhow::Result<RestApiResponse<models::ExchangeInfoResponse>> {
396            if self.force_error {
397                return Err(ConnectorError::ConnectorClientError {
398                    msg: "ResponseError".to_string(),
399                    code: None,
400                }
401                .into());
402            }
403
404            let resp_json: Value = serde_json::from_str(r#"{"timezone":"UTC","serverTime":1565246363776,"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000},{"rateLimitType":"ORDERS","interval":"DAY","intervalNum":1,"limit":160000},{"rateLimitType":"RAW_REQUESTS","interval":"MINUTE","intervalNum":5,"limit":61000}],"exchangeFilters":[],"symbols":[{"symbol":"ETHBTC","status":"TRADING","baseAsset":"ETH","baseAssetPrecision":8,"quoteAsset":"BTC","quotePrecision":8,"quoteAssetPrecision":8,"baseCommissionPrecision":8,"quoteCommissionPrecision":8,"orderTypes":["LIMIT LIMIT_MAKER MARKET STOP_LOSS STOP_LOSS_LIMIT TAKE_PROFIT TAKE_PROFIT_LIMIT"],"icebergAllowed":true,"ocoAllowed":true,"otoAllowed":true,"opoAllowed":true,"quoteOrderQtyMarketAllowed":true,"allowTrailingStop":false,"cancelReplaceAllowed":false,"amendAllowed":false,"pegInstructionsAllowed":true,"isSpotTradingAllowed":true,"isMarginTradingAllowed":true,"filters":[],"permissions":[],"permissionSets":[["SPOT","MARGIN"]],"defaultSelfTradePreventionMode":"NONE","allowedSelfTradePreventionModes":["NONE"]}]}"#).unwrap();
405            let dummy_response: models::ExchangeInfoResponse =
406                serde_json::from_value(resp_json.clone())
407                    .expect("should parse into models::ExchangeInfoResponse");
408
409            let dummy = DummyRestApiResponse {
410                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
411                status: 200,
412                headers: HashMap::new(),
413                rate_limits: None,
414            };
415
416            Ok(dummy.into())
417        }
418
419        async fn execution_rules(
420            &self,
421            _params: ExecutionRulesParams,
422        ) -> anyhow::Result<RestApiResponse<models::ExecutionRulesResponse>> {
423            if self.force_error {
424                return Err(ConnectorError::ConnectorClientError {
425                    msg: "ResponseError".to_string(),
426                    code: None,
427                }
428                .into());
429            }
430
431            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();
432            let dummy_response: models::ExecutionRulesResponse =
433                serde_json::from_value(resp_json.clone())
434                    .expect("should parse into models::ExecutionRulesResponse");
435
436            let dummy = DummyRestApiResponse {
437                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
438                status: 200,
439                headers: HashMap::new(),
440                rate_limits: None,
441            };
442
443            Ok(dummy.into())
444        }
445
446        async fn ping(&self) -> anyhow::Result<RestApiResponse<Value>> {
447            if self.force_error {
448                return Err(ConnectorError::ConnectorClientError {
449                    msg: "ResponseError".to_string(),
450                    code: None,
451                }
452                .into());
453            }
454
455            let dummy_response = Value::Null;
456
457            let dummy = DummyRestApiResponse {
458                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
459                status: 200,
460                headers: HashMap::new(),
461                rate_limits: None,
462            };
463
464            Ok(dummy.into())
465        }
466
467        async fn time(&self) -> anyhow::Result<RestApiResponse<models::TimeResponse>> {
468            if self.force_error {
469                return Err(ConnectorError::ConnectorClientError {
470                    msg: "ResponseError".to_string(),
471                    code: None,
472                }
473                .into());
474            }
475
476            let resp_json: Value = serde_json::from_str(r#"{"serverTime":1499827319559}"#).unwrap();
477            let dummy_response: models::TimeResponse = serde_json::from_value(resp_json.clone())
478                .expect("should parse into models::TimeResponse");
479
480            let dummy = DummyRestApiResponse {
481                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
482                status: 200,
483                headers: HashMap::new(),
484                rate_limits: None,
485            };
486
487            Ok(dummy.into())
488        }
489    }
490
491    #[test]
492    fn exchange_info_required_params_success() {
493        TOKIO_SHARED_RT.block_on(async {
494            let client = MockGeneralApiClient { force_error: false };
495
496            let params = ExchangeInfoParams::builder().build().unwrap();
497
498            let resp_json: Value = serde_json::from_str(r#"{"timezone":"UTC","serverTime":1565246363776,"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000},{"rateLimitType":"ORDERS","interval":"DAY","intervalNum":1,"limit":160000},{"rateLimitType":"RAW_REQUESTS","interval":"MINUTE","intervalNum":5,"limit":61000}],"exchangeFilters":[],"symbols":[{"symbol":"ETHBTC","status":"TRADING","baseAsset":"ETH","baseAssetPrecision":8,"quoteAsset":"BTC","quotePrecision":8,"quoteAssetPrecision":8,"baseCommissionPrecision":8,"quoteCommissionPrecision":8,"orderTypes":["LIMIT LIMIT_MAKER MARKET STOP_LOSS STOP_LOSS_LIMIT TAKE_PROFIT TAKE_PROFIT_LIMIT"],"icebergAllowed":true,"ocoAllowed":true,"otoAllowed":true,"opoAllowed":true,"quoteOrderQtyMarketAllowed":true,"allowTrailingStop":false,"cancelReplaceAllowed":false,"amendAllowed":false,"pegInstructionsAllowed":true,"isSpotTradingAllowed":true,"isMarginTradingAllowed":true,"filters":[],"permissions":[],"permissionSets":[["SPOT","MARGIN"]],"defaultSelfTradePreventionMode":"NONE","allowedSelfTradePreventionModes":["NONE"]}]}"#).unwrap();
499            let expected_response : models::ExchangeInfoResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::ExchangeInfoResponse");
500
501            let resp = client.exchange_info(params).await.expect("Expected a response");
502            let data_future = resp.data();
503            let actual_response = data_future.await.unwrap();
504            assert_eq!(actual_response, expected_response);
505        });
506    }
507
508    #[test]
509    fn exchange_info_optional_params_success() {
510        TOKIO_SHARED_RT.block_on(async {
511            let client = MockGeneralApiClient { force_error: false };
512
513            let params = ExchangeInfoParams::builder().symbol("BNBUSDT".to_string()).symbols(["null".to_string(),].to_vec()).permissions(["null".to_string(),].to_vec()).show_permission_sets(true).symbol_status(ExchangeInfoSymbolStatusEnum::Trading).build().unwrap();
514
515            let resp_json: Value = serde_json::from_str(r#"{"timezone":"UTC","serverTime":1565246363776,"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000},{"rateLimitType":"ORDERS","interval":"DAY","intervalNum":1,"limit":160000},{"rateLimitType":"RAW_REQUESTS","interval":"MINUTE","intervalNum":5,"limit":61000}],"exchangeFilters":[],"symbols":[{"symbol":"ETHBTC","status":"TRADING","baseAsset":"ETH","baseAssetPrecision":8,"quoteAsset":"BTC","quotePrecision":8,"quoteAssetPrecision":8,"baseCommissionPrecision":8,"quoteCommissionPrecision":8,"orderTypes":["LIMIT LIMIT_MAKER MARKET STOP_LOSS STOP_LOSS_LIMIT TAKE_PROFIT TAKE_PROFIT_LIMIT"],"icebergAllowed":true,"ocoAllowed":true,"otoAllowed":true,"opoAllowed":true,"quoteOrderQtyMarketAllowed":true,"allowTrailingStop":false,"cancelReplaceAllowed":false,"amendAllowed":false,"pegInstructionsAllowed":true,"isSpotTradingAllowed":true,"isMarginTradingAllowed":true,"filters":[],"permissions":[],"permissionSets":[["SPOT","MARGIN"]],"defaultSelfTradePreventionMode":"NONE","allowedSelfTradePreventionModes":["NONE"]}]}"#).unwrap();
516            let expected_response : models::ExchangeInfoResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::ExchangeInfoResponse");
517
518            let resp = client.exchange_info(params).await.expect("Expected a response");
519            let data_future = resp.data();
520            let actual_response = data_future.await.unwrap();
521            assert_eq!(actual_response, expected_response);
522        });
523    }
524
525    #[test]
526    fn exchange_info_response_error() {
527        TOKIO_SHARED_RT.block_on(async {
528            let client = MockGeneralApiClient { force_error: true };
529
530            let params = ExchangeInfoParams::builder().build().unwrap();
531
532            match client.exchange_info(params).await {
533                Ok(_) => panic!("Expected an error"),
534                Err(err) => {
535                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
536                }
537            }
538        });
539    }
540
541    #[test]
542    fn execution_rules_required_params_success() {
543        TOKIO_SHARED_RT.block_on(async {
544            let client = MockGeneralApiClient { force_error: false };
545
546            let params = ExecutionRulesParams::builder().build().unwrap();
547
548            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();
549            let expected_response : models::ExecutionRulesResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::ExecutionRulesResponse");
550
551            let resp = client.execution_rules(params).await.expect("Expected a response");
552            let data_future = resp.data();
553            let actual_response = data_future.await.unwrap();
554            assert_eq!(actual_response, expected_response);
555        });
556    }
557
558    #[test]
559    fn execution_rules_optional_params_success() {
560        TOKIO_SHARED_RT.block_on(async {
561            let client = MockGeneralApiClient { force_error: false };
562
563            let params = ExecutionRulesParams::builder().symbol("BNBUSDT".to_string()).symbols(["null".to_string(),].to_vec()).symbol_status(ExecutionRulesSymbolStatusEnum::Trading).build().unwrap();
564
565            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();
566            let expected_response : models::ExecutionRulesResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::ExecutionRulesResponse");
567
568            let resp = client.execution_rules(params).await.expect("Expected a response");
569            let data_future = resp.data();
570            let actual_response = data_future.await.unwrap();
571            assert_eq!(actual_response, expected_response);
572        });
573    }
574
575    #[test]
576    fn execution_rules_response_error() {
577        TOKIO_SHARED_RT.block_on(async {
578            let client = MockGeneralApiClient { force_error: true };
579
580            let params = ExecutionRulesParams::builder().build().unwrap();
581
582            match client.execution_rules(params).await {
583                Ok(_) => panic!("Expected an error"),
584                Err(err) => {
585                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
586                }
587            }
588        });
589    }
590
591    #[test]
592    fn ping_required_params_success() {
593        TOKIO_SHARED_RT.block_on(async {
594            let client = MockGeneralApiClient { force_error: false };
595
596            let expected_response = Value::Null;
597
598            let resp = client.ping().await.expect("Expected a response");
599            let data_future = resp.data();
600            let actual_response = data_future.await.unwrap();
601            assert_eq!(actual_response, expected_response);
602        });
603    }
604
605    #[test]
606    fn ping_optional_params_success() {
607        TOKIO_SHARED_RT.block_on(async {
608            let client = MockGeneralApiClient { force_error: false };
609
610            let expected_response = Value::Null;
611
612            let resp = client.ping().await.expect("Expected a response");
613            let data_future = resp.data();
614            let actual_response = data_future.await.unwrap();
615            assert_eq!(actual_response, expected_response);
616        });
617    }
618
619    #[test]
620    fn ping_response_error() {
621        TOKIO_SHARED_RT.block_on(async {
622            let client = MockGeneralApiClient { force_error: true };
623
624            match client.ping().await {
625                Ok(_) => panic!("Expected an error"),
626                Err(err) => {
627                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
628                }
629            }
630        });
631    }
632
633    #[test]
634    fn time_required_params_success() {
635        TOKIO_SHARED_RT.block_on(async {
636            let client = MockGeneralApiClient { force_error: false };
637
638            let resp_json: Value = serde_json::from_str(r#"{"serverTime":1499827319559}"#).unwrap();
639            let expected_response: models::TimeResponse = serde_json::from_value(resp_json.clone())
640                .expect("should parse into models::TimeResponse");
641
642            let resp = client.time().await.expect("Expected a response");
643            let data_future = resp.data();
644            let actual_response = data_future.await.unwrap();
645            assert_eq!(actual_response, expected_response);
646        });
647    }
648
649    #[test]
650    fn time_optional_params_success() {
651        TOKIO_SHARED_RT.block_on(async {
652            let client = MockGeneralApiClient { force_error: false };
653
654            let resp_json: Value = serde_json::from_str(r#"{"serverTime":1499827319559}"#).unwrap();
655            let expected_response: models::TimeResponse = serde_json::from_value(resp_json.clone())
656                .expect("should parse into models::TimeResponse");
657
658            let resp = client.time().await.expect("Expected a response");
659            let data_future = resp.data();
660            let actual_response = data_future.await.unwrap();
661            assert_eq!(actual_response, expected_response);
662        });
663    }
664
665    #[test]
666    fn time_response_error() {
667        TOKIO_SHARED_RT.block_on(async {
668            let client = MockGeneralApiClient { force_error: true };
669
670            match client.time().await {
671                Ok(_) => panic!("Expected an error"),
672                Err(err) => {
673                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
674                }
675            }
676        });
677    }
678}