Skip to main content

binance_sdk/spot/websocket_api/apis/
market_api.rs

1/*
2 * Binance Spot WebSocket API
3 *
4 * OpenAPI Specifications for the Binance Spot WebSocket API
5 *
6 * API documents:
7 * - [Github web-socket-api documentation file](https://github.com/binance/binance-spot-api-docs/blob/master/web-socket-api.md)
8 * - [General API information for web-socket-api on website](https://developers.binance.com/docs/binance-spot-api-docs/web-socket-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 anyhow::Context;
21use async_trait::async_trait;
22use derive_builder::Builder;
23use rust_decimal::prelude::*;
24use serde::{Deserialize, Serialize};
25use serde_json::Value;
26use std::{collections::BTreeMap, sync::Arc};
27
28use crate::common::{
29    errors::WebsocketError,
30    models::{ParamBuildError, WebsocketApiResponse},
31    utils::remove_empty_value,
32    websocket::{WebsocketApi, WebsocketMessageSendOptions},
33};
34use crate::spot::websocket_api::models;
35
36#[async_trait]
37pub trait MarketApi: Send + Sync {
38    async fn avg_price(
39        &self,
40        params: AvgPriceParams,
41    ) -> anyhow::Result<WebsocketApiResponse<Box<models::AvgPriceResponseResult>>>;
42    async fn block_trades_historical(
43        &self,
44        params: BlockTradesHistoricalParams,
45    ) -> anyhow::Result<WebsocketApiResponse<Vec<models::BlockTradesHistoricalResponseResultInner>>>;
46    async fn depth(
47        &self,
48        params: DepthParams,
49    ) -> anyhow::Result<WebsocketApiResponse<Box<models::DepthResponseResult>>>;
50    async fn klines(
51        &self,
52        params: KlinesParams,
53    ) -> anyhow::Result<WebsocketApiResponse<Vec<Vec<models::KlinesItemInner>>>>;
54    async fn reference_price(
55        &self,
56        params: ReferencePriceParams,
57    ) -> anyhow::Result<WebsocketApiResponse<Box<models::ReferencePriceResponseResult>>>;
58    async fn reference_price_calculation(
59        &self,
60        params: ReferencePriceCalculationParams,
61    ) -> anyhow::Result<WebsocketApiResponse<Box<models::ReferencePriceCalculationResponseResult>>>;
62    async fn ticker(
63        &self,
64        params: TickerParams,
65    ) -> anyhow::Result<WebsocketApiResponse<models::TickerResponse>>;
66    async fn ticker24hr(
67        &self,
68        params: Ticker24hrParams,
69    ) -> anyhow::Result<WebsocketApiResponse<models::Ticker24hrResponse>>;
70    async fn ticker_book(
71        &self,
72        params: TickerBookParams,
73    ) -> anyhow::Result<WebsocketApiResponse<models::TickerBookResponse>>;
74    async fn ticker_price(
75        &self,
76        params: TickerPriceParams,
77    ) -> anyhow::Result<WebsocketApiResponse<models::TickerPriceResponse>>;
78    async fn ticker_trading_day(
79        &self,
80        params: TickerTradingDayParams,
81    ) -> anyhow::Result<WebsocketApiResponse<Vec<models::TickerTradingDayResponseResultInner>>>;
82    async fn trades_aggregate(
83        &self,
84        params: TradesAggregateParams,
85    ) -> anyhow::Result<WebsocketApiResponse<Vec<models::TradesAggregateResponseResultInner>>>;
86    async fn trades_historical(
87        &self,
88        params: TradesHistoricalParams,
89    ) -> anyhow::Result<WebsocketApiResponse<Vec<models::TradesHistoricalResponseResultInner>>>;
90    async fn trades_recent(
91        &self,
92        params: TradesRecentParams,
93    ) -> anyhow::Result<WebsocketApiResponse<Vec<models::TradesRecentResponseResultInner>>>;
94    async fn ui_klines(
95        &self,
96        params: UiKlinesParams,
97    ) -> anyhow::Result<WebsocketApiResponse<Vec<Vec<models::KlinesItemInner>>>>;
98}
99
100#[derive(Clone)]
101pub struct MarketApiClient {
102    websocket_api_base: Arc<WebsocketApi>,
103}
104
105impl MarketApiClient {
106    pub fn new(websocket_api_base: Arc<WebsocketApi>) -> Self {
107        Self { websocket_api_base }
108    }
109}
110
111#[allow(non_camel_case_types)]
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub enum DepthSymbolStatusEnum {
114    #[serde(rename = "TRADING")]
115    Trading,
116    #[serde(rename = "END_OF_DAY")]
117    EndOfDay,
118    #[serde(rename = "HALT")]
119    Halt,
120    #[serde(rename = "BREAK")]
121    Break,
122    #[serde(rename = "NON_REPRESENTABLE")]
123    NonRepresentable,
124}
125
126impl DepthSymbolStatusEnum {
127    #[must_use]
128    pub fn as_str(&self) -> &'static str {
129        match self {
130            Self::Trading => "TRADING",
131            Self::EndOfDay => "END_OF_DAY",
132            Self::Halt => "HALT",
133            Self::Break => "BREAK",
134            Self::NonRepresentable => "NON_REPRESENTABLE",
135        }
136    }
137}
138
139impl std::str::FromStr for DepthSymbolStatusEnum {
140    type Err = Box<dyn std::error::Error + Send + Sync>;
141
142    fn from_str(s: &str) -> Result<Self, Self::Err> {
143        match s {
144            "TRADING" => Ok(Self::Trading),
145            "END_OF_DAY" => Ok(Self::EndOfDay),
146            "HALT" => Ok(Self::Halt),
147            "BREAK" => Ok(Self::Break),
148            "NON_REPRESENTABLE" => Ok(Self::NonRepresentable),
149            other => Err(format!("invalid DepthSymbolStatusEnum: {}", other).into()),
150        }
151    }
152}
153
154#[allow(non_camel_case_types)]
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub enum KlinesIntervalEnum {
157    #[serde(rename = "1s")]
158    Interval1s,
159    #[serde(rename = "1m")]
160    Interval1m,
161    #[serde(rename = "3m")]
162    Interval3m,
163    #[serde(rename = "5m")]
164    Interval5m,
165    #[serde(rename = "15m")]
166    Interval15m,
167    #[serde(rename = "30m")]
168    Interval30m,
169    #[serde(rename = "1h")]
170    Interval1h,
171    #[serde(rename = "2h")]
172    Interval2h,
173    #[serde(rename = "4h")]
174    Interval4h,
175    #[serde(rename = "6h")]
176    Interval6h,
177    #[serde(rename = "8h")]
178    Interval8h,
179    #[serde(rename = "12h")]
180    Interval12h,
181    #[serde(rename = "1d")]
182    Interval1d,
183    #[serde(rename = "3d")]
184    Interval3d,
185    #[serde(rename = "1w")]
186    Interval1w,
187    #[serde(rename = "1M")]
188    Interval1M,
189}
190
191impl KlinesIntervalEnum {
192    #[must_use]
193    pub fn as_str(&self) -> &'static str {
194        match self {
195            Self::Interval1s => "1s",
196            Self::Interval1m => "1m",
197            Self::Interval3m => "3m",
198            Self::Interval5m => "5m",
199            Self::Interval15m => "15m",
200            Self::Interval30m => "30m",
201            Self::Interval1h => "1h",
202            Self::Interval2h => "2h",
203            Self::Interval4h => "4h",
204            Self::Interval6h => "6h",
205            Self::Interval8h => "8h",
206            Self::Interval12h => "12h",
207            Self::Interval1d => "1d",
208            Self::Interval3d => "3d",
209            Self::Interval1w => "1w",
210            Self::Interval1M => "1M",
211        }
212    }
213}
214
215impl std::str::FromStr for KlinesIntervalEnum {
216    type Err = Box<dyn std::error::Error + Send + Sync>;
217
218    fn from_str(s: &str) -> Result<Self, Self::Err> {
219        match s {
220            "1s" => Ok(Self::Interval1s),
221            "1m" => Ok(Self::Interval1m),
222            "3m" => Ok(Self::Interval3m),
223            "5m" => Ok(Self::Interval5m),
224            "15m" => Ok(Self::Interval15m),
225            "30m" => Ok(Self::Interval30m),
226            "1h" => Ok(Self::Interval1h),
227            "2h" => Ok(Self::Interval2h),
228            "4h" => Ok(Self::Interval4h),
229            "6h" => Ok(Self::Interval6h),
230            "8h" => Ok(Self::Interval8h),
231            "12h" => Ok(Self::Interval12h),
232            "1d" => Ok(Self::Interval1d),
233            "3d" => Ok(Self::Interval3d),
234            "1w" => Ok(Self::Interval1w),
235            "1M" => Ok(Self::Interval1M),
236            other => Err(format!("invalid KlinesIntervalEnum: {}", other).into()),
237        }
238    }
239}
240
241#[allow(non_camel_case_types)]
242#[derive(Debug, Clone, Serialize, Deserialize)]
243pub enum ReferencePriceCalculationSymbolStatusEnum {
244    #[serde(rename = "TRADING")]
245    Trading,
246    #[serde(rename = "END_OF_DAY")]
247    EndOfDay,
248    #[serde(rename = "HALT")]
249    Halt,
250    #[serde(rename = "BREAK")]
251    Break,
252    #[serde(rename = "NON_REPRESENTABLE")]
253    NonRepresentable,
254}
255
256impl ReferencePriceCalculationSymbolStatusEnum {
257    #[must_use]
258    pub fn as_str(&self) -> &'static str {
259        match self {
260            Self::Trading => "TRADING",
261            Self::EndOfDay => "END_OF_DAY",
262            Self::Halt => "HALT",
263            Self::Break => "BREAK",
264            Self::NonRepresentable => "NON_REPRESENTABLE",
265        }
266    }
267}
268
269impl std::str::FromStr for ReferencePriceCalculationSymbolStatusEnum {
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            "TRADING" => Ok(Self::Trading),
275            "END_OF_DAY" => Ok(Self::EndOfDay),
276            "HALT" => Ok(Self::Halt),
277            "BREAK" => Ok(Self::Break),
278            "NON_REPRESENTABLE" => Ok(Self::NonRepresentable),
279            other => Err(format!(
280                "invalid ReferencePriceCalculationSymbolStatusEnum: {}",
281                other
282            )
283            .into()),
284        }
285    }
286}
287
288#[allow(non_camel_case_types)]
289#[derive(Debug, Clone, Serialize, Deserialize)]
290pub enum TickerTypeEnum {
291    #[serde(rename = "FULL")]
292    Full,
293    #[serde(rename = "MINI")]
294    Mini,
295}
296
297impl TickerTypeEnum {
298    #[must_use]
299    pub fn as_str(&self) -> &'static str {
300        match self {
301            Self::Full => "FULL",
302            Self::Mini => "MINI",
303        }
304    }
305}
306
307impl std::str::FromStr for TickerTypeEnum {
308    type Err = Box<dyn std::error::Error + Send + Sync>;
309
310    fn from_str(s: &str) -> Result<Self, Self::Err> {
311        match s {
312            "FULL" => Ok(Self::Full),
313            "MINI" => Ok(Self::Mini),
314            other => Err(format!("invalid TickerTypeEnum: {}", other).into()),
315        }
316    }
317}
318
319#[allow(non_camel_case_types)]
320#[derive(Debug, Clone, Serialize, Deserialize)]
321pub enum TickerWindowSizeEnum {
322    #[serde(rename = "1m")]
323    WindowSize1m,
324    #[serde(rename = "2m")]
325    WindowSize2m,
326    #[serde(rename = "3m")]
327    WindowSize3m,
328    #[serde(rename = "4m")]
329    WindowSize4m,
330    #[serde(rename = "5m")]
331    WindowSize5m,
332    #[serde(rename = "6m")]
333    WindowSize6m,
334    #[serde(rename = "7m")]
335    WindowSize7m,
336    #[serde(rename = "8m")]
337    WindowSize8m,
338    #[serde(rename = "9m")]
339    WindowSize9m,
340    #[serde(rename = "10m")]
341    WindowSize10m,
342    #[serde(rename = "11m")]
343    WindowSize11m,
344    #[serde(rename = "12m")]
345    WindowSize12m,
346    #[serde(rename = "13m")]
347    WindowSize13m,
348    #[serde(rename = "14m")]
349    WindowSize14m,
350    #[serde(rename = "15m")]
351    WindowSize15m,
352    #[serde(rename = "16m")]
353    WindowSize16m,
354    #[serde(rename = "17m")]
355    WindowSize17m,
356    #[serde(rename = "18m")]
357    WindowSize18m,
358    #[serde(rename = "19m")]
359    WindowSize19m,
360    #[serde(rename = "20m")]
361    WindowSize20m,
362    #[serde(rename = "21m")]
363    WindowSize21m,
364    #[serde(rename = "22m")]
365    WindowSize22m,
366    #[serde(rename = "23m")]
367    WindowSize23m,
368    #[serde(rename = "24m")]
369    WindowSize24m,
370    #[serde(rename = "25m")]
371    WindowSize25m,
372    #[serde(rename = "26m")]
373    WindowSize26m,
374    #[serde(rename = "27m")]
375    WindowSize27m,
376    #[serde(rename = "28m")]
377    WindowSize28m,
378    #[serde(rename = "29m")]
379    WindowSize29m,
380    #[serde(rename = "30m")]
381    WindowSize30m,
382    #[serde(rename = "31m")]
383    WindowSize31m,
384    #[serde(rename = "32m")]
385    WindowSize32m,
386    #[serde(rename = "33m")]
387    WindowSize33m,
388    #[serde(rename = "34m")]
389    WindowSize34m,
390    #[serde(rename = "35m")]
391    WindowSize35m,
392    #[serde(rename = "36m")]
393    WindowSize36m,
394    #[serde(rename = "37m")]
395    WindowSize37m,
396    #[serde(rename = "38m")]
397    WindowSize38m,
398    #[serde(rename = "39m")]
399    WindowSize39m,
400    #[serde(rename = "40m")]
401    WindowSize40m,
402    #[serde(rename = "41m")]
403    WindowSize41m,
404    #[serde(rename = "42m")]
405    WindowSize42m,
406    #[serde(rename = "43m")]
407    WindowSize43m,
408    #[serde(rename = "44m")]
409    WindowSize44m,
410    #[serde(rename = "45m")]
411    WindowSize45m,
412    #[serde(rename = "46m")]
413    WindowSize46m,
414    #[serde(rename = "47m")]
415    WindowSize47m,
416    #[serde(rename = "48m")]
417    WindowSize48m,
418    #[serde(rename = "49m")]
419    WindowSize49m,
420    #[serde(rename = "50m")]
421    WindowSize50m,
422    #[serde(rename = "51m")]
423    WindowSize51m,
424    #[serde(rename = "52m")]
425    WindowSize52m,
426    #[serde(rename = "53m")]
427    WindowSize53m,
428    #[serde(rename = "54m")]
429    WindowSize54m,
430    #[serde(rename = "55m")]
431    WindowSize55m,
432    #[serde(rename = "56m")]
433    WindowSize56m,
434    #[serde(rename = "57m")]
435    WindowSize57m,
436    #[serde(rename = "58m")]
437    WindowSize58m,
438    #[serde(rename = "59m")]
439    WindowSize59m,
440    #[serde(rename = "1h")]
441    WindowSize1h,
442    #[serde(rename = "2h")]
443    WindowSize2h,
444    #[serde(rename = "3h")]
445    WindowSize3h,
446    #[serde(rename = "4h")]
447    WindowSize4h,
448    #[serde(rename = "5h")]
449    WindowSize5h,
450    #[serde(rename = "6h")]
451    WindowSize6h,
452    #[serde(rename = "7h")]
453    WindowSize7h,
454    #[serde(rename = "8h")]
455    WindowSize8h,
456    #[serde(rename = "9h")]
457    WindowSize9h,
458    #[serde(rename = "10h")]
459    WindowSize10h,
460    #[serde(rename = "11h")]
461    WindowSize11h,
462    #[serde(rename = "12h")]
463    WindowSize12h,
464    #[serde(rename = "13h")]
465    WindowSize13h,
466    #[serde(rename = "14h")]
467    WindowSize14h,
468    #[serde(rename = "15h")]
469    WindowSize15h,
470    #[serde(rename = "16h")]
471    WindowSize16h,
472    #[serde(rename = "17h")]
473    WindowSize17h,
474    #[serde(rename = "18h")]
475    WindowSize18h,
476    #[serde(rename = "19h")]
477    WindowSize19h,
478    #[serde(rename = "20h")]
479    WindowSize20h,
480    #[serde(rename = "21h")]
481    WindowSize21h,
482    #[serde(rename = "22h")]
483    WindowSize22h,
484    #[serde(rename = "23h")]
485    WindowSize23h,
486    #[serde(rename = "1d")]
487    WindowSize1d,
488    #[serde(rename = "2d")]
489    WindowSize2d,
490    #[serde(rename = "3d")]
491    WindowSize3d,
492    #[serde(rename = "4d")]
493    WindowSize4d,
494    #[serde(rename = "5d")]
495    WindowSize5d,
496    #[serde(rename = "6d")]
497    WindowSize6d,
498}
499
500impl TickerWindowSizeEnum {
501    #[must_use]
502    pub fn as_str(&self) -> &'static str {
503        match self {
504            Self::WindowSize1m => "1m",
505            Self::WindowSize2m => "2m",
506            Self::WindowSize3m => "3m",
507            Self::WindowSize4m => "4m",
508            Self::WindowSize5m => "5m",
509            Self::WindowSize6m => "6m",
510            Self::WindowSize7m => "7m",
511            Self::WindowSize8m => "8m",
512            Self::WindowSize9m => "9m",
513            Self::WindowSize10m => "10m",
514            Self::WindowSize11m => "11m",
515            Self::WindowSize12m => "12m",
516            Self::WindowSize13m => "13m",
517            Self::WindowSize14m => "14m",
518            Self::WindowSize15m => "15m",
519            Self::WindowSize16m => "16m",
520            Self::WindowSize17m => "17m",
521            Self::WindowSize18m => "18m",
522            Self::WindowSize19m => "19m",
523            Self::WindowSize20m => "20m",
524            Self::WindowSize21m => "21m",
525            Self::WindowSize22m => "22m",
526            Self::WindowSize23m => "23m",
527            Self::WindowSize24m => "24m",
528            Self::WindowSize25m => "25m",
529            Self::WindowSize26m => "26m",
530            Self::WindowSize27m => "27m",
531            Self::WindowSize28m => "28m",
532            Self::WindowSize29m => "29m",
533            Self::WindowSize30m => "30m",
534            Self::WindowSize31m => "31m",
535            Self::WindowSize32m => "32m",
536            Self::WindowSize33m => "33m",
537            Self::WindowSize34m => "34m",
538            Self::WindowSize35m => "35m",
539            Self::WindowSize36m => "36m",
540            Self::WindowSize37m => "37m",
541            Self::WindowSize38m => "38m",
542            Self::WindowSize39m => "39m",
543            Self::WindowSize40m => "40m",
544            Self::WindowSize41m => "41m",
545            Self::WindowSize42m => "42m",
546            Self::WindowSize43m => "43m",
547            Self::WindowSize44m => "44m",
548            Self::WindowSize45m => "45m",
549            Self::WindowSize46m => "46m",
550            Self::WindowSize47m => "47m",
551            Self::WindowSize48m => "48m",
552            Self::WindowSize49m => "49m",
553            Self::WindowSize50m => "50m",
554            Self::WindowSize51m => "51m",
555            Self::WindowSize52m => "52m",
556            Self::WindowSize53m => "53m",
557            Self::WindowSize54m => "54m",
558            Self::WindowSize55m => "55m",
559            Self::WindowSize56m => "56m",
560            Self::WindowSize57m => "57m",
561            Self::WindowSize58m => "58m",
562            Self::WindowSize59m => "59m",
563            Self::WindowSize1h => "1h",
564            Self::WindowSize2h => "2h",
565            Self::WindowSize3h => "3h",
566            Self::WindowSize4h => "4h",
567            Self::WindowSize5h => "5h",
568            Self::WindowSize6h => "6h",
569            Self::WindowSize7h => "7h",
570            Self::WindowSize8h => "8h",
571            Self::WindowSize9h => "9h",
572            Self::WindowSize10h => "10h",
573            Self::WindowSize11h => "11h",
574            Self::WindowSize12h => "12h",
575            Self::WindowSize13h => "13h",
576            Self::WindowSize14h => "14h",
577            Self::WindowSize15h => "15h",
578            Self::WindowSize16h => "16h",
579            Self::WindowSize17h => "17h",
580            Self::WindowSize18h => "18h",
581            Self::WindowSize19h => "19h",
582            Self::WindowSize20h => "20h",
583            Self::WindowSize21h => "21h",
584            Self::WindowSize22h => "22h",
585            Self::WindowSize23h => "23h",
586            Self::WindowSize1d => "1d",
587            Self::WindowSize2d => "2d",
588            Self::WindowSize3d => "3d",
589            Self::WindowSize4d => "4d",
590            Self::WindowSize5d => "5d",
591            Self::WindowSize6d => "6d",
592        }
593    }
594}
595
596impl std::str::FromStr for TickerWindowSizeEnum {
597    type Err = Box<dyn std::error::Error + Send + Sync>;
598
599    fn from_str(s: &str) -> Result<Self, Self::Err> {
600        match s {
601            "1m" => Ok(Self::WindowSize1m),
602            "2m" => Ok(Self::WindowSize2m),
603            "3m" => Ok(Self::WindowSize3m),
604            "4m" => Ok(Self::WindowSize4m),
605            "5m" => Ok(Self::WindowSize5m),
606            "6m" => Ok(Self::WindowSize6m),
607            "7m" => Ok(Self::WindowSize7m),
608            "8m" => Ok(Self::WindowSize8m),
609            "9m" => Ok(Self::WindowSize9m),
610            "10m" => Ok(Self::WindowSize10m),
611            "11m" => Ok(Self::WindowSize11m),
612            "12m" => Ok(Self::WindowSize12m),
613            "13m" => Ok(Self::WindowSize13m),
614            "14m" => Ok(Self::WindowSize14m),
615            "15m" => Ok(Self::WindowSize15m),
616            "16m" => Ok(Self::WindowSize16m),
617            "17m" => Ok(Self::WindowSize17m),
618            "18m" => Ok(Self::WindowSize18m),
619            "19m" => Ok(Self::WindowSize19m),
620            "20m" => Ok(Self::WindowSize20m),
621            "21m" => Ok(Self::WindowSize21m),
622            "22m" => Ok(Self::WindowSize22m),
623            "23m" => Ok(Self::WindowSize23m),
624            "24m" => Ok(Self::WindowSize24m),
625            "25m" => Ok(Self::WindowSize25m),
626            "26m" => Ok(Self::WindowSize26m),
627            "27m" => Ok(Self::WindowSize27m),
628            "28m" => Ok(Self::WindowSize28m),
629            "29m" => Ok(Self::WindowSize29m),
630            "30m" => Ok(Self::WindowSize30m),
631            "31m" => Ok(Self::WindowSize31m),
632            "32m" => Ok(Self::WindowSize32m),
633            "33m" => Ok(Self::WindowSize33m),
634            "34m" => Ok(Self::WindowSize34m),
635            "35m" => Ok(Self::WindowSize35m),
636            "36m" => Ok(Self::WindowSize36m),
637            "37m" => Ok(Self::WindowSize37m),
638            "38m" => Ok(Self::WindowSize38m),
639            "39m" => Ok(Self::WindowSize39m),
640            "40m" => Ok(Self::WindowSize40m),
641            "41m" => Ok(Self::WindowSize41m),
642            "42m" => Ok(Self::WindowSize42m),
643            "43m" => Ok(Self::WindowSize43m),
644            "44m" => Ok(Self::WindowSize44m),
645            "45m" => Ok(Self::WindowSize45m),
646            "46m" => Ok(Self::WindowSize46m),
647            "47m" => Ok(Self::WindowSize47m),
648            "48m" => Ok(Self::WindowSize48m),
649            "49m" => Ok(Self::WindowSize49m),
650            "50m" => Ok(Self::WindowSize50m),
651            "51m" => Ok(Self::WindowSize51m),
652            "52m" => Ok(Self::WindowSize52m),
653            "53m" => Ok(Self::WindowSize53m),
654            "54m" => Ok(Self::WindowSize54m),
655            "55m" => Ok(Self::WindowSize55m),
656            "56m" => Ok(Self::WindowSize56m),
657            "57m" => Ok(Self::WindowSize57m),
658            "58m" => Ok(Self::WindowSize58m),
659            "59m" => Ok(Self::WindowSize59m),
660            "1h" => Ok(Self::WindowSize1h),
661            "2h" => Ok(Self::WindowSize2h),
662            "3h" => Ok(Self::WindowSize3h),
663            "4h" => Ok(Self::WindowSize4h),
664            "5h" => Ok(Self::WindowSize5h),
665            "6h" => Ok(Self::WindowSize6h),
666            "7h" => Ok(Self::WindowSize7h),
667            "8h" => Ok(Self::WindowSize8h),
668            "9h" => Ok(Self::WindowSize9h),
669            "10h" => Ok(Self::WindowSize10h),
670            "11h" => Ok(Self::WindowSize11h),
671            "12h" => Ok(Self::WindowSize12h),
672            "13h" => Ok(Self::WindowSize13h),
673            "14h" => Ok(Self::WindowSize14h),
674            "15h" => Ok(Self::WindowSize15h),
675            "16h" => Ok(Self::WindowSize16h),
676            "17h" => Ok(Self::WindowSize17h),
677            "18h" => Ok(Self::WindowSize18h),
678            "19h" => Ok(Self::WindowSize19h),
679            "20h" => Ok(Self::WindowSize20h),
680            "21h" => Ok(Self::WindowSize21h),
681            "22h" => Ok(Self::WindowSize22h),
682            "23h" => Ok(Self::WindowSize23h),
683            "1d" => Ok(Self::WindowSize1d),
684            "2d" => Ok(Self::WindowSize2d),
685            "3d" => Ok(Self::WindowSize3d),
686            "4d" => Ok(Self::WindowSize4d),
687            "5d" => Ok(Self::WindowSize5d),
688            "6d" => Ok(Self::WindowSize6d),
689            other => Err(format!("invalid TickerWindowSizeEnum: {}", other).into()),
690        }
691    }
692}
693
694#[allow(non_camel_case_types)]
695#[derive(Debug, Clone, Serialize, Deserialize)]
696pub enum TickerSymbolStatusEnum {
697    #[serde(rename = "TRADING")]
698    Trading,
699    #[serde(rename = "END_OF_DAY")]
700    EndOfDay,
701    #[serde(rename = "HALT")]
702    Halt,
703    #[serde(rename = "BREAK")]
704    Break,
705    #[serde(rename = "NON_REPRESENTABLE")]
706    NonRepresentable,
707}
708
709impl TickerSymbolStatusEnum {
710    #[must_use]
711    pub fn as_str(&self) -> &'static str {
712        match self {
713            Self::Trading => "TRADING",
714            Self::EndOfDay => "END_OF_DAY",
715            Self::Halt => "HALT",
716            Self::Break => "BREAK",
717            Self::NonRepresentable => "NON_REPRESENTABLE",
718        }
719    }
720}
721
722impl std::str::FromStr for TickerSymbolStatusEnum {
723    type Err = Box<dyn std::error::Error + Send + Sync>;
724
725    fn from_str(s: &str) -> Result<Self, Self::Err> {
726        match s {
727            "TRADING" => Ok(Self::Trading),
728            "END_OF_DAY" => Ok(Self::EndOfDay),
729            "HALT" => Ok(Self::Halt),
730            "BREAK" => Ok(Self::Break),
731            "NON_REPRESENTABLE" => Ok(Self::NonRepresentable),
732            other => Err(format!("invalid TickerSymbolStatusEnum: {}", other).into()),
733        }
734    }
735}
736
737#[allow(non_camel_case_types)]
738#[derive(Debug, Clone, Serialize, Deserialize)]
739pub enum Ticker24hrTypeEnum {
740    #[serde(rename = "FULL")]
741    Full,
742    #[serde(rename = "MINI")]
743    Mini,
744}
745
746impl Ticker24hrTypeEnum {
747    #[must_use]
748    pub fn as_str(&self) -> &'static str {
749        match self {
750            Self::Full => "FULL",
751            Self::Mini => "MINI",
752        }
753    }
754}
755
756impl std::str::FromStr for Ticker24hrTypeEnum {
757    type Err = Box<dyn std::error::Error + Send + Sync>;
758
759    fn from_str(s: &str) -> Result<Self, Self::Err> {
760        match s {
761            "FULL" => Ok(Self::Full),
762            "MINI" => Ok(Self::Mini),
763            other => Err(format!("invalid Ticker24hrTypeEnum: {}", other).into()),
764        }
765    }
766}
767
768#[allow(non_camel_case_types)]
769#[derive(Debug, Clone, Serialize, Deserialize)]
770pub enum Ticker24hrSymbolStatusEnum {
771    #[serde(rename = "TRADING")]
772    Trading,
773    #[serde(rename = "END_OF_DAY")]
774    EndOfDay,
775    #[serde(rename = "HALT")]
776    Halt,
777    #[serde(rename = "BREAK")]
778    Break,
779    #[serde(rename = "NON_REPRESENTABLE")]
780    NonRepresentable,
781}
782
783impl Ticker24hrSymbolStatusEnum {
784    #[must_use]
785    pub fn as_str(&self) -> &'static str {
786        match self {
787            Self::Trading => "TRADING",
788            Self::EndOfDay => "END_OF_DAY",
789            Self::Halt => "HALT",
790            Self::Break => "BREAK",
791            Self::NonRepresentable => "NON_REPRESENTABLE",
792        }
793    }
794}
795
796impl std::str::FromStr for Ticker24hrSymbolStatusEnum {
797    type Err = Box<dyn std::error::Error + Send + Sync>;
798
799    fn from_str(s: &str) -> Result<Self, Self::Err> {
800        match s {
801            "TRADING" => Ok(Self::Trading),
802            "END_OF_DAY" => Ok(Self::EndOfDay),
803            "HALT" => Ok(Self::Halt),
804            "BREAK" => Ok(Self::Break),
805            "NON_REPRESENTABLE" => Ok(Self::NonRepresentable),
806            other => Err(format!("invalid Ticker24hrSymbolStatusEnum: {}", other).into()),
807        }
808    }
809}
810
811#[allow(non_camel_case_types)]
812#[derive(Debug, Clone, Serialize, Deserialize)]
813pub enum TickerBookSymbolStatusEnum {
814    #[serde(rename = "TRADING")]
815    Trading,
816    #[serde(rename = "END_OF_DAY")]
817    EndOfDay,
818    #[serde(rename = "HALT")]
819    Halt,
820    #[serde(rename = "BREAK")]
821    Break,
822    #[serde(rename = "NON_REPRESENTABLE")]
823    NonRepresentable,
824}
825
826impl TickerBookSymbolStatusEnum {
827    #[must_use]
828    pub fn as_str(&self) -> &'static str {
829        match self {
830            Self::Trading => "TRADING",
831            Self::EndOfDay => "END_OF_DAY",
832            Self::Halt => "HALT",
833            Self::Break => "BREAK",
834            Self::NonRepresentable => "NON_REPRESENTABLE",
835        }
836    }
837}
838
839impl std::str::FromStr for TickerBookSymbolStatusEnum {
840    type Err = Box<dyn std::error::Error + Send + Sync>;
841
842    fn from_str(s: &str) -> Result<Self, Self::Err> {
843        match s {
844            "TRADING" => Ok(Self::Trading),
845            "END_OF_DAY" => Ok(Self::EndOfDay),
846            "HALT" => Ok(Self::Halt),
847            "BREAK" => Ok(Self::Break),
848            "NON_REPRESENTABLE" => Ok(Self::NonRepresentable),
849            other => Err(format!("invalid TickerBookSymbolStatusEnum: {}", other).into()),
850        }
851    }
852}
853
854#[allow(non_camel_case_types)]
855#[derive(Debug, Clone, Serialize, Deserialize)]
856pub enum TickerPriceSymbolStatusEnum {
857    #[serde(rename = "TRADING")]
858    Trading,
859    #[serde(rename = "END_OF_DAY")]
860    EndOfDay,
861    #[serde(rename = "HALT")]
862    Halt,
863    #[serde(rename = "BREAK")]
864    Break,
865    #[serde(rename = "NON_REPRESENTABLE")]
866    NonRepresentable,
867}
868
869impl TickerPriceSymbolStatusEnum {
870    #[must_use]
871    pub fn as_str(&self) -> &'static str {
872        match self {
873            Self::Trading => "TRADING",
874            Self::EndOfDay => "END_OF_DAY",
875            Self::Halt => "HALT",
876            Self::Break => "BREAK",
877            Self::NonRepresentable => "NON_REPRESENTABLE",
878        }
879    }
880}
881
882impl std::str::FromStr for TickerPriceSymbolStatusEnum {
883    type Err = Box<dyn std::error::Error + Send + Sync>;
884
885    fn from_str(s: &str) -> Result<Self, Self::Err> {
886        match s {
887            "TRADING" => Ok(Self::Trading),
888            "END_OF_DAY" => Ok(Self::EndOfDay),
889            "HALT" => Ok(Self::Halt),
890            "BREAK" => Ok(Self::Break),
891            "NON_REPRESENTABLE" => Ok(Self::NonRepresentable),
892            other => Err(format!("invalid TickerPriceSymbolStatusEnum: {}", other).into()),
893        }
894    }
895}
896
897#[allow(non_camel_case_types)]
898#[derive(Debug, Clone, Serialize, Deserialize)]
899pub enum TickerTradingDayTypeEnum {
900    #[serde(rename = "FULL")]
901    Full,
902    #[serde(rename = "MINI")]
903    Mini,
904}
905
906impl TickerTradingDayTypeEnum {
907    #[must_use]
908    pub fn as_str(&self) -> &'static str {
909        match self {
910            Self::Full => "FULL",
911            Self::Mini => "MINI",
912        }
913    }
914}
915
916impl std::str::FromStr for TickerTradingDayTypeEnum {
917    type Err = Box<dyn std::error::Error + Send + Sync>;
918
919    fn from_str(s: &str) -> Result<Self, Self::Err> {
920        match s {
921            "FULL" => Ok(Self::Full),
922            "MINI" => Ok(Self::Mini),
923            other => Err(format!("invalid TickerTradingDayTypeEnum: {}", other).into()),
924        }
925    }
926}
927
928#[allow(non_camel_case_types)]
929#[derive(Debug, Clone, Serialize, Deserialize)]
930pub enum TickerTradingDaySymbolStatusEnum {
931    #[serde(rename = "TRADING")]
932    Trading,
933    #[serde(rename = "END_OF_DAY")]
934    EndOfDay,
935    #[serde(rename = "HALT")]
936    Halt,
937    #[serde(rename = "BREAK")]
938    Break,
939    #[serde(rename = "NON_REPRESENTABLE")]
940    NonRepresentable,
941}
942
943impl TickerTradingDaySymbolStatusEnum {
944    #[must_use]
945    pub fn as_str(&self) -> &'static str {
946        match self {
947            Self::Trading => "TRADING",
948            Self::EndOfDay => "END_OF_DAY",
949            Self::Halt => "HALT",
950            Self::Break => "BREAK",
951            Self::NonRepresentable => "NON_REPRESENTABLE",
952        }
953    }
954}
955
956impl std::str::FromStr for TickerTradingDaySymbolStatusEnum {
957    type Err = Box<dyn std::error::Error + Send + Sync>;
958
959    fn from_str(s: &str) -> Result<Self, Self::Err> {
960        match s {
961            "TRADING" => Ok(Self::Trading),
962            "END_OF_DAY" => Ok(Self::EndOfDay),
963            "HALT" => Ok(Self::Halt),
964            "BREAK" => Ok(Self::Break),
965            "NON_REPRESENTABLE" => Ok(Self::NonRepresentable),
966            other => Err(format!("invalid TickerTradingDaySymbolStatusEnum: {}", other).into()),
967        }
968    }
969}
970
971#[allow(non_camel_case_types)]
972#[derive(Debug, Clone, Serialize, Deserialize)]
973pub enum UiKlinesIntervalEnum {
974    #[serde(rename = "1s")]
975    Interval1s,
976    #[serde(rename = "1m")]
977    Interval1m,
978    #[serde(rename = "3m")]
979    Interval3m,
980    #[serde(rename = "5m")]
981    Interval5m,
982    #[serde(rename = "15m")]
983    Interval15m,
984    #[serde(rename = "30m")]
985    Interval30m,
986    #[serde(rename = "1h")]
987    Interval1h,
988    #[serde(rename = "2h")]
989    Interval2h,
990    #[serde(rename = "4h")]
991    Interval4h,
992    #[serde(rename = "6h")]
993    Interval6h,
994    #[serde(rename = "8h")]
995    Interval8h,
996    #[serde(rename = "12h")]
997    Interval12h,
998    #[serde(rename = "1d")]
999    Interval1d,
1000    #[serde(rename = "3d")]
1001    Interval3d,
1002    #[serde(rename = "1w")]
1003    Interval1w,
1004    #[serde(rename = "1M")]
1005    Interval1M,
1006}
1007
1008impl UiKlinesIntervalEnum {
1009    #[must_use]
1010    pub fn as_str(&self) -> &'static str {
1011        match self {
1012            Self::Interval1s => "1s",
1013            Self::Interval1m => "1m",
1014            Self::Interval3m => "3m",
1015            Self::Interval5m => "5m",
1016            Self::Interval15m => "15m",
1017            Self::Interval30m => "30m",
1018            Self::Interval1h => "1h",
1019            Self::Interval2h => "2h",
1020            Self::Interval4h => "4h",
1021            Self::Interval6h => "6h",
1022            Self::Interval8h => "8h",
1023            Self::Interval12h => "12h",
1024            Self::Interval1d => "1d",
1025            Self::Interval3d => "3d",
1026            Self::Interval1w => "1w",
1027            Self::Interval1M => "1M",
1028        }
1029    }
1030}
1031
1032impl std::str::FromStr for UiKlinesIntervalEnum {
1033    type Err = Box<dyn std::error::Error + Send + Sync>;
1034
1035    fn from_str(s: &str) -> Result<Self, Self::Err> {
1036        match s {
1037            "1s" => Ok(Self::Interval1s),
1038            "1m" => Ok(Self::Interval1m),
1039            "3m" => Ok(Self::Interval3m),
1040            "5m" => Ok(Self::Interval5m),
1041            "15m" => Ok(Self::Interval15m),
1042            "30m" => Ok(Self::Interval30m),
1043            "1h" => Ok(Self::Interval1h),
1044            "2h" => Ok(Self::Interval2h),
1045            "4h" => Ok(Self::Interval4h),
1046            "6h" => Ok(Self::Interval6h),
1047            "8h" => Ok(Self::Interval8h),
1048            "12h" => Ok(Self::Interval12h),
1049            "1d" => Ok(Self::Interval1d),
1050            "3d" => Ok(Self::Interval3d),
1051            "1w" => Ok(Self::Interval1w),
1052            "1M" => Ok(Self::Interval1M),
1053            other => Err(format!("invalid UiKlinesIntervalEnum: {}", other).into()),
1054        }
1055    }
1056}
1057
1058/// Request parameters for the [`avg_price`] operation.
1059///
1060/// This struct holds all of the inputs you can pass when calling
1061/// [`avg_price`](#method.avg_price).
1062#[derive(Clone, Debug, Builder)]
1063#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
1064pub struct AvgPriceParams {
1065    ///
1066    /// The `symbol` parameter.
1067    ///
1068    /// This field is **required.
1069    #[builder(setter(into))]
1070    pub symbol: String,
1071    /// Unique WebSocket request ID.
1072    ///
1073    /// This field is **optional.
1074    #[builder(setter(into), default)]
1075    pub id: Option<String>,
1076}
1077
1078impl AvgPriceParams {
1079    /// Create a builder for [`avg_price`].
1080    ///
1081    /// Required parameters:
1082    ///
1083    /// * `symbol` — String
1084    ///
1085    #[must_use]
1086    pub fn builder(symbol: String) -> AvgPriceParamsBuilder {
1087        AvgPriceParamsBuilder::default().symbol(symbol)
1088    }
1089}
1090/// Request parameters for the [`block_trades_historical`] operation.
1091///
1092/// This struct holds all of the inputs you can pass when calling
1093/// [`block_trades_historical`](#method.block_trades_historical).
1094#[derive(Clone, Debug, Builder)]
1095#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
1096pub struct BlockTradesHistoricalParams {
1097    ///
1098    /// The `symbol` parameter.
1099    ///
1100    /// This field is **required.
1101    #[builder(setter(into))]
1102    pub symbol: String,
1103    /// Block trade ID to fetch from
1104    ///
1105    /// This field is **required.
1106    #[builder(setter(into))]
1107    pub from_id: i64,
1108    /// Unique WebSocket request ID.
1109    ///
1110    /// This field is **optional.
1111    #[builder(setter(into), default)]
1112    pub id: Option<String>,
1113    /// Default: 500; Maximum: 1000
1114    ///
1115    /// This field is **optional.
1116    #[builder(setter(into), default)]
1117    pub limit: Option<i64>,
1118}
1119
1120impl BlockTradesHistoricalParams {
1121    /// Create a builder for [`block_trades_historical`].
1122    ///
1123    /// Required parameters:
1124    ///
1125    /// * `symbol` — String
1126    /// * `from_id` — Block trade ID to fetch from
1127    ///
1128    #[must_use]
1129    pub fn builder(symbol: String, from_id: i64) -> BlockTradesHistoricalParamsBuilder {
1130        BlockTradesHistoricalParamsBuilder::default()
1131            .symbol(symbol)
1132            .from_id(from_id)
1133    }
1134}
1135/// Request parameters for the [`depth`] operation.
1136///
1137/// This struct holds all of the inputs you can pass when calling
1138/// [`depth`](#method.depth).
1139#[derive(Clone, Debug, Builder)]
1140#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
1141pub struct DepthParams {
1142    ///
1143    /// The `symbol` parameter.
1144    ///
1145    /// This field is **required.
1146    #[builder(setter(into))]
1147    pub symbol: String,
1148    /// Unique WebSocket request ID.
1149    ///
1150    /// This field is **optional.
1151    #[builder(setter(into), default)]
1152    pub id: Option<String>,
1153    /// Default: 100; Maximum: 5000
1154    ///
1155    /// This field is **optional.
1156    #[builder(setter(into), default)]
1157    pub limit: Option<i32>,
1158    ///
1159    /// The `symbol_status` parameter.
1160    ///
1161    /// This field is **optional.
1162    #[builder(setter(into), default)]
1163    pub symbol_status: Option<DepthSymbolStatusEnum>,
1164}
1165
1166impl DepthParams {
1167    /// Create a builder for [`depth`].
1168    ///
1169    /// Required parameters:
1170    ///
1171    /// * `symbol` — String
1172    ///
1173    #[must_use]
1174    pub fn builder(symbol: String) -> DepthParamsBuilder {
1175        DepthParamsBuilder::default().symbol(symbol)
1176    }
1177}
1178/// Request parameters for the [`klines`] operation.
1179///
1180/// This struct holds all of the inputs you can pass when calling
1181/// [`klines`](#method.klines).
1182#[derive(Clone, Debug, Builder)]
1183#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
1184pub struct KlinesParams {
1185    ///
1186    /// The `symbol` parameter.
1187    ///
1188    /// This field is **required.
1189    #[builder(setter(into))]
1190    pub symbol: String,
1191    ///
1192    /// The `interval` parameter.
1193    ///
1194    /// This field is **required.
1195    #[builder(setter(into))]
1196    pub interval: KlinesIntervalEnum,
1197    /// Unique WebSocket request ID.
1198    ///
1199    /// This field is **optional.
1200    #[builder(setter(into), default)]
1201    pub id: Option<String>,
1202    ///
1203    /// The `start_time` parameter.
1204    ///
1205    /// This field is **optional.
1206    #[builder(setter(into), default)]
1207    pub start_time: Option<i64>,
1208    ///
1209    /// The `end_time` parameter.
1210    ///
1211    /// This field is **optional.
1212    #[builder(setter(into), default)]
1213    pub end_time: Option<i64>,
1214    /// Default: 0 (UTC)
1215    ///
1216    /// This field is **optional.
1217    #[builder(setter(into), default)]
1218    pub time_zone: Option<String>,
1219    /// Default: 100; Maximum: 5000
1220    ///
1221    /// This field is **optional.
1222    #[builder(setter(into), default)]
1223    pub limit: Option<i32>,
1224}
1225
1226impl KlinesParams {
1227    /// Create a builder for [`klines`].
1228    ///
1229    /// Required parameters:
1230    ///
1231    /// * `symbol` — String
1232    /// * `interval` — String
1233    ///
1234    #[must_use]
1235    pub fn builder(symbol: String, interval: KlinesIntervalEnum) -> KlinesParamsBuilder {
1236        KlinesParamsBuilder::default()
1237            .symbol(symbol)
1238            .interval(interval)
1239    }
1240}
1241/// Request parameters for the [`reference_price`] operation.
1242///
1243/// This struct holds all of the inputs you can pass when calling
1244/// [`reference_price`](#method.reference_price).
1245#[derive(Clone, Debug, Builder)]
1246#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
1247pub struct ReferencePriceParams {
1248    ///
1249    /// The `symbol` parameter.
1250    ///
1251    /// This field is **required.
1252    #[builder(setter(into))]
1253    pub symbol: String,
1254    /// Unique WebSocket request ID.
1255    ///
1256    /// This field is **optional.
1257    #[builder(setter(into), default)]
1258    pub id: Option<String>,
1259}
1260
1261impl ReferencePriceParams {
1262    /// Create a builder for [`reference_price`].
1263    ///
1264    /// Required parameters:
1265    ///
1266    /// * `symbol` — String
1267    ///
1268    #[must_use]
1269    pub fn builder(symbol: String) -> ReferencePriceParamsBuilder {
1270        ReferencePriceParamsBuilder::default().symbol(symbol)
1271    }
1272}
1273/// Request parameters for the [`reference_price_calculation`] operation.
1274///
1275/// This struct holds all of the inputs you can pass when calling
1276/// [`reference_price_calculation`](#method.reference_price_calculation).
1277#[derive(Clone, Debug, Builder)]
1278#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
1279pub struct ReferencePriceCalculationParams {
1280    ///
1281    /// The `symbol` parameter.
1282    ///
1283    /// This field is **required.
1284    #[builder(setter(into))]
1285    pub symbol: String,
1286    /// Unique WebSocket request ID.
1287    ///
1288    /// This field is **optional.
1289    #[builder(setter(into), default)]
1290    pub id: Option<String>,
1291    ///
1292    /// The `symbol_status` parameter.
1293    ///
1294    /// This field is **optional.
1295    #[builder(setter(into), default)]
1296    pub symbol_status: Option<ReferencePriceCalculationSymbolStatusEnum>,
1297}
1298
1299impl ReferencePriceCalculationParams {
1300    /// Create a builder for [`reference_price_calculation`].
1301    ///
1302    /// Required parameters:
1303    ///
1304    /// * `symbol` — String
1305    ///
1306    #[must_use]
1307    pub fn builder(symbol: String) -> ReferencePriceCalculationParamsBuilder {
1308        ReferencePriceCalculationParamsBuilder::default().symbol(symbol)
1309    }
1310}
1311/// Request parameters for the [`ticker`] operation.
1312///
1313/// This struct holds all of the inputs you can pass when calling
1314/// [`ticker`](#method.ticker).
1315#[derive(Clone, Debug, Builder, Default)]
1316#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
1317pub struct TickerParams {
1318    /// Unique WebSocket request ID.
1319    ///
1320    /// This field is **optional.
1321    #[builder(setter(into), default)]
1322    pub id: Option<String>,
1323    /// Describe a single symbol
1324    ///
1325    /// This field is **optional.
1326    #[builder(setter(into), default)]
1327    pub symbol: Option<String>,
1328    /// List of symbols to query
1329    ///
1330    /// This field is **optional.
1331    #[builder(setter(into), default)]
1332    pub symbols: Option<Vec<String>>,
1333    ///
1334    /// The `r#type` parameter.
1335    ///
1336    /// This field is **optional.
1337    #[builder(setter(into), default)]
1338    pub r#type: Option<TickerTypeEnum>,
1339    ///
1340    /// The `window_size` parameter.
1341    ///
1342    /// This field is **optional.
1343    #[builder(setter(into), default)]
1344    pub window_size: Option<TickerWindowSizeEnum>,
1345    ///
1346    /// The `symbol_status` parameter.
1347    ///
1348    /// This field is **optional.
1349    #[builder(setter(into), default)]
1350    pub symbol_status: Option<TickerSymbolStatusEnum>,
1351}
1352
1353impl TickerParams {
1354    /// Create a builder for [`ticker`].
1355    ///
1356    #[must_use]
1357    pub fn builder() -> TickerParamsBuilder {
1358        TickerParamsBuilder::default()
1359    }
1360}
1361/// Request parameters for the [`ticker24hr`] operation.
1362///
1363/// This struct holds all of the inputs you can pass when calling
1364/// [`ticker24hr`](#method.ticker24hr).
1365#[derive(Clone, Debug, Builder, Default)]
1366#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
1367pub struct Ticker24hrParams {
1368    /// Unique WebSocket request ID.
1369    ///
1370    /// This field is **optional.
1371    #[builder(setter(into), default)]
1372    pub id: Option<String>,
1373    /// Describe a single symbol
1374    ///
1375    /// This field is **optional.
1376    #[builder(setter(into), default)]
1377    pub symbol: Option<String>,
1378    /// List of symbols to query
1379    ///
1380    /// This field is **optional.
1381    #[builder(setter(into), default)]
1382    pub symbols: Option<Vec<String>>,
1383    ///
1384    /// The `r#type` parameter.
1385    ///
1386    /// This field is **optional.
1387    #[builder(setter(into), default)]
1388    pub r#type: Option<Ticker24hrTypeEnum>,
1389    ///
1390    /// The `symbol_status` parameter.
1391    ///
1392    /// This field is **optional.
1393    #[builder(setter(into), default)]
1394    pub symbol_status: Option<Ticker24hrSymbolStatusEnum>,
1395}
1396
1397impl Ticker24hrParams {
1398    /// Create a builder for [`ticker24hr`].
1399    ///
1400    #[must_use]
1401    pub fn builder() -> Ticker24hrParamsBuilder {
1402        Ticker24hrParamsBuilder::default()
1403    }
1404}
1405/// Request parameters for the [`ticker_book`] operation.
1406///
1407/// This struct holds all of the inputs you can pass when calling
1408/// [`ticker_book`](#method.ticker_book).
1409#[derive(Clone, Debug, Builder, Default)]
1410#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
1411pub struct TickerBookParams {
1412    /// Unique WebSocket request ID.
1413    ///
1414    /// This field is **optional.
1415    #[builder(setter(into), default)]
1416    pub id: Option<String>,
1417    /// Describe a single symbol
1418    ///
1419    /// This field is **optional.
1420    #[builder(setter(into), default)]
1421    pub symbol: Option<String>,
1422    /// List of symbols to query
1423    ///
1424    /// This field is **optional.
1425    #[builder(setter(into), default)]
1426    pub symbols: Option<Vec<String>>,
1427    ///
1428    /// The `symbol_status` parameter.
1429    ///
1430    /// This field is **optional.
1431    #[builder(setter(into), default)]
1432    pub symbol_status: Option<TickerBookSymbolStatusEnum>,
1433}
1434
1435impl TickerBookParams {
1436    /// Create a builder for [`ticker_book`].
1437    ///
1438    #[must_use]
1439    pub fn builder() -> TickerBookParamsBuilder {
1440        TickerBookParamsBuilder::default()
1441    }
1442}
1443/// Request parameters for the [`ticker_price`] operation.
1444///
1445/// This struct holds all of the inputs you can pass when calling
1446/// [`ticker_price`](#method.ticker_price).
1447#[derive(Clone, Debug, Builder, Default)]
1448#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
1449pub struct TickerPriceParams {
1450    /// Unique WebSocket request ID.
1451    ///
1452    /// This field is **optional.
1453    #[builder(setter(into), default)]
1454    pub id: Option<String>,
1455    /// Describe a single symbol
1456    ///
1457    /// This field is **optional.
1458    #[builder(setter(into), default)]
1459    pub symbol: Option<String>,
1460    /// List of symbols to query
1461    ///
1462    /// This field is **optional.
1463    #[builder(setter(into), default)]
1464    pub symbols: Option<Vec<String>>,
1465    ///
1466    /// The `symbol_status` parameter.
1467    ///
1468    /// This field is **optional.
1469    #[builder(setter(into), default)]
1470    pub symbol_status: Option<TickerPriceSymbolStatusEnum>,
1471}
1472
1473impl TickerPriceParams {
1474    /// Create a builder for [`ticker_price`].
1475    ///
1476    #[must_use]
1477    pub fn builder() -> TickerPriceParamsBuilder {
1478        TickerPriceParamsBuilder::default()
1479    }
1480}
1481/// Request parameters for the [`ticker_trading_day`] operation.
1482///
1483/// This struct holds all of the inputs you can pass when calling
1484/// [`ticker_trading_day`](#method.ticker_trading_day).
1485#[derive(Clone, Debug, Builder, Default)]
1486#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
1487pub struct TickerTradingDayParams {
1488    /// Unique WebSocket request ID.
1489    ///
1490    /// This field is **optional.
1491    #[builder(setter(into), default)]
1492    pub id: Option<String>,
1493    /// Describe a single symbol
1494    ///
1495    /// This field is **optional.
1496    #[builder(setter(into), default)]
1497    pub symbol: Option<String>,
1498    /// List of symbols to query
1499    ///
1500    /// This field is **optional.
1501    #[builder(setter(into), default)]
1502    pub symbols: Option<Vec<String>>,
1503    /// Default: 0 (UTC)
1504    ///
1505    /// This field is **optional.
1506    #[builder(setter(into), default)]
1507    pub time_zone: Option<String>,
1508    ///
1509    /// The `r#type` parameter.
1510    ///
1511    /// This field is **optional.
1512    #[builder(setter(into), default)]
1513    pub r#type: Option<TickerTradingDayTypeEnum>,
1514    ///
1515    /// The `symbol_status` parameter.
1516    ///
1517    /// This field is **optional.
1518    #[builder(setter(into), default)]
1519    pub symbol_status: Option<TickerTradingDaySymbolStatusEnum>,
1520}
1521
1522impl TickerTradingDayParams {
1523    /// Create a builder for [`ticker_trading_day`].
1524    ///
1525    #[must_use]
1526    pub fn builder() -> TickerTradingDayParamsBuilder {
1527        TickerTradingDayParamsBuilder::default()
1528    }
1529}
1530/// Request parameters for the [`trades_aggregate`] operation.
1531///
1532/// This struct holds all of the inputs you can pass when calling
1533/// [`trades_aggregate`](#method.trades_aggregate).
1534#[derive(Clone, Debug, Builder)]
1535#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
1536pub struct TradesAggregateParams {
1537    ///
1538    /// The `symbol` parameter.
1539    ///
1540    /// This field is **required.
1541    #[builder(setter(into))]
1542    pub symbol: String,
1543    /// Unique WebSocket request ID.
1544    ///
1545    /// This field is **optional.
1546    #[builder(setter(into), default)]
1547    pub id: Option<String>,
1548    /// Aggregate trade ID to begin at
1549    ///
1550    /// This field is **optional.
1551    #[builder(setter(into), default)]
1552    pub from_id: Option<i64>,
1553    ///
1554    /// The `start_time` parameter.
1555    ///
1556    /// This field is **optional.
1557    #[builder(setter(into), default)]
1558    pub start_time: Option<i64>,
1559    ///
1560    /// The `end_time` parameter.
1561    ///
1562    /// This field is **optional.
1563    #[builder(setter(into), default)]
1564    pub end_time: Option<i64>,
1565    /// Default: 500; Maximum: 1000
1566    ///
1567    /// This field is **optional.
1568    #[builder(setter(into), default)]
1569    pub limit: Option<i64>,
1570}
1571
1572impl TradesAggregateParams {
1573    /// Create a builder for [`trades_aggregate`].
1574    ///
1575    /// Required parameters:
1576    ///
1577    /// * `symbol` — String
1578    ///
1579    #[must_use]
1580    pub fn builder(symbol: String) -> TradesAggregateParamsBuilder {
1581        TradesAggregateParamsBuilder::default().symbol(symbol)
1582    }
1583}
1584/// Request parameters for the [`trades_historical`] operation.
1585///
1586/// This struct holds all of the inputs you can pass when calling
1587/// [`trades_historical`](#method.trades_historical).
1588#[derive(Clone, Debug, Builder)]
1589#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
1590pub struct TradesHistoricalParams {
1591    ///
1592    /// The `symbol` parameter.
1593    ///
1594    /// This field is **required.
1595    #[builder(setter(into))]
1596    pub symbol: String,
1597    /// Unique WebSocket request ID.
1598    ///
1599    /// This field is **optional.
1600    #[builder(setter(into), default)]
1601    pub id: Option<String>,
1602    /// Trade ID to begin at
1603    ///
1604    /// This field is **optional.
1605    #[builder(setter(into), default)]
1606    pub from_id: Option<i32>,
1607    /// Default: 100; Maximum: 5000
1608    ///
1609    /// This field is **optional.
1610    #[builder(setter(into), default)]
1611    pub limit: Option<i32>,
1612}
1613
1614impl TradesHistoricalParams {
1615    /// Create a builder for [`trades_historical`].
1616    ///
1617    /// Required parameters:
1618    ///
1619    /// * `symbol` — String
1620    ///
1621    #[must_use]
1622    pub fn builder(symbol: String) -> TradesHistoricalParamsBuilder {
1623        TradesHistoricalParamsBuilder::default().symbol(symbol)
1624    }
1625}
1626/// Request parameters for the [`trades_recent`] operation.
1627///
1628/// This struct holds all of the inputs you can pass when calling
1629/// [`trades_recent`](#method.trades_recent).
1630#[derive(Clone, Debug, Builder)]
1631#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
1632pub struct TradesRecentParams {
1633    ///
1634    /// The `symbol` parameter.
1635    ///
1636    /// This field is **required.
1637    #[builder(setter(into))]
1638    pub symbol: String,
1639    /// Unique WebSocket request ID.
1640    ///
1641    /// This field is **optional.
1642    #[builder(setter(into), default)]
1643    pub id: Option<String>,
1644    /// Default: 100; Maximum: 5000
1645    ///
1646    /// This field is **optional.
1647    #[builder(setter(into), default)]
1648    pub limit: Option<i32>,
1649}
1650
1651impl TradesRecentParams {
1652    /// Create a builder for [`trades_recent`].
1653    ///
1654    /// Required parameters:
1655    ///
1656    /// * `symbol` — String
1657    ///
1658    #[must_use]
1659    pub fn builder(symbol: String) -> TradesRecentParamsBuilder {
1660        TradesRecentParamsBuilder::default().symbol(symbol)
1661    }
1662}
1663/// Request parameters for the [`ui_klines`] operation.
1664///
1665/// This struct holds all of the inputs you can pass when calling
1666/// [`ui_klines`](#method.ui_klines).
1667#[derive(Clone, Debug, Builder)]
1668#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
1669pub struct UiKlinesParams {
1670    ///
1671    /// The `symbol` parameter.
1672    ///
1673    /// This field is **required.
1674    #[builder(setter(into))]
1675    pub symbol: String,
1676    ///
1677    /// The `interval` parameter.
1678    ///
1679    /// This field is **required.
1680    #[builder(setter(into))]
1681    pub interval: UiKlinesIntervalEnum,
1682    /// Unique WebSocket request ID.
1683    ///
1684    /// This field is **optional.
1685    #[builder(setter(into), default)]
1686    pub id: Option<String>,
1687    ///
1688    /// The `start_time` parameter.
1689    ///
1690    /// This field is **optional.
1691    #[builder(setter(into), default)]
1692    pub start_time: Option<i64>,
1693    ///
1694    /// The `end_time` parameter.
1695    ///
1696    /// This field is **optional.
1697    #[builder(setter(into), default)]
1698    pub end_time: Option<i64>,
1699    /// Default: 0 (UTC)
1700    ///
1701    /// This field is **optional.
1702    #[builder(setter(into), default)]
1703    pub time_zone: Option<String>,
1704    /// Default: 100; Maximum: 5000
1705    ///
1706    /// This field is **optional.
1707    #[builder(setter(into), default)]
1708    pub limit: Option<i32>,
1709}
1710
1711impl UiKlinesParams {
1712    /// Create a builder for [`ui_klines`].
1713    ///
1714    /// Required parameters:
1715    ///
1716    /// * `symbol` — String
1717    /// * `interval` — String
1718    ///
1719    #[must_use]
1720    pub fn builder(symbol: String, interval: UiKlinesIntervalEnum) -> UiKlinesParamsBuilder {
1721        UiKlinesParamsBuilder::default()
1722            .symbol(symbol)
1723            .interval(interval)
1724    }
1725}
1726
1727#[async_trait]
1728impl MarketApi for MarketApiClient {
1729    async fn avg_price(
1730        &self,
1731        params: AvgPriceParams,
1732    ) -> anyhow::Result<WebsocketApiResponse<Box<models::AvgPriceResponseResult>>> {
1733        let AvgPriceParams { symbol, id } = params;
1734
1735        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
1736        payload.insert("symbol".to_string(), serde_json::json!(symbol));
1737        if let Some(value) = id {
1738            payload.insert("id".to_string(), serde_json::json!(value));
1739        }
1740        let payload = remove_empty_value(payload);
1741
1742        self.websocket_api_base
1743            .send_message::<Box<models::AvgPriceResponseResult>>(
1744                "/avgPrice".trim_start_matches('/'),
1745                payload,
1746                WebsocketMessageSendOptions::new(),
1747            )
1748            .await
1749            .map_err(anyhow::Error::from)?
1750            .into_iter()
1751            .next()
1752            .ok_or(WebsocketError::NoResponse)
1753            .map_err(anyhow::Error::from)
1754    }
1755
1756    async fn block_trades_historical(
1757        &self,
1758        params: BlockTradesHistoricalParams,
1759    ) -> anyhow::Result<WebsocketApiResponse<Vec<models::BlockTradesHistoricalResponseResultInner>>>
1760    {
1761        let BlockTradesHistoricalParams {
1762            symbol,
1763            from_id,
1764            id,
1765            limit,
1766        } = params;
1767
1768        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
1769        payload.insert("symbol".to_string(), serde_json::json!(symbol));
1770        payload.insert("fromId".to_string(), serde_json::json!(from_id));
1771        if let Some(value) = id {
1772            payload.insert("id".to_string(), serde_json::json!(value));
1773        }
1774        if let Some(value) = limit {
1775            payload.insert("limit".to_string(), serde_json::json!(value));
1776        }
1777        let payload = remove_empty_value(payload);
1778
1779        self.websocket_api_base
1780            .send_message::<Vec<models::BlockTradesHistoricalResponseResultInner>>(
1781                "/blockTrades.historical".trim_start_matches('/'),
1782                payload,
1783                WebsocketMessageSendOptions::new(),
1784            )
1785            .await
1786            .map_err(anyhow::Error::from)?
1787            .into_iter()
1788            .next()
1789            .ok_or(WebsocketError::NoResponse)
1790            .map_err(anyhow::Error::from)
1791    }
1792
1793    async fn depth(
1794        &self,
1795        params: DepthParams,
1796    ) -> anyhow::Result<WebsocketApiResponse<Box<models::DepthResponseResult>>> {
1797        let DepthParams {
1798            symbol,
1799            id,
1800            limit,
1801            symbol_status,
1802        } = params;
1803
1804        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
1805        payload.insert("symbol".to_string(), serde_json::json!(symbol));
1806        if let Some(value) = id {
1807            payload.insert("id".to_string(), serde_json::json!(value));
1808        }
1809        if let Some(value) = limit {
1810            payload.insert("limit".to_string(), serde_json::json!(value));
1811        }
1812        if let Some(value) = symbol_status {
1813            payload.insert("symbolStatus".to_string(), serde_json::json!(value));
1814        }
1815        let payload = remove_empty_value(payload);
1816
1817        self.websocket_api_base
1818            .send_message::<Box<models::DepthResponseResult>>(
1819                "/depth".trim_start_matches('/'),
1820                payload,
1821                WebsocketMessageSendOptions::new(),
1822            )
1823            .await
1824            .map_err(anyhow::Error::from)?
1825            .into_iter()
1826            .next()
1827            .ok_or(WebsocketError::NoResponse)
1828            .map_err(anyhow::Error::from)
1829    }
1830
1831    async fn klines(
1832        &self,
1833        params: KlinesParams,
1834    ) -> anyhow::Result<WebsocketApiResponse<Vec<Vec<models::KlinesItemInner>>>> {
1835        let KlinesParams {
1836            symbol,
1837            interval,
1838            id,
1839            start_time,
1840            end_time,
1841            time_zone,
1842            limit,
1843        } = params;
1844
1845        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
1846        payload.insert("symbol".to_string(), serde_json::json!(symbol));
1847        payload.insert("interval".to_string(), serde_json::json!(interval));
1848        if let Some(value) = id {
1849            payload.insert("id".to_string(), serde_json::json!(value));
1850        }
1851        if let Some(value) = start_time {
1852            payload.insert("startTime".to_string(), serde_json::json!(value));
1853        }
1854        if let Some(value) = end_time {
1855            payload.insert("endTime".to_string(), serde_json::json!(value));
1856        }
1857        if let Some(value) = time_zone {
1858            payload.insert("timeZone".to_string(), serde_json::json!(value));
1859        }
1860        if let Some(value) = limit {
1861            payload.insert("limit".to_string(), serde_json::json!(value));
1862        }
1863        let payload = remove_empty_value(payload);
1864
1865        self.websocket_api_base
1866            .send_message::<Vec<Vec<models::KlinesItemInner>>>(
1867                "/klines".trim_start_matches('/'),
1868                payload,
1869                WebsocketMessageSendOptions::new(),
1870            )
1871            .await
1872            .map_err(anyhow::Error::from)?
1873            .into_iter()
1874            .next()
1875            .ok_or(WebsocketError::NoResponse)
1876            .map_err(anyhow::Error::from)
1877    }
1878
1879    async fn reference_price(
1880        &self,
1881        params: ReferencePriceParams,
1882    ) -> anyhow::Result<WebsocketApiResponse<Box<models::ReferencePriceResponseResult>>> {
1883        let ReferencePriceParams { symbol, id } = params;
1884
1885        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
1886        payload.insert("symbol".to_string(), serde_json::json!(symbol));
1887        if let Some(value) = id {
1888            payload.insert("id".to_string(), serde_json::json!(value));
1889        }
1890        let payload = remove_empty_value(payload);
1891
1892        self.websocket_api_base
1893            .send_message::<Box<models::ReferencePriceResponseResult>>(
1894                "/referencePrice".trim_start_matches('/'),
1895                payload,
1896                WebsocketMessageSendOptions::new(),
1897            )
1898            .await
1899            .map_err(anyhow::Error::from)?
1900            .into_iter()
1901            .next()
1902            .ok_or(WebsocketError::NoResponse)
1903            .map_err(anyhow::Error::from)
1904    }
1905
1906    async fn reference_price_calculation(
1907        &self,
1908        params: ReferencePriceCalculationParams,
1909    ) -> anyhow::Result<WebsocketApiResponse<Box<models::ReferencePriceCalculationResponseResult>>>
1910    {
1911        let ReferencePriceCalculationParams {
1912            symbol,
1913            id,
1914            symbol_status,
1915        } = params;
1916
1917        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
1918        payload.insert("symbol".to_string(), serde_json::json!(symbol));
1919        if let Some(value) = id {
1920            payload.insert("id".to_string(), serde_json::json!(value));
1921        }
1922        if let Some(value) = symbol_status {
1923            payload.insert("symbolStatus".to_string(), serde_json::json!(value));
1924        }
1925        let payload = remove_empty_value(payload);
1926
1927        self.websocket_api_base
1928            .send_message::<Box<models::ReferencePriceCalculationResponseResult>>(
1929                "/referencePrice.calculation".trim_start_matches('/'),
1930                payload,
1931                WebsocketMessageSendOptions::new(),
1932            )
1933            .await
1934            .map_err(anyhow::Error::from)?
1935            .into_iter()
1936            .next()
1937            .ok_or(WebsocketError::NoResponse)
1938            .map_err(anyhow::Error::from)
1939    }
1940
1941    async fn ticker(
1942        &self,
1943        params: TickerParams,
1944    ) -> anyhow::Result<WebsocketApiResponse<models::TickerResponse>> {
1945        let TickerParams {
1946            id,
1947            symbol,
1948            symbols,
1949            r#type,
1950            window_size,
1951            symbol_status,
1952        } = params;
1953
1954        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
1955        if let Some(value) = id {
1956            payload.insert("id".to_string(), serde_json::json!(value));
1957        }
1958        if let Some(value) = symbol {
1959            payload.insert("symbol".to_string(), serde_json::json!(value));
1960        }
1961        if let Some(value) = symbols {
1962            payload.insert("symbols".to_string(), serde_json::json!(value));
1963        }
1964        if let Some(value) = r#type {
1965            payload.insert("type".to_string(), serde_json::json!(value));
1966        }
1967        if let Some(value) = window_size {
1968            payload.insert("windowSize".to_string(), serde_json::json!(value));
1969        }
1970        if let Some(value) = symbol_status {
1971            payload.insert("symbolStatus".to_string(), serde_json::json!(value));
1972        }
1973        let payload = remove_empty_value(payload);
1974
1975        self.websocket_api_base
1976            .send_message::<models::TickerResponse>(
1977                "/ticker".trim_start_matches('/'),
1978                payload,
1979                WebsocketMessageSendOptions::new(),
1980            )
1981            .await
1982            .map_err(anyhow::Error::from)?
1983            .into_iter()
1984            .next()
1985            .ok_or(WebsocketError::NoResponse)
1986            .map_err(anyhow::Error::from)
1987    }
1988
1989    async fn ticker24hr(
1990        &self,
1991        params: Ticker24hrParams,
1992    ) -> anyhow::Result<WebsocketApiResponse<models::Ticker24hrResponse>> {
1993        let Ticker24hrParams {
1994            id,
1995            symbol,
1996            symbols,
1997            r#type,
1998            symbol_status,
1999        } = params;
2000
2001        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
2002        if let Some(value) = id {
2003            payload.insert("id".to_string(), serde_json::json!(value));
2004        }
2005        if let Some(value) = symbol {
2006            payload.insert("symbol".to_string(), serde_json::json!(value));
2007        }
2008        if let Some(value) = symbols {
2009            payload.insert("symbols".to_string(), serde_json::json!(value));
2010        }
2011        if let Some(value) = r#type {
2012            payload.insert("type".to_string(), serde_json::json!(value));
2013        }
2014        if let Some(value) = symbol_status {
2015            payload.insert("symbolStatus".to_string(), serde_json::json!(value));
2016        }
2017        let payload = remove_empty_value(payload);
2018
2019        self.websocket_api_base
2020            .send_message::<models::Ticker24hrResponse>(
2021                "/ticker.24hr".trim_start_matches('/'),
2022                payload,
2023                WebsocketMessageSendOptions::new(),
2024            )
2025            .await
2026            .map_err(anyhow::Error::from)?
2027            .into_iter()
2028            .next()
2029            .ok_or(WebsocketError::NoResponse)
2030            .map_err(anyhow::Error::from)
2031    }
2032
2033    async fn ticker_book(
2034        &self,
2035        params: TickerBookParams,
2036    ) -> anyhow::Result<WebsocketApiResponse<models::TickerBookResponse>> {
2037        let TickerBookParams {
2038            id,
2039            symbol,
2040            symbols,
2041            symbol_status,
2042        } = params;
2043
2044        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
2045        if let Some(value) = id {
2046            payload.insert("id".to_string(), serde_json::json!(value));
2047        }
2048        if let Some(value) = symbol {
2049            payload.insert("symbol".to_string(), serde_json::json!(value));
2050        }
2051        if let Some(value) = symbols {
2052            payload.insert("symbols".to_string(), serde_json::json!(value));
2053        }
2054        if let Some(value) = symbol_status {
2055            payload.insert("symbolStatus".to_string(), serde_json::json!(value));
2056        }
2057        let payload = remove_empty_value(payload);
2058
2059        self.websocket_api_base
2060            .send_message::<models::TickerBookResponse>(
2061                "/ticker.book".trim_start_matches('/'),
2062                payload,
2063                WebsocketMessageSendOptions::new(),
2064            )
2065            .await
2066            .map_err(anyhow::Error::from)?
2067            .into_iter()
2068            .next()
2069            .ok_or(WebsocketError::NoResponse)
2070            .map_err(anyhow::Error::from)
2071    }
2072
2073    async fn ticker_price(
2074        &self,
2075        params: TickerPriceParams,
2076    ) -> anyhow::Result<WebsocketApiResponse<models::TickerPriceResponse>> {
2077        let TickerPriceParams {
2078            id,
2079            symbol,
2080            symbols,
2081            symbol_status,
2082        } = params;
2083
2084        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
2085        if let Some(value) = id {
2086            payload.insert("id".to_string(), serde_json::json!(value));
2087        }
2088        if let Some(value) = symbol {
2089            payload.insert("symbol".to_string(), serde_json::json!(value));
2090        }
2091        if let Some(value) = symbols {
2092            payload.insert("symbols".to_string(), serde_json::json!(value));
2093        }
2094        if let Some(value) = symbol_status {
2095            payload.insert("symbolStatus".to_string(), serde_json::json!(value));
2096        }
2097        let payload = remove_empty_value(payload);
2098
2099        self.websocket_api_base
2100            .send_message::<models::TickerPriceResponse>(
2101                "/ticker.price".trim_start_matches('/'),
2102                payload,
2103                WebsocketMessageSendOptions::new(),
2104            )
2105            .await
2106            .map_err(anyhow::Error::from)?
2107            .into_iter()
2108            .next()
2109            .ok_or(WebsocketError::NoResponse)
2110            .map_err(anyhow::Error::from)
2111    }
2112
2113    async fn ticker_trading_day(
2114        &self,
2115        params: TickerTradingDayParams,
2116    ) -> anyhow::Result<WebsocketApiResponse<Vec<models::TickerTradingDayResponseResultInner>>>
2117    {
2118        let TickerTradingDayParams {
2119            id,
2120            symbol,
2121            symbols,
2122            time_zone,
2123            r#type,
2124            symbol_status,
2125        } = params;
2126
2127        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
2128        if let Some(value) = id {
2129            payload.insert("id".to_string(), serde_json::json!(value));
2130        }
2131        if let Some(value) = symbol {
2132            payload.insert("symbol".to_string(), serde_json::json!(value));
2133        }
2134        if let Some(value) = symbols {
2135            payload.insert("symbols".to_string(), serde_json::json!(value));
2136        }
2137        if let Some(value) = time_zone {
2138            payload.insert("timeZone".to_string(), serde_json::json!(value));
2139        }
2140        if let Some(value) = r#type {
2141            payload.insert("type".to_string(), serde_json::json!(value));
2142        }
2143        if let Some(value) = symbol_status {
2144            payload.insert("symbolStatus".to_string(), serde_json::json!(value));
2145        }
2146        let payload = remove_empty_value(payload);
2147
2148        self.websocket_api_base
2149            .send_message::<Vec<models::TickerTradingDayResponseResultInner>>(
2150                "/ticker.tradingDay".trim_start_matches('/'),
2151                payload,
2152                WebsocketMessageSendOptions::new(),
2153            )
2154            .await
2155            .map_err(anyhow::Error::from)?
2156            .into_iter()
2157            .next()
2158            .ok_or(WebsocketError::NoResponse)
2159            .map_err(anyhow::Error::from)
2160    }
2161
2162    async fn trades_aggregate(
2163        &self,
2164        params: TradesAggregateParams,
2165    ) -> anyhow::Result<WebsocketApiResponse<Vec<models::TradesAggregateResponseResultInner>>> {
2166        let TradesAggregateParams {
2167            symbol,
2168            id,
2169            from_id,
2170            start_time,
2171            end_time,
2172            limit,
2173        } = params;
2174
2175        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
2176        payload.insert("symbol".to_string(), serde_json::json!(symbol));
2177        if let Some(value) = id {
2178            payload.insert("id".to_string(), serde_json::json!(value));
2179        }
2180        if let Some(value) = from_id {
2181            payload.insert("fromId".to_string(), serde_json::json!(value));
2182        }
2183        if let Some(value) = start_time {
2184            payload.insert("startTime".to_string(), serde_json::json!(value));
2185        }
2186        if let Some(value) = end_time {
2187            payload.insert("endTime".to_string(), serde_json::json!(value));
2188        }
2189        if let Some(value) = limit {
2190            payload.insert("limit".to_string(), serde_json::json!(value));
2191        }
2192        let payload = remove_empty_value(payload);
2193
2194        self.websocket_api_base
2195            .send_message::<Vec<models::TradesAggregateResponseResultInner>>(
2196                "/trades.aggregate".trim_start_matches('/'),
2197                payload,
2198                WebsocketMessageSendOptions::new(),
2199            )
2200            .await
2201            .map_err(anyhow::Error::from)?
2202            .into_iter()
2203            .next()
2204            .ok_or(WebsocketError::NoResponse)
2205            .map_err(anyhow::Error::from)
2206    }
2207
2208    async fn trades_historical(
2209        &self,
2210        params: TradesHistoricalParams,
2211    ) -> anyhow::Result<WebsocketApiResponse<Vec<models::TradesHistoricalResponseResultInner>>>
2212    {
2213        let TradesHistoricalParams {
2214            symbol,
2215            id,
2216            from_id,
2217            limit,
2218        } = params;
2219
2220        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
2221        payload.insert("symbol".to_string(), serde_json::json!(symbol));
2222        if let Some(value) = id {
2223            payload.insert("id".to_string(), serde_json::json!(value));
2224        }
2225        if let Some(value) = from_id {
2226            payload.insert("fromId".to_string(), serde_json::json!(value));
2227        }
2228        if let Some(value) = limit {
2229            payload.insert("limit".to_string(), serde_json::json!(value));
2230        }
2231        let payload = remove_empty_value(payload);
2232
2233        self.websocket_api_base
2234            .send_message::<Vec<models::TradesHistoricalResponseResultInner>>(
2235                "/trades.historical".trim_start_matches('/'),
2236                payload,
2237                WebsocketMessageSendOptions::new(),
2238            )
2239            .await
2240            .map_err(anyhow::Error::from)?
2241            .into_iter()
2242            .next()
2243            .ok_or(WebsocketError::NoResponse)
2244            .map_err(anyhow::Error::from)
2245    }
2246
2247    async fn trades_recent(
2248        &self,
2249        params: TradesRecentParams,
2250    ) -> anyhow::Result<WebsocketApiResponse<Vec<models::TradesRecentResponseResultInner>>> {
2251        let TradesRecentParams { symbol, id, limit } = params;
2252
2253        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
2254        payload.insert("symbol".to_string(), serde_json::json!(symbol));
2255        if let Some(value) = id {
2256            payload.insert("id".to_string(), serde_json::json!(value));
2257        }
2258        if let Some(value) = limit {
2259            payload.insert("limit".to_string(), serde_json::json!(value));
2260        }
2261        let payload = remove_empty_value(payload);
2262
2263        self.websocket_api_base
2264            .send_message::<Vec<models::TradesRecentResponseResultInner>>(
2265                "/trades.recent".trim_start_matches('/'),
2266                payload,
2267                WebsocketMessageSendOptions::new(),
2268            )
2269            .await
2270            .map_err(anyhow::Error::from)?
2271            .into_iter()
2272            .next()
2273            .ok_or(WebsocketError::NoResponse)
2274            .map_err(anyhow::Error::from)
2275    }
2276
2277    async fn ui_klines(
2278        &self,
2279        params: UiKlinesParams,
2280    ) -> anyhow::Result<WebsocketApiResponse<Vec<Vec<models::KlinesItemInner>>>> {
2281        let UiKlinesParams {
2282            symbol,
2283            interval,
2284            id,
2285            start_time,
2286            end_time,
2287            time_zone,
2288            limit,
2289        } = params;
2290
2291        let mut payload: BTreeMap<String, Value> = BTreeMap::new();
2292        payload.insert("symbol".to_string(), serde_json::json!(symbol));
2293        payload.insert("interval".to_string(), serde_json::json!(interval));
2294        if let Some(value) = id {
2295            payload.insert("id".to_string(), serde_json::json!(value));
2296        }
2297        if let Some(value) = start_time {
2298            payload.insert("startTime".to_string(), serde_json::json!(value));
2299        }
2300        if let Some(value) = end_time {
2301            payload.insert("endTime".to_string(), serde_json::json!(value));
2302        }
2303        if let Some(value) = time_zone {
2304            payload.insert("timeZone".to_string(), serde_json::json!(value));
2305        }
2306        if let Some(value) = limit {
2307            payload.insert("limit".to_string(), serde_json::json!(value));
2308        }
2309        let payload = remove_empty_value(payload);
2310
2311        self.websocket_api_base
2312            .send_message::<Vec<Vec<models::KlinesItemInner>>>(
2313                "/uiKlines".trim_start_matches('/'),
2314                payload,
2315                WebsocketMessageSendOptions::new(),
2316            )
2317            .await
2318            .map_err(anyhow::Error::from)?
2319            .into_iter()
2320            .next()
2321            .ok_or(WebsocketError::NoResponse)
2322            .map_err(anyhow::Error::from)
2323    }
2324}
2325
2326#[cfg(all(test, feature = "spot"))]
2327mod tests {
2328    use super::*;
2329    use crate::TOKIO_SHARED_RT;
2330    use crate::common::websocket::{WebsocketApi, WebsocketConnection, WebsocketHandler};
2331    use crate::config::ConfigurationWebsocketApi;
2332    use crate::errors::WebsocketError;
2333    use crate::models::WebsocketApiRateLimit;
2334    use serde_json::{Value, json};
2335    use tokio::spawn;
2336    use tokio::sync::mpsc::{UnboundedReceiver, unbounded_channel};
2337    use tokio::time::{Duration, timeout};
2338    use tokio_tungstenite::tungstenite::Message;
2339
2340    async fn setup() -> (
2341        Arc<WebsocketApi>,
2342        Arc<WebsocketConnection>,
2343        UnboundedReceiver<Message>,
2344    ) {
2345        let conn = WebsocketConnection::new("test-conn");
2346        let (tx, rx) = unbounded_channel::<Message>();
2347        {
2348            let mut conn_state = conn.state.lock().await;
2349            conn_state.ws_write_tx = Some(tx);
2350        }
2351
2352        let config = ConfigurationWebsocketApi::builder()
2353            .api_key("key")
2354            .api_secret("secret")
2355            .build()
2356            .expect("Failed to build configuration");
2357        let ws_api = WebsocketApi::new(config, vec![conn.clone()]);
2358        conn.set_handler(ws_api.clone() as Arc<dyn WebsocketHandler>)
2359            .await;
2360        ws_api.clone().connect().await.unwrap();
2361
2362        (ws_api, conn, rx)
2363    }
2364
2365    #[test]
2366    fn avg_price_success() {
2367        TOKIO_SHARED_RT.block_on(async {
2368            let (ws_api, conn, mut rx) = setup().await;
2369            let client = MarketApiClient::new(ws_api.clone());
2370
2371            let handle = spawn(async move {
2372                let params = AvgPriceParams::builder("BNBUSDT".to_string(),).build().unwrap();
2373                client.avg_price(params).await
2374            });
2375
2376            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
2377            let Message::Text(text) = sent else { panic!() };
2378            let v: Value = serde_json::from_str(&text).unwrap();
2379            let id = v["id"].as_str().unwrap();
2380            assert_eq!(v["method"], "/avgPrice".trim_start_matches('/'));
2381
2382            let mut resp_json: Value = serde_json::from_str(r#"{"id":"ddbfb65f-9ebf-42ec-8240-8f0f91de0867","status":200,"result":{"mins":5,"price":"9.35751834","closeTime":1694061154503},"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000,"count":2}]}"#).unwrap();
2383            resp_json["id"] = id.into();
2384
2385            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
2386            let expected_data: Box<models::AvgPriceResponseResult> = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
2387            let empty_array = Value::Array(vec![]);
2388            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
2389            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
2390                match raw_rate_limits.as_array() {
2391                    Some(arr) if arr.is_empty() => None,
2392                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
2393                    None => None,
2394                };
2395
2396            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
2397
2398            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");
2399
2400
2401            let response_rate_limits = response.rate_limits.clone();
2402            let response_data = response.data().expect("deserialize data");
2403
2404            assert_eq!(response_rate_limits, expected_rate_limits);
2405            assert_eq!(response_data, expected_data);
2406        });
2407    }
2408
2409    #[test]
2410    fn avg_price_error_response() {
2411        TOKIO_SHARED_RT.block_on(async {
2412            let (ws_api, conn, mut rx) = setup().await;
2413            let client = MarketApiClient::new(ws_api.clone());
2414
2415            let handle = tokio::spawn(async move {
2416                let params = AvgPriceParams::builder("BNBUSDT".to_string(),).build().unwrap();
2417                client.avg_price(params).await
2418            });
2419
2420            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
2421            let Message::Text(text) = sent else { panic!() };
2422            let v: Value = serde_json::from_str(&text).unwrap();
2423            let id = v["id"].as_str().unwrap().to_string();
2424
2425            let resp_json = json!({
2426                "id": id,
2427                "status": 400,
2428                    "error": {
2429                        "code": -2010,
2430                        "msg": "Account has insufficient balance for requested action.",
2431                    },
2432                    "rateLimits": [
2433                        {
2434                            "rateLimitType": "ORDERS",
2435                            "interval": "SECOND",
2436                            "intervalNum": 10,
2437                            "limit": 50,
2438                            "count": 13
2439                        },
2440                    ],
2441            });
2442            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
2443
2444            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
2445            match join {
2446                Ok(Err(e)) => {
2447                    let msg = e.to_string();
2448                    assert!(
2449                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
2450                        "Expected error msg to contain server error, got: {msg}"
2451                    );
2452                }
2453                Ok(Ok(_)) => panic!("Expected error"),
2454                Err(_) => panic!("Task panicked"),
2455            }
2456        });
2457    }
2458
2459    #[test]
2460    fn avg_price_request_timeout() {
2461        TOKIO_SHARED_RT.block_on(async {
2462            let (ws_api, _conn, mut rx) = setup().await;
2463            let client = MarketApiClient::new(ws_api.clone());
2464
2465            let handle = spawn(async move {
2466                let params = AvgPriceParams::builder("BNBUSDT".to_string())
2467                    .build()
2468                    .unwrap();
2469                client.avg_price(params).await
2470            });
2471
2472            let sent = timeout(Duration::from_secs(1), rx.recv())
2473                .await
2474                .expect("send should occur")
2475                .expect("channel closed");
2476            let Message::Text(text) = sent else {
2477                panic!("expected Message Text")
2478            };
2479
2480            let _: Value = serde_json::from_str(&text).unwrap();
2481
2482            let result = handle.await.expect("task completed");
2483            match result {
2484                Err(e) => {
2485                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
2486                        assert!(matches!(inner, WebsocketError::Timeout));
2487                    } else {
2488                        panic!("Unexpected error type: {:?}", e);
2489                    }
2490                }
2491                Ok(_) => panic!("Expected timeout error"),
2492            }
2493        });
2494    }
2495
2496    #[test]
2497    fn block_trades_historical_success() {
2498        TOKIO_SHARED_RT.block_on(async {
2499            let (ws_api, conn, mut rx) = setup().await;
2500            let client = MarketApiClient::new(ws_api.clone());
2501
2502            let handle = spawn(async move {
2503                let params = BlockTradesHistoricalParams::builder("BNBUSDT".to_string(),1,).build().unwrap();
2504                client.block_trades_historical(params).await
2505            });
2506
2507            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
2508            let Message::Text(text) = sent else { panic!() };
2509            let v: Value = serde_json::from_str(&text).unwrap();
2510            let id = v["id"].as_str().unwrap();
2511            assert_eq!(v["method"], "/blockTrades.historical".trim_start_matches('/'));
2512
2513            let mut resp_json: Value = serde_json::from_str(r#"{"id":"cffc9c7d-4efc-4ce0-b587-6b87448f052a","status":200,"result":[{"id":582,"price":"0.052","qty":"5838","quoteQty":"303.576","time":1772506983321,"isBuyerMaker":true}],"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000,"count":10}]}"#).unwrap();
2514            resp_json["id"] = id.into();
2515
2516            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
2517            let expected_data: Vec<models::BlockTradesHistoricalResponseResultInner> = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
2518            let empty_array = Value::Array(vec![]);
2519            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
2520            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
2521                match raw_rate_limits.as_array() {
2522                    Some(arr) if arr.is_empty() => None,
2523                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
2524                    None => None,
2525                };
2526
2527            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
2528
2529            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");
2530
2531
2532            let response_rate_limits = response.rate_limits.clone();
2533            let response_data = response.data().expect("deserialize data");
2534
2535            assert_eq!(response_rate_limits, expected_rate_limits);
2536            assert_eq!(response_data, expected_data);
2537        });
2538    }
2539
2540    #[test]
2541    fn block_trades_historical_error_response() {
2542        TOKIO_SHARED_RT.block_on(async {
2543            let (ws_api, conn, mut rx) = setup().await;
2544            let client = MarketApiClient::new(ws_api.clone());
2545
2546            let handle = tokio::spawn(async move {
2547                let params = BlockTradesHistoricalParams::builder("BNBUSDT".to_string(),1,).build().unwrap();
2548                client.block_trades_historical(params).await
2549            });
2550
2551            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
2552            let Message::Text(text) = sent else { panic!() };
2553            let v: Value = serde_json::from_str(&text).unwrap();
2554            let id = v["id"].as_str().unwrap().to_string();
2555
2556            let resp_json = json!({
2557                "id": id,
2558                "status": 400,
2559                    "error": {
2560                        "code": -2010,
2561                        "msg": "Account has insufficient balance for requested action.",
2562                    },
2563                    "rateLimits": [
2564                        {
2565                            "rateLimitType": "ORDERS",
2566                            "interval": "SECOND",
2567                            "intervalNum": 10,
2568                            "limit": 50,
2569                            "count": 13
2570                        },
2571                    ],
2572            });
2573            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
2574
2575            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
2576            match join {
2577                Ok(Err(e)) => {
2578                    let msg = e.to_string();
2579                    assert!(
2580                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
2581                        "Expected error msg to contain server error, got: {msg}"
2582                    );
2583                }
2584                Ok(Ok(_)) => panic!("Expected error"),
2585                Err(_) => panic!("Task panicked"),
2586            }
2587        });
2588    }
2589
2590    #[test]
2591    fn block_trades_historical_request_timeout() {
2592        TOKIO_SHARED_RT.block_on(async {
2593            let (ws_api, _conn, mut rx) = setup().await;
2594            let client = MarketApiClient::new(ws_api.clone());
2595
2596            let handle = spawn(async move {
2597                let params = BlockTradesHistoricalParams::builder("BNBUSDT".to_string(), 1)
2598                    .build()
2599                    .unwrap();
2600                client.block_trades_historical(params).await
2601            });
2602
2603            let sent = timeout(Duration::from_secs(1), rx.recv())
2604                .await
2605                .expect("send should occur")
2606                .expect("channel closed");
2607            let Message::Text(text) = sent else {
2608                panic!("expected Message Text")
2609            };
2610
2611            let _: Value = serde_json::from_str(&text).unwrap();
2612
2613            let result = handle.await.expect("task completed");
2614            match result {
2615                Err(e) => {
2616                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
2617                        assert!(matches!(inner, WebsocketError::Timeout));
2618                    } else {
2619                        panic!("Unexpected error type: {:?}", e);
2620                    }
2621                }
2622                Ok(_) => panic!("Expected timeout error"),
2623            }
2624        });
2625    }
2626
2627    #[test]
2628    fn depth_success() {
2629        TOKIO_SHARED_RT.block_on(async {
2630            let (ws_api, conn, mut rx) = setup().await;
2631            let client = MarketApiClient::new(ws_api.clone());
2632
2633            let handle = spawn(async move {
2634                let params = DepthParams::builder("BNBUSDT".to_string(),).build().unwrap();
2635                client.depth(params).await
2636            });
2637
2638            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
2639            let Message::Text(text) = sent else { panic!() };
2640            let v: Value = serde_json::from_str(&text).unwrap();
2641            let id = v["id"].as_str().unwrap();
2642            assert_eq!(v["method"], "/depth".trim_start_matches('/'));
2643
2644            let mut resp_json: Value = serde_json::from_str(r#"{"id":"51e2affb-0aba-4821-ba75-f2625006eb43","status":200,"result":{"lastUpdateId":2731179239,"bids":[["0.01379900","3.43200000"],["0.01379800","3.24300000"],["0.01379700","10.45500000"],["0.01379600","3.82100000"],["0.01379500","10.26200000"]],"asks":[["0.01380000","5.91700000"],["0.01380100","6.01400000"],["0.01380200","0.26800000"],["0.01380300","0.33800000"],["0.01380400","0.26800000"]]},"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000,"count":2}]}"#).unwrap();
2645            resp_json["id"] = id.into();
2646
2647            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
2648            let expected_data: Box<models::DepthResponseResult> = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
2649            let empty_array = Value::Array(vec![]);
2650            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
2651            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
2652                match raw_rate_limits.as_array() {
2653                    Some(arr) if arr.is_empty() => None,
2654                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
2655                    None => None,
2656                };
2657
2658            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
2659
2660            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");
2661
2662
2663            let response_rate_limits = response.rate_limits.clone();
2664            let response_data = response.data().expect("deserialize data");
2665
2666            assert_eq!(response_rate_limits, expected_rate_limits);
2667            assert_eq!(response_data, expected_data);
2668        });
2669    }
2670
2671    #[test]
2672    fn depth_error_response() {
2673        TOKIO_SHARED_RT.block_on(async {
2674            let (ws_api, conn, mut rx) = setup().await;
2675            let client = MarketApiClient::new(ws_api.clone());
2676
2677            let handle = tokio::spawn(async move {
2678                let params = DepthParams::builder("BNBUSDT".to_string(),).build().unwrap();
2679                client.depth(params).await
2680            });
2681
2682            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
2683            let Message::Text(text) = sent else { panic!() };
2684            let v: Value = serde_json::from_str(&text).unwrap();
2685            let id = v["id"].as_str().unwrap().to_string();
2686
2687            let resp_json = json!({
2688                "id": id,
2689                "status": 400,
2690                    "error": {
2691                        "code": -2010,
2692                        "msg": "Account has insufficient balance for requested action.",
2693                    },
2694                    "rateLimits": [
2695                        {
2696                            "rateLimitType": "ORDERS",
2697                            "interval": "SECOND",
2698                            "intervalNum": 10,
2699                            "limit": 50,
2700                            "count": 13
2701                        },
2702                    ],
2703            });
2704            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
2705
2706            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
2707            match join {
2708                Ok(Err(e)) => {
2709                    let msg = e.to_string();
2710                    assert!(
2711                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
2712                        "Expected error msg to contain server error, got: {msg}"
2713                    );
2714                }
2715                Ok(Ok(_)) => panic!("Expected error"),
2716                Err(_) => panic!("Task panicked"),
2717            }
2718        });
2719    }
2720
2721    #[test]
2722    fn depth_request_timeout() {
2723        TOKIO_SHARED_RT.block_on(async {
2724            let (ws_api, _conn, mut rx) = setup().await;
2725            let client = MarketApiClient::new(ws_api.clone());
2726
2727            let handle = spawn(async move {
2728                let params = DepthParams::builder("BNBUSDT".to_string()).build().unwrap();
2729                client.depth(params).await
2730            });
2731
2732            let sent = timeout(Duration::from_secs(1), rx.recv())
2733                .await
2734                .expect("send should occur")
2735                .expect("channel closed");
2736            let Message::Text(text) = sent else {
2737                panic!("expected Message Text")
2738            };
2739
2740            let _: Value = serde_json::from_str(&text).unwrap();
2741
2742            let result = handle.await.expect("task completed");
2743            match result {
2744                Err(e) => {
2745                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
2746                        assert!(matches!(inner, WebsocketError::Timeout));
2747                    } else {
2748                        panic!("Unexpected error type: {:?}", e);
2749                    }
2750                }
2751                Ok(_) => panic!("Expected timeout error"),
2752            }
2753        });
2754    }
2755
2756    #[test]
2757    fn klines_success() {
2758        TOKIO_SHARED_RT.block_on(async {
2759            let (ws_api, conn, mut rx) = setup().await;
2760            let client = MarketApiClient::new(ws_api.clone());
2761
2762            let handle = spawn(async move {
2763                let params = KlinesParams::builder("BNBUSDT".to_string(),KlinesIntervalEnum::Interval1s,).build().unwrap();
2764                client.klines(params).await
2765            });
2766
2767            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
2768            let Message::Text(text) = sent else { panic!() };
2769            let v: Value = serde_json::from_str(&text).unwrap();
2770            let id = v["id"].as_str().unwrap();
2771            assert_eq!(v["method"], "/klines".trim_start_matches('/'));
2772
2773            let mut resp_json: Value = serde_json::from_str(r#"{"id":"1dbbeb56-8eea-466a-8f6e-86bdcfa2fc0b","status":200,"result":[[1655971200000,"0.01086000","0.01086600","0.01083600","0.01083800","2290.53800000",1655974799999,"24.85074442",2283,"1171.64000000","12.71225884","0"]],"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000,"count":2}]}"#).unwrap();
2774            resp_json["id"] = id.into();
2775
2776            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
2777            let expected_data: Vec<Vec<models::KlinesItemInner>> = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
2778            let empty_array = Value::Array(vec![]);
2779            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
2780            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
2781                match raw_rate_limits.as_array() {
2782                    Some(arr) if arr.is_empty() => None,
2783                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
2784                    None => None,
2785                };
2786
2787            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
2788
2789            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");
2790
2791
2792            let response_rate_limits = response.rate_limits.clone();
2793            let response_data = response.data().expect("deserialize data");
2794
2795            assert_eq!(response_rate_limits, expected_rate_limits);
2796            assert_eq!(response_data, expected_data);
2797        });
2798    }
2799
2800    #[test]
2801    fn klines_error_response() {
2802        TOKIO_SHARED_RT.block_on(async {
2803            let (ws_api, conn, mut rx) = setup().await;
2804            let client = MarketApiClient::new(ws_api.clone());
2805
2806            let handle = tokio::spawn(async move {
2807                let params = KlinesParams::builder("BNBUSDT".to_string(),KlinesIntervalEnum::Interval1s,).build().unwrap();
2808                client.klines(params).await
2809            });
2810
2811            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
2812            let Message::Text(text) = sent else { panic!() };
2813            let v: Value = serde_json::from_str(&text).unwrap();
2814            let id = v["id"].as_str().unwrap().to_string();
2815
2816            let resp_json = json!({
2817                "id": id,
2818                "status": 400,
2819                    "error": {
2820                        "code": -2010,
2821                        "msg": "Account has insufficient balance for requested action.",
2822                    },
2823                    "rateLimits": [
2824                        {
2825                            "rateLimitType": "ORDERS",
2826                            "interval": "SECOND",
2827                            "intervalNum": 10,
2828                            "limit": 50,
2829                            "count": 13
2830                        },
2831                    ],
2832            });
2833            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
2834
2835            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
2836            match join {
2837                Ok(Err(e)) => {
2838                    let msg = e.to_string();
2839                    assert!(
2840                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
2841                        "Expected error msg to contain server error, got: {msg}"
2842                    );
2843                }
2844                Ok(Ok(_)) => panic!("Expected error"),
2845                Err(_) => panic!("Task panicked"),
2846            }
2847        });
2848    }
2849
2850    #[test]
2851    fn klines_request_timeout() {
2852        TOKIO_SHARED_RT.block_on(async {
2853            let (ws_api, _conn, mut rx) = setup().await;
2854            let client = MarketApiClient::new(ws_api.clone());
2855
2856            let handle = spawn(async move {
2857                let params =
2858                    KlinesParams::builder("BNBUSDT".to_string(), KlinesIntervalEnum::Interval1s)
2859                        .build()
2860                        .unwrap();
2861                client.klines(params).await
2862            });
2863
2864            let sent = timeout(Duration::from_secs(1), rx.recv())
2865                .await
2866                .expect("send should occur")
2867                .expect("channel closed");
2868            let Message::Text(text) = sent else {
2869                panic!("expected Message Text")
2870            };
2871
2872            let _: Value = serde_json::from_str(&text).unwrap();
2873
2874            let result = handle.await.expect("task completed");
2875            match result {
2876                Err(e) => {
2877                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
2878                        assert!(matches!(inner, WebsocketError::Timeout));
2879                    } else {
2880                        panic!("Unexpected error type: {:?}", e);
2881                    }
2882                }
2883                Ok(_) => panic!("Expected timeout error"),
2884            }
2885        });
2886    }
2887
2888    #[test]
2889    fn reference_price_success() {
2890        TOKIO_SHARED_RT.block_on(async {
2891            let (ws_api, conn, mut rx) = setup().await;
2892            let client = MarketApiClient::new(ws_api.clone());
2893
2894            let handle = spawn(async move {
2895                let params = ReferencePriceParams::builder("BNBUSDT".to_string(),).build().unwrap();
2896                client.reference_price(params).await
2897            });
2898
2899            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
2900            let Message::Text(text) = sent else { panic!() };
2901            let v: Value = serde_json::from_str(&text).unwrap();
2902            let id = v["id"].as_str().unwrap();
2903            assert_eq!(v["method"], "/referencePrice".trim_start_matches('/'));
2904
2905            let mut resp_json: Value = serde_json::from_str(r#"{"id":"5132affa-0aba-4831-b475-f262504556b41","status":200,"result":{"symbol":"BAZUSD","referencePrice":"0.00501900","timestamp":1770946889251,"code":-2043,"msg":"This symbol doesn't have a reference price."}}"#).unwrap();
2906            resp_json["id"] = id.into();
2907
2908            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
2909            let expected_data: Box<models::ReferencePriceResponseResult> = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
2910            let empty_array = Value::Array(vec![]);
2911            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
2912            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
2913                match raw_rate_limits.as_array() {
2914                    Some(arr) if arr.is_empty() => None,
2915                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
2916                    None => None,
2917                };
2918
2919            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
2920
2921            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");
2922
2923
2924            let response_rate_limits = response.rate_limits.clone();
2925            let response_data = response.data().expect("deserialize data");
2926
2927            assert_eq!(response_rate_limits, expected_rate_limits);
2928            assert_eq!(response_data, expected_data);
2929        });
2930    }
2931
2932    #[test]
2933    fn reference_price_error_response() {
2934        TOKIO_SHARED_RT.block_on(async {
2935            let (ws_api, conn, mut rx) = setup().await;
2936            let client = MarketApiClient::new(ws_api.clone());
2937
2938            let handle = tokio::spawn(async move {
2939                let params = ReferencePriceParams::builder("BNBUSDT".to_string(),).build().unwrap();
2940                client.reference_price(params).await
2941            });
2942
2943            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
2944            let Message::Text(text) = sent else { panic!() };
2945            let v: Value = serde_json::from_str(&text).unwrap();
2946            let id = v["id"].as_str().unwrap().to_string();
2947
2948            let resp_json = json!({
2949                "id": id,
2950                "status": 400,
2951                    "error": {
2952                        "code": -2010,
2953                        "msg": "Account has insufficient balance for requested action.",
2954                    },
2955                    "rateLimits": [
2956                        {
2957                            "rateLimitType": "ORDERS",
2958                            "interval": "SECOND",
2959                            "intervalNum": 10,
2960                            "limit": 50,
2961                            "count": 13
2962                        },
2963                    ],
2964            });
2965            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
2966
2967            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
2968            match join {
2969                Ok(Err(e)) => {
2970                    let msg = e.to_string();
2971                    assert!(
2972                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
2973                        "Expected error msg to contain server error, got: {msg}"
2974                    );
2975                }
2976                Ok(Ok(_)) => panic!("Expected error"),
2977                Err(_) => panic!("Task panicked"),
2978            }
2979        });
2980    }
2981
2982    #[test]
2983    fn reference_price_request_timeout() {
2984        TOKIO_SHARED_RT.block_on(async {
2985            let (ws_api, _conn, mut rx) = setup().await;
2986            let client = MarketApiClient::new(ws_api.clone());
2987
2988            let handle = spawn(async move {
2989                let params = ReferencePriceParams::builder("BNBUSDT".to_string())
2990                    .build()
2991                    .unwrap();
2992                client.reference_price(params).await
2993            });
2994
2995            let sent = timeout(Duration::from_secs(1), rx.recv())
2996                .await
2997                .expect("send should occur")
2998                .expect("channel closed");
2999            let Message::Text(text) = sent else {
3000                panic!("expected Message Text")
3001            };
3002
3003            let _: Value = serde_json::from_str(&text).unwrap();
3004
3005            let result = handle.await.expect("task completed");
3006            match result {
3007                Err(e) => {
3008                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
3009                        assert!(matches!(inner, WebsocketError::Timeout));
3010                    } else {
3011                        panic!("Unexpected error type: {:?}", e);
3012                    }
3013                }
3014                Ok(_) => panic!("Expected timeout error"),
3015            }
3016        });
3017    }
3018
3019    #[test]
3020    fn reference_price_calculation_success() {
3021        TOKIO_SHARED_RT.block_on(async {
3022            let (ws_api, conn, mut rx) = setup().await;
3023            let client = MarketApiClient::new(ws_api.clone());
3024
3025            let handle = spawn(async move {
3026                let params = ReferencePriceCalculationParams::builder("BNBUSDT".to_string(),).build().unwrap();
3027                client.reference_price_calculation(params).await
3028            });
3029
3030            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
3031            let Message::Text(text) = sent else { panic!() };
3032            let v: Value = serde_json::from_str(&text).unwrap();
3033            let id = v["id"].as_str().unwrap();
3034            assert_eq!(v["method"], "/referencePrice.calculation".trim_start_matches('/'));
3035
3036            let mut resp_json: Value = serde_json::from_str(r#"{"id":"5132affa-0aba-4831-b475-f262504556b41","status":200,"result":{"symbol":"BAZUSD","calculationType":"EXTERNAL","bucketCount":10,"bucketWidthMs":1000,"externalCalculationId":42}}"#).unwrap();
3037            resp_json["id"] = id.into();
3038
3039            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
3040            let expected_data: Box<models::ReferencePriceCalculationResponseResult> = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
3041            let empty_array = Value::Array(vec![]);
3042            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
3043            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
3044                match raw_rate_limits.as_array() {
3045                    Some(arr) if arr.is_empty() => None,
3046                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
3047                    None => None,
3048                };
3049
3050            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
3051
3052            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");
3053
3054
3055            let response_rate_limits = response.rate_limits.clone();
3056            let response_data = response.data().expect("deserialize data");
3057
3058            assert_eq!(response_rate_limits, expected_rate_limits);
3059            assert_eq!(response_data, expected_data);
3060        });
3061    }
3062
3063    #[test]
3064    fn reference_price_calculation_error_response() {
3065        TOKIO_SHARED_RT.block_on(async {
3066            let (ws_api, conn, mut rx) = setup().await;
3067            let client = MarketApiClient::new(ws_api.clone());
3068
3069            let handle = tokio::spawn(async move {
3070                let params = ReferencePriceCalculationParams::builder("BNBUSDT".to_string(),).build().unwrap();
3071                client.reference_price_calculation(params).await
3072            });
3073
3074            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
3075            let Message::Text(text) = sent else { panic!() };
3076            let v: Value = serde_json::from_str(&text).unwrap();
3077            let id = v["id"].as_str().unwrap().to_string();
3078
3079            let resp_json = json!({
3080                "id": id,
3081                "status": 400,
3082                    "error": {
3083                        "code": -2010,
3084                        "msg": "Account has insufficient balance for requested action.",
3085                    },
3086                    "rateLimits": [
3087                        {
3088                            "rateLimitType": "ORDERS",
3089                            "interval": "SECOND",
3090                            "intervalNum": 10,
3091                            "limit": 50,
3092                            "count": 13
3093                        },
3094                    ],
3095            });
3096            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
3097
3098            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
3099            match join {
3100                Ok(Err(e)) => {
3101                    let msg = e.to_string();
3102                    assert!(
3103                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
3104                        "Expected error msg to contain server error, got: {msg}"
3105                    );
3106                }
3107                Ok(Ok(_)) => panic!("Expected error"),
3108                Err(_) => panic!("Task panicked"),
3109            }
3110        });
3111    }
3112
3113    #[test]
3114    fn reference_price_calculation_request_timeout() {
3115        TOKIO_SHARED_RT.block_on(async {
3116            let (ws_api, _conn, mut rx) = setup().await;
3117            let client = MarketApiClient::new(ws_api.clone());
3118
3119            let handle = spawn(async move {
3120                let params = ReferencePriceCalculationParams::builder("BNBUSDT".to_string())
3121                    .build()
3122                    .unwrap();
3123                client.reference_price_calculation(params).await
3124            });
3125
3126            let sent = timeout(Duration::from_secs(1), rx.recv())
3127                .await
3128                .expect("send should occur")
3129                .expect("channel closed");
3130            let Message::Text(text) = sent else {
3131                panic!("expected Message Text")
3132            };
3133
3134            let _: Value = serde_json::from_str(&text).unwrap();
3135
3136            let result = handle.await.expect("task completed");
3137            match result {
3138                Err(e) => {
3139                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
3140                        assert!(matches!(inner, WebsocketError::Timeout));
3141                    } else {
3142                        panic!("Unexpected error type: {:?}", e);
3143                    }
3144                }
3145                Ok(_) => panic!("Expected timeout error"),
3146            }
3147        });
3148    }
3149
3150    #[test]
3151    fn ticker_success() {
3152        TOKIO_SHARED_RT.block_on(async {
3153            let (ws_api, conn, mut rx) = setup().await;
3154            let client = MarketApiClient::new(ws_api.clone());
3155
3156            let handle = spawn(async move {
3157                let params = TickerParams::builder().build().unwrap();
3158                client.ticker(params).await
3159            });
3160
3161            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
3162            let Message::Text(text) = sent else { panic!() };
3163            let v: Value = serde_json::from_str(&text).unwrap();
3164            let id = v["id"].as_str().unwrap();
3165            assert_eq!(v["method"], "/ticker".trim_start_matches('/'));
3166
3167            let mut resp_json: Value = serde_json::from_str(r#"{"id":"bdb7c503-542c-495c-b797-4d2ee2e91173","status":200,"result":{"symbol":"BNBBTC","priceChange":"0.00061500","priceChangePercent":"4.735","weightedAvgPrice":"0.01368242","openPrice":"0.01298900","highPrice":"0.01418800","lowPrice":"0.01296000","lastPrice":"0.01360400","volume":"587179.23900000","quoteVolume":"8034.03382165","openTime":1659580020000,"closeTime":1660184865291,"firstId":192977765,"lastId":195365758,"count":2387994},"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000,"count":4}]}"#).unwrap();
3168            resp_json["id"] = id.into();
3169
3170            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
3171            let expected_data: models::TickerResponse = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
3172            let empty_array = Value::Array(vec![]);
3173            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
3174            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
3175                match raw_rate_limits.as_array() {
3176                    Some(arr) if arr.is_empty() => None,
3177                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
3178                    None => None,
3179                };
3180
3181            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
3182
3183            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");
3184
3185
3186            let response_rate_limits = response.rate_limits.clone();
3187            let response_data = response.data().expect("deserialize data");
3188
3189            assert_eq!(response_rate_limits, expected_rate_limits);
3190            assert_eq!(response_data, expected_data);
3191        });
3192    }
3193
3194    #[test]
3195    fn ticker_error_response() {
3196        TOKIO_SHARED_RT.block_on(async {
3197            let (ws_api, conn, mut rx) = setup().await;
3198            let client = MarketApiClient::new(ws_api.clone());
3199
3200            let handle = tokio::spawn(async move {
3201                let params = TickerParams::builder().build().unwrap();
3202                client.ticker(params).await
3203            });
3204
3205            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
3206            let Message::Text(text) = sent else { panic!() };
3207            let v: Value = serde_json::from_str(&text).unwrap();
3208            let id = v["id"].as_str().unwrap().to_string();
3209
3210            let resp_json = json!({
3211                "id": id,
3212                "status": 400,
3213                    "error": {
3214                        "code": -2010,
3215                        "msg": "Account has insufficient balance for requested action.",
3216                    },
3217                    "rateLimits": [
3218                        {
3219                            "rateLimitType": "ORDERS",
3220                            "interval": "SECOND",
3221                            "intervalNum": 10,
3222                            "limit": 50,
3223                            "count": 13
3224                        },
3225                    ],
3226            });
3227            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
3228
3229            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
3230            match join {
3231                Ok(Err(e)) => {
3232                    let msg = e.to_string();
3233                    assert!(
3234                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
3235                        "Expected error msg to contain server error, got: {msg}"
3236                    );
3237                }
3238                Ok(Ok(_)) => panic!("Expected error"),
3239                Err(_) => panic!("Task panicked"),
3240            }
3241        });
3242    }
3243
3244    #[test]
3245    fn ticker_request_timeout() {
3246        TOKIO_SHARED_RT.block_on(async {
3247            let (ws_api, _conn, mut rx) = setup().await;
3248            let client = MarketApiClient::new(ws_api.clone());
3249
3250            let handle = spawn(async move {
3251                let params = TickerParams::builder().build().unwrap();
3252                client.ticker(params).await
3253            });
3254
3255            let sent = timeout(Duration::from_secs(1), rx.recv())
3256                .await
3257                .expect("send should occur")
3258                .expect("channel closed");
3259            let Message::Text(text) = sent else {
3260                panic!("expected Message Text")
3261            };
3262
3263            let _: Value = serde_json::from_str(&text).unwrap();
3264
3265            let result = handle.await.expect("task completed");
3266            match result {
3267                Err(e) => {
3268                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
3269                        assert!(matches!(inner, WebsocketError::Timeout));
3270                    } else {
3271                        panic!("Unexpected error type: {:?}", e);
3272                    }
3273                }
3274                Ok(_) => panic!("Expected timeout error"),
3275            }
3276        });
3277    }
3278
3279    #[test]
3280    fn ticker24hr_success() {
3281        TOKIO_SHARED_RT.block_on(async {
3282            let (ws_api, conn, mut rx) = setup().await;
3283            let client = MarketApiClient::new(ws_api.clone());
3284
3285            let handle = spawn(async move {
3286                let params = Ticker24hrParams::builder().build().unwrap();
3287                client.ticker24hr(params).await
3288            });
3289
3290            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
3291            let Message::Text(text) = sent else { panic!() };
3292            let v: Value = serde_json::from_str(&text).unwrap();
3293            let id = v["id"].as_str().unwrap();
3294            assert_eq!(v["method"], "/ticker.24hr".trim_start_matches('/'));
3295
3296            let mut resp_json: Value = serde_json::from_str(r#"{"id":"9fa2a91b-3fca-4ed7-a9ad-58e3b67483de","status":200,"result":{"symbol":"BNBBTC","priceChange":"0.00013900","priceChangePercent":"1.020","weightedAvgPrice":"0.01382453","prevClosePrice":"0.01362800","lastPrice":"0.01376700","lastQty":"1.78800000","bidPrice":"0.01376700","bidQty":"4.64600000","askPrice":"0.01376800","askQty":"14.31400000","openPrice":"0.01362800","highPrice":"0.01414900","lowPrice":"0.01346600","volume":"69412.40500000","quoteVolume":"959.59411487","openTime":1660014164909,"closeTime":1660100564909,"firstId":194696115,"lastId":194968287,"count":272173},"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000,"count":2}]}"#).unwrap();
3297            resp_json["id"] = id.into();
3298
3299            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
3300            let expected_data: models::Ticker24hrResponse = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
3301            let empty_array = Value::Array(vec![]);
3302            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
3303            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
3304                match raw_rate_limits.as_array() {
3305                    Some(arr) if arr.is_empty() => None,
3306                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
3307                    None => None,
3308                };
3309
3310            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
3311
3312            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");
3313
3314
3315            let response_rate_limits = response.rate_limits.clone();
3316            let response_data = response.data().expect("deserialize data");
3317
3318            assert_eq!(response_rate_limits, expected_rate_limits);
3319            assert_eq!(response_data, expected_data);
3320        });
3321    }
3322
3323    #[test]
3324    fn ticker24hr_error_response() {
3325        TOKIO_SHARED_RT.block_on(async {
3326            let (ws_api, conn, mut rx) = setup().await;
3327            let client = MarketApiClient::new(ws_api.clone());
3328
3329            let handle = tokio::spawn(async move {
3330                let params = Ticker24hrParams::builder().build().unwrap();
3331                client.ticker24hr(params).await
3332            });
3333
3334            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
3335            let Message::Text(text) = sent else { panic!() };
3336            let v: Value = serde_json::from_str(&text).unwrap();
3337            let id = v["id"].as_str().unwrap().to_string();
3338
3339            let resp_json = json!({
3340                "id": id,
3341                "status": 400,
3342                    "error": {
3343                        "code": -2010,
3344                        "msg": "Account has insufficient balance for requested action.",
3345                    },
3346                    "rateLimits": [
3347                        {
3348                            "rateLimitType": "ORDERS",
3349                            "interval": "SECOND",
3350                            "intervalNum": 10,
3351                            "limit": 50,
3352                            "count": 13
3353                        },
3354                    ],
3355            });
3356            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
3357
3358            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
3359            match join {
3360                Ok(Err(e)) => {
3361                    let msg = e.to_string();
3362                    assert!(
3363                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
3364                        "Expected error msg to contain server error, got: {msg}"
3365                    );
3366                }
3367                Ok(Ok(_)) => panic!("Expected error"),
3368                Err(_) => panic!("Task panicked"),
3369            }
3370        });
3371    }
3372
3373    #[test]
3374    fn ticker24hr_request_timeout() {
3375        TOKIO_SHARED_RT.block_on(async {
3376            let (ws_api, _conn, mut rx) = setup().await;
3377            let client = MarketApiClient::new(ws_api.clone());
3378
3379            let handle = spawn(async move {
3380                let params = Ticker24hrParams::builder().build().unwrap();
3381                client.ticker24hr(params).await
3382            });
3383
3384            let sent = timeout(Duration::from_secs(1), rx.recv())
3385                .await
3386                .expect("send should occur")
3387                .expect("channel closed");
3388            let Message::Text(text) = sent else {
3389                panic!("expected Message Text")
3390            };
3391
3392            let _: Value = serde_json::from_str(&text).unwrap();
3393
3394            let result = handle.await.expect("task completed");
3395            match result {
3396                Err(e) => {
3397                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
3398                        assert!(matches!(inner, WebsocketError::Timeout));
3399                    } else {
3400                        panic!("Unexpected error type: {:?}", e);
3401                    }
3402                }
3403                Ok(_) => panic!("Expected timeout error"),
3404            }
3405        });
3406    }
3407
3408    #[test]
3409    fn ticker_book_success() {
3410        TOKIO_SHARED_RT.block_on(async {
3411            let (ws_api, conn, mut rx) = setup().await;
3412            let client = MarketApiClient::new(ws_api.clone());
3413
3414            let handle = spawn(async move {
3415                let params = TickerBookParams::builder().build().unwrap();
3416                client.ticker_book(params).await
3417            });
3418
3419            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
3420            let Message::Text(text) = sent else { panic!() };
3421            let v: Value = serde_json::from_str(&text).unwrap();
3422            let id = v["id"].as_str().unwrap();
3423            assert_eq!(v["method"], "/ticker.book".trim_start_matches('/'));
3424
3425            let mut resp_json: Value = serde_json::from_str(r#"{"id":"9d32157c-a556-4d27-9866-66760a174b57","status":200,"result":{"symbol":"BNBBTC","bidPrice":"0.01358000","bidQty":"12.53400000","askPrice":"0.01358100","askQty":"17.83700000"},"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000,"count":2}]}"#).unwrap();
3426            resp_json["id"] = id.into();
3427
3428            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
3429            let expected_data: models::TickerBookResponse = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
3430            let empty_array = Value::Array(vec![]);
3431            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
3432            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
3433                match raw_rate_limits.as_array() {
3434                    Some(arr) if arr.is_empty() => None,
3435                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
3436                    None => None,
3437                };
3438
3439            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
3440
3441            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");
3442
3443
3444            let response_rate_limits = response.rate_limits.clone();
3445            let response_data = response.data().expect("deserialize data");
3446
3447            assert_eq!(response_rate_limits, expected_rate_limits);
3448            assert_eq!(response_data, expected_data);
3449        });
3450    }
3451
3452    #[test]
3453    fn ticker_book_error_response() {
3454        TOKIO_SHARED_RT.block_on(async {
3455            let (ws_api, conn, mut rx) = setup().await;
3456            let client = MarketApiClient::new(ws_api.clone());
3457
3458            let handle = tokio::spawn(async move {
3459                let params = TickerBookParams::builder().build().unwrap();
3460                client.ticker_book(params).await
3461            });
3462
3463            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
3464            let Message::Text(text) = sent else { panic!() };
3465            let v: Value = serde_json::from_str(&text).unwrap();
3466            let id = v["id"].as_str().unwrap().to_string();
3467
3468            let resp_json = json!({
3469                "id": id,
3470                "status": 400,
3471                    "error": {
3472                        "code": -2010,
3473                        "msg": "Account has insufficient balance for requested action.",
3474                    },
3475                    "rateLimits": [
3476                        {
3477                            "rateLimitType": "ORDERS",
3478                            "interval": "SECOND",
3479                            "intervalNum": 10,
3480                            "limit": 50,
3481                            "count": 13
3482                        },
3483                    ],
3484            });
3485            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
3486
3487            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
3488            match join {
3489                Ok(Err(e)) => {
3490                    let msg = e.to_string();
3491                    assert!(
3492                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
3493                        "Expected error msg to contain server error, got: {msg}"
3494                    );
3495                }
3496                Ok(Ok(_)) => panic!("Expected error"),
3497                Err(_) => panic!("Task panicked"),
3498            }
3499        });
3500    }
3501
3502    #[test]
3503    fn ticker_book_request_timeout() {
3504        TOKIO_SHARED_RT.block_on(async {
3505            let (ws_api, _conn, mut rx) = setup().await;
3506            let client = MarketApiClient::new(ws_api.clone());
3507
3508            let handle = spawn(async move {
3509                let params = TickerBookParams::builder().build().unwrap();
3510                client.ticker_book(params).await
3511            });
3512
3513            let sent = timeout(Duration::from_secs(1), rx.recv())
3514                .await
3515                .expect("send should occur")
3516                .expect("channel closed");
3517            let Message::Text(text) = sent else {
3518                panic!("expected Message Text")
3519            };
3520
3521            let _: Value = serde_json::from_str(&text).unwrap();
3522
3523            let result = handle.await.expect("task completed");
3524            match result {
3525                Err(e) => {
3526                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
3527                        assert!(matches!(inner, WebsocketError::Timeout));
3528                    } else {
3529                        panic!("Unexpected error type: {:?}", e);
3530                    }
3531                }
3532                Ok(_) => panic!("Expected timeout error"),
3533            }
3534        });
3535    }
3536
3537    #[test]
3538    fn ticker_price_success() {
3539        TOKIO_SHARED_RT.block_on(async {
3540            let (ws_api, conn, mut rx) = setup().await;
3541            let client = MarketApiClient::new(ws_api.clone());
3542
3543            let handle = spawn(async move {
3544                let params = TickerPriceParams::builder().build().unwrap();
3545                client.ticker_price(params).await
3546            });
3547
3548            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
3549            let Message::Text(text) = sent else { panic!() };
3550            let v: Value = serde_json::from_str(&text).unwrap();
3551            let id = v["id"].as_str().unwrap();
3552            assert_eq!(v["method"], "/ticker.price".trim_start_matches('/'));
3553
3554            let mut resp_json: Value = serde_json::from_str(r#"{"id":"043a7cf2-bde3-4888-9604-c8ac41fcba4d","status":200,"result":{"symbol":"BNBBTC","price":"0.01361900"},"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000,"count":2}]}"#).unwrap();
3555            resp_json["id"] = id.into();
3556
3557            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
3558            let expected_data: models::TickerPriceResponse = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
3559            let empty_array = Value::Array(vec![]);
3560            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
3561            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
3562                match raw_rate_limits.as_array() {
3563                    Some(arr) if arr.is_empty() => None,
3564                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
3565                    None => None,
3566                };
3567
3568            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
3569
3570            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");
3571
3572
3573            let response_rate_limits = response.rate_limits.clone();
3574            let response_data = response.data().expect("deserialize data");
3575
3576            assert_eq!(response_rate_limits, expected_rate_limits);
3577            assert_eq!(response_data, expected_data);
3578        });
3579    }
3580
3581    #[test]
3582    fn ticker_price_error_response() {
3583        TOKIO_SHARED_RT.block_on(async {
3584            let (ws_api, conn, mut rx) = setup().await;
3585            let client = MarketApiClient::new(ws_api.clone());
3586
3587            let handle = tokio::spawn(async move {
3588                let params = TickerPriceParams::builder().build().unwrap();
3589                client.ticker_price(params).await
3590            });
3591
3592            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
3593            let Message::Text(text) = sent else { panic!() };
3594            let v: Value = serde_json::from_str(&text).unwrap();
3595            let id = v["id"].as_str().unwrap().to_string();
3596
3597            let resp_json = json!({
3598                "id": id,
3599                "status": 400,
3600                    "error": {
3601                        "code": -2010,
3602                        "msg": "Account has insufficient balance for requested action.",
3603                    },
3604                    "rateLimits": [
3605                        {
3606                            "rateLimitType": "ORDERS",
3607                            "interval": "SECOND",
3608                            "intervalNum": 10,
3609                            "limit": 50,
3610                            "count": 13
3611                        },
3612                    ],
3613            });
3614            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
3615
3616            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
3617            match join {
3618                Ok(Err(e)) => {
3619                    let msg = e.to_string();
3620                    assert!(
3621                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
3622                        "Expected error msg to contain server error, got: {msg}"
3623                    );
3624                }
3625                Ok(Ok(_)) => panic!("Expected error"),
3626                Err(_) => panic!("Task panicked"),
3627            }
3628        });
3629    }
3630
3631    #[test]
3632    fn ticker_price_request_timeout() {
3633        TOKIO_SHARED_RT.block_on(async {
3634            let (ws_api, _conn, mut rx) = setup().await;
3635            let client = MarketApiClient::new(ws_api.clone());
3636
3637            let handle = spawn(async move {
3638                let params = TickerPriceParams::builder().build().unwrap();
3639                client.ticker_price(params).await
3640            });
3641
3642            let sent = timeout(Duration::from_secs(1), rx.recv())
3643                .await
3644                .expect("send should occur")
3645                .expect("channel closed");
3646            let Message::Text(text) = sent else {
3647                panic!("expected Message Text")
3648            };
3649
3650            let _: Value = serde_json::from_str(&text).unwrap();
3651
3652            let result = handle.await.expect("task completed");
3653            match result {
3654                Err(e) => {
3655                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
3656                        assert!(matches!(inner, WebsocketError::Timeout));
3657                    } else {
3658                        panic!("Unexpected error type: {:?}", e);
3659                    }
3660                }
3661                Ok(_) => panic!("Expected timeout error"),
3662            }
3663        });
3664    }
3665
3666    #[test]
3667    fn ticker_trading_day_success() {
3668        TOKIO_SHARED_RT.block_on(async {
3669            let (ws_api, conn, mut rx) = setup().await;
3670            let client = MarketApiClient::new(ws_api.clone());
3671
3672            let handle = spawn(async move {
3673                let params = TickerTradingDayParams::builder().build().unwrap();
3674                client.ticker_trading_day(params).await
3675            });
3676
3677            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
3678            let Message::Text(text) = sent else { panic!() };
3679            let v: Value = serde_json::from_str(&text).unwrap();
3680            let id = v["id"].as_str().unwrap();
3681            assert_eq!(v["method"], "/ticker.tradingDay".trim_start_matches('/'));
3682
3683            let mut resp_json: Value = serde_json::from_str(r#"{"id":"f4b3b507-c8f2-442a-81a6-b2f12daa030f","status":200,"result":[{"symbol":"BNBUSDT","priceChange":"2.60000000","priceChangePercent":"1.238","weightedAvgPrice":"211.92276958","openPrice":"210.00000000","highPrice":"213.70000000","lowPrice":"209.70000000","lastPrice":"212.60000000","volume":"280709.58900000","quoteVolume":"59488753.54750000","openTime":1695686400000,"closeTime":1695772799999,"firstId":672397461,"lastId":672496158,"count":98698},{"symbol":"BTCUSDT","priceChange":"-83.13000000","priceChangePercent":"-0.317","weightedAvgPrice":"26234.58803036","openPrice":"26304.80000000","highPrice":"26397.46000000","lowPrice":"26088.34000000","lastPrice":"26221.67000000","volume":"18495.35066000","quoteVolume":"485217905.04210480","openTime":1695686400000,"closeTime":1695772799999,"firstId":3220151555,"lastId":3220849281,"count":697727}],"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000,"count":8}]}"#).unwrap();
3684            resp_json["id"] = id.into();
3685
3686            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
3687            let expected_data: Vec<models::TickerTradingDayResponseResultInner> = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
3688            let empty_array = Value::Array(vec![]);
3689            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
3690            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
3691                match raw_rate_limits.as_array() {
3692                    Some(arr) if arr.is_empty() => None,
3693                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
3694                    None => None,
3695                };
3696
3697            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
3698
3699            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");
3700
3701
3702            let response_rate_limits = response.rate_limits.clone();
3703            let response_data = response.data().expect("deserialize data");
3704
3705            assert_eq!(response_rate_limits, expected_rate_limits);
3706            assert_eq!(response_data, expected_data);
3707        });
3708    }
3709
3710    #[test]
3711    fn ticker_trading_day_error_response() {
3712        TOKIO_SHARED_RT.block_on(async {
3713            let (ws_api, conn, mut rx) = setup().await;
3714            let client = MarketApiClient::new(ws_api.clone());
3715
3716            let handle = tokio::spawn(async move {
3717                let params = TickerTradingDayParams::builder().build().unwrap();
3718                client.ticker_trading_day(params).await
3719            });
3720
3721            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
3722            let Message::Text(text) = sent else { panic!() };
3723            let v: Value = serde_json::from_str(&text).unwrap();
3724            let id = v["id"].as_str().unwrap().to_string();
3725
3726            let resp_json = json!({
3727                "id": id,
3728                "status": 400,
3729                    "error": {
3730                        "code": -2010,
3731                        "msg": "Account has insufficient balance for requested action.",
3732                    },
3733                    "rateLimits": [
3734                        {
3735                            "rateLimitType": "ORDERS",
3736                            "interval": "SECOND",
3737                            "intervalNum": 10,
3738                            "limit": 50,
3739                            "count": 13
3740                        },
3741                    ],
3742            });
3743            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
3744
3745            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
3746            match join {
3747                Ok(Err(e)) => {
3748                    let msg = e.to_string();
3749                    assert!(
3750                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
3751                        "Expected error msg to contain server error, got: {msg}"
3752                    );
3753                }
3754                Ok(Ok(_)) => panic!("Expected error"),
3755                Err(_) => panic!("Task panicked"),
3756            }
3757        });
3758    }
3759
3760    #[test]
3761    fn ticker_trading_day_request_timeout() {
3762        TOKIO_SHARED_RT.block_on(async {
3763            let (ws_api, _conn, mut rx) = setup().await;
3764            let client = MarketApiClient::new(ws_api.clone());
3765
3766            let handle = spawn(async move {
3767                let params = TickerTradingDayParams::builder().build().unwrap();
3768                client.ticker_trading_day(params).await
3769            });
3770
3771            let sent = timeout(Duration::from_secs(1), rx.recv())
3772                .await
3773                .expect("send should occur")
3774                .expect("channel closed");
3775            let Message::Text(text) = sent else {
3776                panic!("expected Message Text")
3777            };
3778
3779            let _: Value = serde_json::from_str(&text).unwrap();
3780
3781            let result = handle.await.expect("task completed");
3782            match result {
3783                Err(e) => {
3784                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
3785                        assert!(matches!(inner, WebsocketError::Timeout));
3786                    } else {
3787                        panic!("Unexpected error type: {:?}", e);
3788                    }
3789                }
3790                Ok(_) => panic!("Expected timeout error"),
3791            }
3792        });
3793    }
3794
3795    #[test]
3796    fn trades_aggregate_success() {
3797        TOKIO_SHARED_RT.block_on(async {
3798            let (ws_api, conn, mut rx) = setup().await;
3799            let client = MarketApiClient::new(ws_api.clone());
3800
3801            let handle = spawn(async move {
3802                let params = TradesAggregateParams::builder("BNBUSDT".to_string(),).build().unwrap();
3803                client.trades_aggregate(params).await
3804            });
3805
3806            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
3807            let Message::Text(text) = sent else { panic!() };
3808            let v: Value = serde_json::from_str(&text).unwrap();
3809            let id = v["id"].as_str().unwrap();
3810            assert_eq!(v["method"], "/trades.aggregate".trim_start_matches('/'));
3811
3812            let mut resp_json: Value = serde_json::from_str(r#"{"id":"189da436-d4bd-48ca-9f95-9f613d621717","status":200,"result":[{"a":50000000,"p":"0.00274100","q":"57.19000000","f":59120167,"l":59120170,"T":1565877971222,"m":true,"M":true}],"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000,"count":2}]}"#).unwrap();
3813            resp_json["id"] = id.into();
3814
3815            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
3816            let expected_data: Vec<models::TradesAggregateResponseResultInner> = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
3817            let empty_array = Value::Array(vec![]);
3818            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
3819            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
3820                match raw_rate_limits.as_array() {
3821                    Some(arr) if arr.is_empty() => None,
3822                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
3823                    None => None,
3824                };
3825
3826            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
3827
3828            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");
3829
3830
3831            let response_rate_limits = response.rate_limits.clone();
3832            let response_data = response.data().expect("deserialize data");
3833
3834            assert_eq!(response_rate_limits, expected_rate_limits);
3835            assert_eq!(response_data, expected_data);
3836        });
3837    }
3838
3839    #[test]
3840    fn trades_aggregate_error_response() {
3841        TOKIO_SHARED_RT.block_on(async {
3842            let (ws_api, conn, mut rx) = setup().await;
3843            let client = MarketApiClient::new(ws_api.clone());
3844
3845            let handle = tokio::spawn(async move {
3846                let params = TradesAggregateParams::builder("BNBUSDT".to_string(),).build().unwrap();
3847                client.trades_aggregate(params).await
3848            });
3849
3850            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
3851            let Message::Text(text) = sent else { panic!() };
3852            let v: Value = serde_json::from_str(&text).unwrap();
3853            let id = v["id"].as_str().unwrap().to_string();
3854
3855            let resp_json = json!({
3856                "id": id,
3857                "status": 400,
3858                    "error": {
3859                        "code": -2010,
3860                        "msg": "Account has insufficient balance for requested action.",
3861                    },
3862                    "rateLimits": [
3863                        {
3864                            "rateLimitType": "ORDERS",
3865                            "interval": "SECOND",
3866                            "intervalNum": 10,
3867                            "limit": 50,
3868                            "count": 13
3869                        },
3870                    ],
3871            });
3872            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
3873
3874            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
3875            match join {
3876                Ok(Err(e)) => {
3877                    let msg = e.to_string();
3878                    assert!(
3879                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
3880                        "Expected error msg to contain server error, got: {msg}"
3881                    );
3882                }
3883                Ok(Ok(_)) => panic!("Expected error"),
3884                Err(_) => panic!("Task panicked"),
3885            }
3886        });
3887    }
3888
3889    #[test]
3890    fn trades_aggregate_request_timeout() {
3891        TOKIO_SHARED_RT.block_on(async {
3892            let (ws_api, _conn, mut rx) = setup().await;
3893            let client = MarketApiClient::new(ws_api.clone());
3894
3895            let handle = spawn(async move {
3896                let params = TradesAggregateParams::builder("BNBUSDT".to_string())
3897                    .build()
3898                    .unwrap();
3899                client.trades_aggregate(params).await
3900            });
3901
3902            let sent = timeout(Duration::from_secs(1), rx.recv())
3903                .await
3904                .expect("send should occur")
3905                .expect("channel closed");
3906            let Message::Text(text) = sent else {
3907                panic!("expected Message Text")
3908            };
3909
3910            let _: Value = serde_json::from_str(&text).unwrap();
3911
3912            let result = handle.await.expect("task completed");
3913            match result {
3914                Err(e) => {
3915                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
3916                        assert!(matches!(inner, WebsocketError::Timeout));
3917                    } else {
3918                        panic!("Unexpected error type: {:?}", e);
3919                    }
3920                }
3921                Ok(_) => panic!("Expected timeout error"),
3922            }
3923        });
3924    }
3925
3926    #[test]
3927    fn trades_historical_success() {
3928        TOKIO_SHARED_RT.block_on(async {
3929            let (ws_api, conn, mut rx) = setup().await;
3930            let client = MarketApiClient::new(ws_api.clone());
3931
3932            let handle = spawn(async move {
3933                let params = TradesHistoricalParams::builder("BNBUSDT".to_string(),).build().unwrap();
3934                client.trades_historical(params).await
3935            });
3936
3937            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
3938            let Message::Text(text) = sent else { panic!() };
3939            let v: Value = serde_json::from_str(&text).unwrap();
3940            let id = v["id"].as_str().unwrap();
3941            assert_eq!(v["method"], "/trades.historical".trim_start_matches('/'));
3942
3943            let mut resp_json: Value = serde_json::from_str(r#"{"id":"cffc9c7d-4efc-4ce0-b587-6b87448f052a","status":200,"result":[{"id":0,"price":"0.00005000","qty":"40.00000000","quoteQty":"0.00200000","time":1500004800376,"isBuyerMaker":true,"isBestMatch":true}],"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000,"count":10}]}"#).unwrap();
3944            resp_json["id"] = id.into();
3945
3946            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
3947            let expected_data: Vec<models::TradesHistoricalResponseResultInner> = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
3948            let empty_array = Value::Array(vec![]);
3949            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
3950            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
3951                match raw_rate_limits.as_array() {
3952                    Some(arr) if arr.is_empty() => None,
3953                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
3954                    None => None,
3955                };
3956
3957            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
3958
3959            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");
3960
3961
3962            let response_rate_limits = response.rate_limits.clone();
3963            let response_data = response.data().expect("deserialize data");
3964
3965            assert_eq!(response_rate_limits, expected_rate_limits);
3966            assert_eq!(response_data, expected_data);
3967        });
3968    }
3969
3970    #[test]
3971    fn trades_historical_error_response() {
3972        TOKIO_SHARED_RT.block_on(async {
3973            let (ws_api, conn, mut rx) = setup().await;
3974            let client = MarketApiClient::new(ws_api.clone());
3975
3976            let handle = tokio::spawn(async move {
3977                let params = TradesHistoricalParams::builder("BNBUSDT".to_string(),).build().unwrap();
3978                client.trades_historical(params).await
3979            });
3980
3981            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
3982            let Message::Text(text) = sent else { panic!() };
3983            let v: Value = serde_json::from_str(&text).unwrap();
3984            let id = v["id"].as_str().unwrap().to_string();
3985
3986            let resp_json = json!({
3987                "id": id,
3988                "status": 400,
3989                    "error": {
3990                        "code": -2010,
3991                        "msg": "Account has insufficient balance for requested action.",
3992                    },
3993                    "rateLimits": [
3994                        {
3995                            "rateLimitType": "ORDERS",
3996                            "interval": "SECOND",
3997                            "intervalNum": 10,
3998                            "limit": 50,
3999                            "count": 13
4000                        },
4001                    ],
4002            });
4003            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
4004
4005            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
4006            match join {
4007                Ok(Err(e)) => {
4008                    let msg = e.to_string();
4009                    assert!(
4010                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
4011                        "Expected error msg to contain server error, got: {msg}"
4012                    );
4013                }
4014                Ok(Ok(_)) => panic!("Expected error"),
4015                Err(_) => panic!("Task panicked"),
4016            }
4017        });
4018    }
4019
4020    #[test]
4021    fn trades_historical_request_timeout() {
4022        TOKIO_SHARED_RT.block_on(async {
4023            let (ws_api, _conn, mut rx) = setup().await;
4024            let client = MarketApiClient::new(ws_api.clone());
4025
4026            let handle = spawn(async move {
4027                let params = TradesHistoricalParams::builder("BNBUSDT".to_string())
4028                    .build()
4029                    .unwrap();
4030                client.trades_historical(params).await
4031            });
4032
4033            let sent = timeout(Duration::from_secs(1), rx.recv())
4034                .await
4035                .expect("send should occur")
4036                .expect("channel closed");
4037            let Message::Text(text) = sent else {
4038                panic!("expected Message Text")
4039            };
4040
4041            let _: Value = serde_json::from_str(&text).unwrap();
4042
4043            let result = handle.await.expect("task completed");
4044            match result {
4045                Err(e) => {
4046                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
4047                        assert!(matches!(inner, WebsocketError::Timeout));
4048                    } else {
4049                        panic!("Unexpected error type: {:?}", e);
4050                    }
4051                }
4052                Ok(_) => panic!("Expected timeout error"),
4053            }
4054        });
4055    }
4056
4057    #[test]
4058    fn trades_recent_success() {
4059        TOKIO_SHARED_RT.block_on(async {
4060            let (ws_api, conn, mut rx) = setup().await;
4061            let client = MarketApiClient::new(ws_api.clone());
4062
4063            let handle = spawn(async move {
4064                let params = TradesRecentParams::builder("BNBUSDT".to_string(),).build().unwrap();
4065                client.trades_recent(params).await
4066            });
4067
4068            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
4069            let Message::Text(text) = sent else { panic!() };
4070            let v: Value = serde_json::from_str(&text).unwrap();
4071            let id = v["id"].as_str().unwrap();
4072            assert_eq!(v["method"], "/trades.recent".trim_start_matches('/'));
4073
4074            let mut resp_json: Value = serde_json::from_str(r#"{"id":"409a20bd-253d-41db-a6dd-687862a5882f","status":200,"result":[{"id":194686783,"price":"0.01361000","qty":"0.01400000","quoteQty":"0.00019054","time":1660009530807,"isBuyerMaker":true,"isBestMatch":true}],"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000,"count":2}]}"#).unwrap();
4075            resp_json["id"] = id.into();
4076
4077            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
4078            let expected_data: Vec<models::TradesRecentResponseResultInner> = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
4079            let empty_array = Value::Array(vec![]);
4080            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
4081            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
4082                match raw_rate_limits.as_array() {
4083                    Some(arr) if arr.is_empty() => None,
4084                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
4085                    None => None,
4086                };
4087
4088            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
4089
4090            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");
4091
4092
4093            let response_rate_limits = response.rate_limits.clone();
4094            let response_data = response.data().expect("deserialize data");
4095
4096            assert_eq!(response_rate_limits, expected_rate_limits);
4097            assert_eq!(response_data, expected_data);
4098        });
4099    }
4100
4101    #[test]
4102    fn trades_recent_error_response() {
4103        TOKIO_SHARED_RT.block_on(async {
4104            let (ws_api, conn, mut rx) = setup().await;
4105            let client = MarketApiClient::new(ws_api.clone());
4106
4107            let handle = tokio::spawn(async move {
4108                let params = TradesRecentParams::builder("BNBUSDT".to_string(),).build().unwrap();
4109                client.trades_recent(params).await
4110            });
4111
4112            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
4113            let Message::Text(text) = sent else { panic!() };
4114            let v: Value = serde_json::from_str(&text).unwrap();
4115            let id = v["id"].as_str().unwrap().to_string();
4116
4117            let resp_json = json!({
4118                "id": id,
4119                "status": 400,
4120                    "error": {
4121                        "code": -2010,
4122                        "msg": "Account has insufficient balance for requested action.",
4123                    },
4124                    "rateLimits": [
4125                        {
4126                            "rateLimitType": "ORDERS",
4127                            "interval": "SECOND",
4128                            "intervalNum": 10,
4129                            "limit": 50,
4130                            "count": 13
4131                        },
4132                    ],
4133            });
4134            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
4135
4136            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
4137            match join {
4138                Ok(Err(e)) => {
4139                    let msg = e.to_string();
4140                    assert!(
4141                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
4142                        "Expected error msg to contain server error, got: {msg}"
4143                    );
4144                }
4145                Ok(Ok(_)) => panic!("Expected error"),
4146                Err(_) => panic!("Task panicked"),
4147            }
4148        });
4149    }
4150
4151    #[test]
4152    fn trades_recent_request_timeout() {
4153        TOKIO_SHARED_RT.block_on(async {
4154            let (ws_api, _conn, mut rx) = setup().await;
4155            let client = MarketApiClient::new(ws_api.clone());
4156
4157            let handle = spawn(async move {
4158                let params = TradesRecentParams::builder("BNBUSDT".to_string())
4159                    .build()
4160                    .unwrap();
4161                client.trades_recent(params).await
4162            });
4163
4164            let sent = timeout(Duration::from_secs(1), rx.recv())
4165                .await
4166                .expect("send should occur")
4167                .expect("channel closed");
4168            let Message::Text(text) = sent else {
4169                panic!("expected Message Text")
4170            };
4171
4172            let _: Value = serde_json::from_str(&text).unwrap();
4173
4174            let result = handle.await.expect("task completed");
4175            match result {
4176                Err(e) => {
4177                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
4178                        assert!(matches!(inner, WebsocketError::Timeout));
4179                    } else {
4180                        panic!("Unexpected error type: {:?}", e);
4181                    }
4182                }
4183                Ok(_) => panic!("Expected timeout error"),
4184            }
4185        });
4186    }
4187
4188    #[test]
4189    fn ui_klines_success() {
4190        TOKIO_SHARED_RT.block_on(async {
4191            let (ws_api, conn, mut rx) = setup().await;
4192            let client = MarketApiClient::new(ws_api.clone());
4193
4194            let handle = spawn(async move {
4195                let params = UiKlinesParams::builder("BNBUSDT".to_string(),UiKlinesIntervalEnum::Interval1s,).build().unwrap();
4196                client.ui_klines(params).await
4197            });
4198
4199            let sent = timeout(Duration::from_secs(1), rx.recv()).await.expect("send should occur").expect("channel closed");
4200            let Message::Text(text) = sent else { panic!() };
4201            let v: Value = serde_json::from_str(&text).unwrap();
4202            let id = v["id"].as_str().unwrap();
4203            assert_eq!(v["method"], "/uiKlines".trim_start_matches('/'));
4204
4205            let mut resp_json: Value = serde_json::from_str(r#"{"id":"b137468a-fb20-4c06-bd6b-625148eec958","status":200,"result":[[1655971200000,"0.01086000","0.01086600","0.01083600","0.01083800","2290.53800000",1655974799999,"24.85074442",2283,"1171.64000000","12.71225884","0"]],"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000,"count":2}]}"#).unwrap();
4206            resp_json["id"] = id.into();
4207
4208            let raw_data = resp_json.get("result").or_else(|| resp_json.get("response")).expect("no response in JSON");
4209            let expected_data: Vec<Vec<models::KlinesItemInner>> = serde_json::from_value(raw_data.clone()).expect("should parse raw response");
4210            let empty_array = Value::Array(vec![]);
4211            let raw_rate_limits = resp_json.get("rateLimits").unwrap_or(&empty_array);
4212            let expected_rate_limits: Option<Vec<WebsocketApiRateLimit>> =
4213                match raw_rate_limits.as_array() {
4214                    Some(arr) if arr.is_empty() => None,
4215                    Some(_) => Some(serde_json::from_value(raw_rate_limits.clone()).expect("should parse rateLimits array")),
4216                    None => None,
4217                };
4218
4219            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
4220
4221            let response = timeout(Duration::from_secs(1), handle).await.expect("task done").expect("no panic").expect("no error");
4222
4223
4224            let response_rate_limits = response.rate_limits.clone();
4225            let response_data = response.data().expect("deserialize data");
4226
4227            assert_eq!(response_rate_limits, expected_rate_limits);
4228            assert_eq!(response_data, expected_data);
4229        });
4230    }
4231
4232    #[test]
4233    fn ui_klines_error_response() {
4234        TOKIO_SHARED_RT.block_on(async {
4235            let (ws_api, conn, mut rx) = setup().await;
4236            let client = MarketApiClient::new(ws_api.clone());
4237
4238            let handle = tokio::spawn(async move {
4239                let params = UiKlinesParams::builder("BNBUSDT".to_string(),UiKlinesIntervalEnum::Interval1s,).build().unwrap();
4240                client.ui_klines(params).await
4241            });
4242
4243            let sent = timeout(Duration::from_secs(1), rx.recv()).await.unwrap().unwrap();
4244            let Message::Text(text) = sent else { panic!() };
4245            let v: Value = serde_json::from_str(&text).unwrap();
4246            let id = v["id"].as_str().unwrap().to_string();
4247
4248            let resp_json = json!({
4249                "id": id,
4250                "status": 400,
4251                    "error": {
4252                        "code": -2010,
4253                        "msg": "Account has insufficient balance for requested action.",
4254                    },
4255                    "rateLimits": [
4256                        {
4257                            "rateLimitType": "ORDERS",
4258                            "interval": "SECOND",
4259                            "intervalNum": 10,
4260                            "limit": 50,
4261                            "count": 13
4262                        },
4263                    ],
4264            });
4265            WebsocketHandler::on_message(&*ws_api, resp_json.to_string(), conn.clone()).await;
4266
4267            let join = timeout(Duration::from_secs(1), handle).await.unwrap();
4268            match join {
4269                Ok(Err(e)) => {
4270                    let msg = e.to_string();
4271                    assert!(
4272                        msg.contains("Server‐side response error (code -2010): Account has insufficient balance for requested action."),
4273                        "Expected error msg to contain server error, got: {msg}"
4274                    );
4275                }
4276                Ok(Ok(_)) => panic!("Expected error"),
4277                Err(_) => panic!("Task panicked"),
4278            }
4279        });
4280    }
4281
4282    #[test]
4283    fn ui_klines_request_timeout() {
4284        TOKIO_SHARED_RT.block_on(async {
4285            let (ws_api, _conn, mut rx) = setup().await;
4286            let client = MarketApiClient::new(ws_api.clone());
4287
4288            let handle = spawn(async move {
4289                let params = UiKlinesParams::builder(
4290                    "BNBUSDT".to_string(),
4291                    UiKlinesIntervalEnum::Interval1s,
4292                )
4293                .build()
4294                .unwrap();
4295                client.ui_klines(params).await
4296            });
4297
4298            let sent = timeout(Duration::from_secs(1), rx.recv())
4299                .await
4300                .expect("send should occur")
4301                .expect("channel closed");
4302            let Message::Text(text) = sent else {
4303                panic!("expected Message Text")
4304            };
4305
4306            let _: Value = serde_json::from_str(&text).unwrap();
4307
4308            let result = handle.await.expect("task completed");
4309            match result {
4310                Err(e) => {
4311                    if let Some(inner) = e.downcast_ref::<WebsocketError>() {
4312                        assert!(matches!(inner, WebsocketError::Timeout));
4313                    } else {
4314                        panic!("Unexpected error type: {:?}", e);
4315                    }
4316                }
4317                Ok(_) => panic!("Expected timeout error"),
4318            }
4319        });
4320    }
4321}