binance_sdk/convert/rest_api/apis/
market_data_api.rs1#![allow(unused_imports)]
15use async_trait::async_trait;
16use derive_builder::Builder;
17use reqwest;
18use rust_decimal::prelude::*;
19use serde::{Deserialize, Serialize};
20use serde_json::{Value, json};
21use std::collections::BTreeMap;
22
23use crate::common::{
24 config::ConfigurationRestApi,
25 models::{ParamBuildError, RestApiResponse},
26 utils::send_request,
27};
28use crate::convert::rest_api::models;
29
30const HAS_TIME_UNIT: bool = false;
31
32#[async_trait]
33pub trait MarketDataApi: Send + Sync {
34 async fn list_all_convert_pairs(
35 &self,
36 params: ListAllConvertPairsParams,
37 ) -> anyhow::Result<RestApiResponse<Vec<models::ListAllConvertPairsResponseInner>>>;
38 async fn query_order_quantity_precision_per_asset(
39 &self,
40 params: QueryOrderQuantityPrecisionPerAssetParams,
41 ) -> anyhow::Result<
42 RestApiResponse<Vec<models::QueryOrderQuantityPrecisionPerAssetResponseInner>>,
43 >;
44}
45
46#[derive(Debug, Clone)]
47pub struct MarketDataApiClient {
48 configuration: ConfigurationRestApi,
49}
50
51impl MarketDataApiClient {
52 pub fn new(configuration: ConfigurationRestApi) -> Self {
53 Self { configuration }
54 }
55}
56
57#[derive(Clone, Debug, Builder, Default)]
62#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
63pub struct ListAllConvertPairsParams {
64 #[builder(setter(into), default)]
68 pub from_asset: Option<String>,
69 #[builder(setter(into), default)]
73 pub to_asset: Option<String>,
74}
75
76impl ListAllConvertPairsParams {
77 #[must_use]
80 pub fn builder() -> ListAllConvertPairsParamsBuilder {
81 ListAllConvertPairsParamsBuilder::default()
82 }
83}
84#[derive(Clone, Debug, Builder, Default)]
89#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
90pub struct QueryOrderQuantityPrecisionPerAssetParams {
91 #[builder(setter(into), default)]
95 pub recv_window: Option<i64>,
96}
97
98impl QueryOrderQuantityPrecisionPerAssetParams {
99 #[must_use]
102 pub fn builder() -> QueryOrderQuantityPrecisionPerAssetParamsBuilder {
103 QueryOrderQuantityPrecisionPerAssetParamsBuilder::default()
104 }
105}
106
107#[async_trait]
108impl MarketDataApi for MarketDataApiClient {
109 async fn list_all_convert_pairs(
110 &self,
111 params: ListAllConvertPairsParams,
112 ) -> anyhow::Result<RestApiResponse<Vec<models::ListAllConvertPairsResponseInner>>> {
113 let ListAllConvertPairsParams {
114 from_asset,
115 to_asset,
116 } = params;
117
118 let mut query_params = BTreeMap::new();
119 let body_params = BTreeMap::new();
120
121 if let Some(rw) = from_asset {
122 query_params.insert("fromAsset".to_string(), json!(rw));
123 }
124
125 if let Some(rw) = to_asset {
126 query_params.insert("toAsset".to_string(), json!(rw));
127 }
128
129 send_request::<Vec<models::ListAllConvertPairsResponseInner>>(
130 &self.configuration,
131 "/sapi/v1/convert/exchangeInfo",
132 reqwest::Method::GET,
133 query_params,
134 body_params,
135 if HAS_TIME_UNIT {
136 self.configuration.time_unit
137 } else {
138 None
139 },
140 false,
141 )
142 .await
143 }
144
145 async fn query_order_quantity_precision_per_asset(
146 &self,
147 params: QueryOrderQuantityPrecisionPerAssetParams,
148 ) -> anyhow::Result<
149 RestApiResponse<Vec<models::QueryOrderQuantityPrecisionPerAssetResponseInner>>,
150 > {
151 let QueryOrderQuantityPrecisionPerAssetParams { recv_window } = params;
152
153 let mut query_params = BTreeMap::new();
154 let body_params = BTreeMap::new();
155
156 if let Some(rw) = recv_window {
157 query_params.insert("recvWindow".to_string(), json!(rw));
158 }
159
160 send_request::<Vec<models::QueryOrderQuantityPrecisionPerAssetResponseInner>>(
161 &self.configuration,
162 "/sapi/v1/convert/assetInfo",
163 reqwest::Method::GET,
164 query_params,
165 body_params,
166 if HAS_TIME_UNIT {
167 self.configuration.time_unit
168 } else {
169 None
170 },
171 true,
172 )
173 .await
174 }
175}
176
177#[cfg(all(test, feature = "convert"))]
178mod tests {
179 use super::*;
180 use crate::TOKIO_SHARED_RT;
181 use crate::{errors::ConnectorError, models::DataFuture, models::RestApiRateLimit};
182 use async_trait::async_trait;
183 use std::collections::HashMap;
184
185 struct DummyRestApiResponse<T> {
186 inner: Box<dyn FnOnce() -> DataFuture<Result<T, ConnectorError>> + Send + Sync>,
187 status: u16,
188 headers: HashMap<String, String>,
189 rate_limits: Option<Vec<RestApiRateLimit>>,
190 }
191
192 impl<T> From<DummyRestApiResponse<T>> for RestApiResponse<T> {
193 fn from(dummy: DummyRestApiResponse<T>) -> Self {
194 Self {
195 data_fn: dummy.inner,
196 status: dummy.status,
197 headers: dummy.headers,
198 rate_limits: dummy.rate_limits,
199 }
200 }
201 }
202
203 struct MockMarketDataApiClient {
204 force_error: bool,
205 }
206
207 #[async_trait]
208 impl MarketDataApi for MockMarketDataApiClient {
209 async fn list_all_convert_pairs(
210 &self,
211 _params: ListAllConvertPairsParams,
212 ) -> anyhow::Result<RestApiResponse<Vec<models::ListAllConvertPairsResponseInner>>>
213 {
214 if self.force_error {
215 return Err(ConnectorError::ConnectorClientError {
216 msg: "ResponseError".to_string(),
217 code: None,
218 }
219 .into());
220 }
221
222 let resp_json: Value = serde_json::from_str(r#"[{"fromAsset":"BTC","toAsset":"USDT","fromAssetMinAmount":"0.0004","fromAssetMaxAmount":"50","toAssetMinAmount":"20","toAssetMaxAmount":"9E+24"}]"#).unwrap();
223 let dummy_response: Vec<models::ListAllConvertPairsResponseInner> =
224 serde_json::from_value(resp_json.clone())
225 .expect("should parse into Vec<models::ListAllConvertPairsResponseInner>");
226
227 let dummy = DummyRestApiResponse {
228 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
229 status: 200,
230 headers: HashMap::new(),
231 rate_limits: None,
232 };
233
234 Ok(dummy.into())
235 }
236
237 async fn query_order_quantity_precision_per_asset(
238 &self,
239 _params: QueryOrderQuantityPrecisionPerAssetParams,
240 ) -> anyhow::Result<
241 RestApiResponse<Vec<models::QueryOrderQuantityPrecisionPerAssetResponseInner>>,
242 > {
243 if self.force_error {
244 return Err(ConnectorError::ConnectorClientError {
245 msg: "ResponseError".to_string(),
246 code: None,
247 }
248 .into());
249 }
250
251 let resp_json: Value = serde_json::from_str(
252 r#"[{"asset":"BTC","fraction":8},{"asset":"SHIB","fraction":2}]"#,
253 )
254 .unwrap();
255 let dummy_response : Vec<models::QueryOrderQuantityPrecisionPerAssetResponseInner> = serde_json::from_value(resp_json.clone()).expect("should parse into Vec<models::QueryOrderQuantityPrecisionPerAssetResponseInner>");
256
257 let dummy = DummyRestApiResponse {
258 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
259 status: 200,
260 headers: HashMap::new(),
261 rate_limits: None,
262 };
263
264 Ok(dummy.into())
265 }
266 }
267
268 #[test]
269 fn list_all_convert_pairs_required_params_success() {
270 TOKIO_SHARED_RT.block_on(async {
271 let client = MockMarketDataApiClient { force_error: false };
272
273 let params = ListAllConvertPairsParams::builder().build().unwrap();
274
275 let resp_json: Value = serde_json::from_str(r#"[{"fromAsset":"BTC","toAsset":"USDT","fromAssetMinAmount":"0.0004","fromAssetMaxAmount":"50","toAssetMinAmount":"20","toAssetMaxAmount":"9E+24"}]"#).unwrap();
276 let expected_response : Vec<models::ListAllConvertPairsResponseInner> = serde_json::from_value(resp_json.clone()).expect("should parse into Vec<models::ListAllConvertPairsResponseInner>");
277
278 let resp = client.list_all_convert_pairs(params).await.expect("Expected a response");
279 let data_future = resp.data();
280 let actual_response = data_future.await.unwrap();
281 assert_eq!(actual_response, expected_response);
282 });
283 }
284
285 #[test]
286 fn list_all_convert_pairs_optional_params_success() {
287 TOKIO_SHARED_RT.block_on(async {
288 let client = MockMarketDataApiClient { force_error: false };
289
290 let params = ListAllConvertPairsParams::builder().from_asset("from_asset_example".to_string()).to_asset("to_asset_example".to_string()).build().unwrap();
291
292 let resp_json: Value = serde_json::from_str(r#"[{"fromAsset":"BTC","toAsset":"USDT","fromAssetMinAmount":"0.0004","fromAssetMaxAmount":"50","toAssetMinAmount":"20","toAssetMaxAmount":"9E+24"}]"#).unwrap();
293 let expected_response : Vec<models::ListAllConvertPairsResponseInner> = serde_json::from_value(resp_json.clone()).expect("should parse into Vec<models::ListAllConvertPairsResponseInner>");
294
295 let resp = client.list_all_convert_pairs(params).await.expect("Expected a response");
296 let data_future = resp.data();
297 let actual_response = data_future.await.unwrap();
298 assert_eq!(actual_response, expected_response);
299 });
300 }
301
302 #[test]
303 fn list_all_convert_pairs_response_error() {
304 TOKIO_SHARED_RT.block_on(async {
305 let client = MockMarketDataApiClient { force_error: true };
306
307 let params = ListAllConvertPairsParams::builder().build().unwrap();
308
309 match client.list_all_convert_pairs(params).await {
310 Ok(_) => panic!("Expected an error"),
311 Err(err) => {
312 assert_eq!(err.to_string(), "Connector client error: ResponseError");
313 }
314 }
315 });
316 }
317
318 #[test]
319 fn query_order_quantity_precision_per_asset_required_params_success() {
320 TOKIO_SHARED_RT.block_on(async {
321 let client = MockMarketDataApiClient { force_error: false };
322
323 let params = QueryOrderQuantityPrecisionPerAssetParams::builder().build().unwrap();
324
325 let resp_json: Value = serde_json::from_str(r#"[{"asset":"BTC","fraction":8},{"asset":"SHIB","fraction":2}]"#).unwrap();
326 let expected_response : Vec<models::QueryOrderQuantityPrecisionPerAssetResponseInner> = serde_json::from_value(resp_json.clone()).expect("should parse into Vec<models::QueryOrderQuantityPrecisionPerAssetResponseInner>");
327
328 let resp = client.query_order_quantity_precision_per_asset(params).await.expect("Expected a response");
329 let data_future = resp.data();
330 let actual_response = data_future.await.unwrap();
331 assert_eq!(actual_response, expected_response);
332 });
333 }
334
335 #[test]
336 fn query_order_quantity_precision_per_asset_optional_params_success() {
337 TOKIO_SHARED_RT.block_on(async {
338 let client = MockMarketDataApiClient { force_error: false };
339
340 let params = QueryOrderQuantityPrecisionPerAssetParams::builder().recv_window(5000).build().unwrap();
341
342 let resp_json: Value = serde_json::from_str(r#"[{"asset":"BTC","fraction":8},{"asset":"SHIB","fraction":2}]"#).unwrap();
343 let expected_response : Vec<models::QueryOrderQuantityPrecisionPerAssetResponseInner> = serde_json::from_value(resp_json.clone()).expect("should parse into Vec<models::QueryOrderQuantityPrecisionPerAssetResponseInner>");
344
345 let resp = client.query_order_quantity_precision_per_asset(params).await.expect("Expected a response");
346 let data_future = resp.data();
347 let actual_response = data_future.await.unwrap();
348 assert_eq!(actual_response, expected_response);
349 });
350 }
351
352 #[test]
353 fn query_order_quantity_precision_per_asset_response_error() {
354 TOKIO_SHARED_RT.block_on(async {
355 let client = MockMarketDataApiClient { force_error: true };
356
357 let params = QueryOrderQuantityPrecisionPerAssetParams::builder()
358 .build()
359 .unwrap();
360
361 match client
362 .query_order_quantity_precision_per_asset(params)
363 .await
364 {
365 Ok(_) => panic!("Expected an error"),
366 Err(err) => {
367 assert_eq!(err.to_string(), "Connector client error: ResponseError");
368 }
369 }
370 });
371 }
372}