binance_sdk/copy_trading/rest_api/apis/
future_copy_trading_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::copy_trading::rest_api::models;
29
30const HAS_TIME_UNIT: bool = false;
31
32#[async_trait]
33pub trait FutureCopyTradingApi: Send + Sync {
34 async fn get_futures_lead_trader_status(
35 &self,
36 params: GetFuturesLeadTraderStatusParams,
37 ) -> anyhow::Result<RestApiResponse<models::GetFuturesLeadTraderStatusResponse>>;
38 async fn get_futures_lead_trading_symbol_whitelist(
39 &self,
40 params: GetFuturesLeadTradingSymbolWhitelistParams,
41 ) -> anyhow::Result<RestApiResponse<models::GetFuturesLeadTradingSymbolWhitelistResponse>>;
42}
43
44#[derive(Debug, Clone)]
45pub struct FutureCopyTradingApiClient {
46 configuration: ConfigurationRestApi,
47}
48
49impl FutureCopyTradingApiClient {
50 pub fn new(configuration: ConfigurationRestApi) -> Self {
51 Self { configuration }
52 }
53}
54
55#[derive(Clone, Debug, Builder, Default)]
60#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
61pub struct GetFuturesLeadTraderStatusParams {
62 #[builder(setter(into), default)]
67 pub recv_window: Option<i64>,
68}
69
70impl GetFuturesLeadTraderStatusParams {
71 #[must_use]
74 pub fn builder() -> GetFuturesLeadTraderStatusParamsBuilder {
75 GetFuturesLeadTraderStatusParamsBuilder::default()
76 }
77}
78#[derive(Clone, Debug, Builder, Default)]
83#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
84pub struct GetFuturesLeadTradingSymbolWhitelistParams {
85 #[builder(setter(into), default)]
90 pub recv_window: Option<i64>,
91}
92
93impl GetFuturesLeadTradingSymbolWhitelistParams {
94 #[must_use]
97 pub fn builder() -> GetFuturesLeadTradingSymbolWhitelistParamsBuilder {
98 GetFuturesLeadTradingSymbolWhitelistParamsBuilder::default()
99 }
100}
101
102#[async_trait]
103impl FutureCopyTradingApi for FutureCopyTradingApiClient {
104 async fn get_futures_lead_trader_status(
105 &self,
106 params: GetFuturesLeadTraderStatusParams,
107 ) -> anyhow::Result<RestApiResponse<models::GetFuturesLeadTraderStatusResponse>> {
108 let GetFuturesLeadTraderStatusParams { recv_window } = params;
109
110 let mut query_params = BTreeMap::new();
111 let body_params = BTreeMap::new();
112
113 if let Some(rw) = recv_window {
114 query_params.insert("recvWindow".to_string(), json!(rw));
115 }
116
117 send_request::<models::GetFuturesLeadTraderStatusResponse>(
118 &self.configuration,
119 "/sapi/v1/copyTrading/futures/userStatus",
120 reqwest::Method::GET,
121 query_params,
122 body_params,
123 if HAS_TIME_UNIT {
124 self.configuration.time_unit
125 } else {
126 None
127 },
128 true,
129 )
130 .await
131 }
132
133 async fn get_futures_lead_trading_symbol_whitelist(
134 &self,
135 params: GetFuturesLeadTradingSymbolWhitelistParams,
136 ) -> anyhow::Result<RestApiResponse<models::GetFuturesLeadTradingSymbolWhitelistResponse>> {
137 let GetFuturesLeadTradingSymbolWhitelistParams { recv_window } = params;
138
139 let mut query_params = BTreeMap::new();
140 let body_params = BTreeMap::new();
141
142 if let Some(rw) = recv_window {
143 query_params.insert("recvWindow".to_string(), json!(rw));
144 }
145
146 send_request::<models::GetFuturesLeadTradingSymbolWhitelistResponse>(
147 &self.configuration,
148 "/sapi/v1/copyTrading/futures/leadSymbol",
149 reqwest::Method::GET,
150 query_params,
151 body_params,
152 if HAS_TIME_UNIT {
153 self.configuration.time_unit
154 } else {
155 None
156 },
157 true,
158 )
159 .await
160 }
161}
162
163#[cfg(all(test, feature = "copy_trading"))]
164mod tests {
165 use super::*;
166 use crate::TOKIO_SHARED_RT;
167 use crate::{errors::ConnectorError, models::DataFuture, models::RestApiRateLimit};
168 use async_trait::async_trait;
169 use std::collections::HashMap;
170
171 struct DummyRestApiResponse<T> {
172 inner: Box<dyn FnOnce() -> DataFuture<Result<T, ConnectorError>> + Send + Sync>,
173 status: u16,
174 headers: HashMap<String, String>,
175 rate_limits: Option<Vec<RestApiRateLimit>>,
176 }
177
178 impl<T> From<DummyRestApiResponse<T>> for RestApiResponse<T> {
179 fn from(dummy: DummyRestApiResponse<T>) -> Self {
180 Self {
181 data_fn: dummy.inner,
182 status: dummy.status,
183 headers: dummy.headers,
184 rate_limits: dummy.rate_limits,
185 }
186 }
187 }
188
189 struct MockFutureCopyTradingApiClient {
190 force_error: bool,
191 }
192
193 #[async_trait]
194 impl FutureCopyTradingApi for MockFutureCopyTradingApiClient {
195 async fn get_futures_lead_trader_status(
196 &self,
197 _params: GetFuturesLeadTraderStatusParams,
198 ) -> anyhow::Result<RestApiResponse<models::GetFuturesLeadTraderStatusResponse>> {
199 if self.force_error {
200 return Err(ConnectorError::ConnectorClientError {
201 msg: "ResponseError".to_string(),
202 code: None,
203 }
204 .into());
205 }
206
207 let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"success","data":{"isLeadTrader":true,"time":1717382310843},"success":true}"#).unwrap();
208 let dummy_response: models::GetFuturesLeadTraderStatusResponse =
209 serde_json::from_value(resp_json.clone())
210 .expect("should parse into models::GetFuturesLeadTraderStatusResponse");
211
212 let dummy = DummyRestApiResponse {
213 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
214 status: 200,
215 headers: HashMap::new(),
216 rate_limits: None,
217 };
218
219 Ok(dummy.into())
220 }
221
222 async fn get_futures_lead_trading_symbol_whitelist(
223 &self,
224 _params: GetFuturesLeadTradingSymbolWhitelistParams,
225 ) -> anyhow::Result<RestApiResponse<models::GetFuturesLeadTradingSymbolWhitelistResponse>>
226 {
227 if self.force_error {
228 return Err(ConnectorError::ConnectorClientError {
229 msg: "ResponseError".to_string(),
230 code: None,
231 }
232 .into());
233 }
234
235 let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"success","data":[{"symbol":"BTCUSDT","baseAsset":"BTC","quoteAsset":"USDT"},{"symbol":"ETHUSDT","baseAsset":"ETH","quoteAsset":"USDT"}]}"#).unwrap();
236 let dummy_response: models::GetFuturesLeadTradingSymbolWhitelistResponse =
237 serde_json::from_value(resp_json.clone()).expect(
238 "should parse into models::GetFuturesLeadTradingSymbolWhitelistResponse",
239 );
240
241 let dummy = DummyRestApiResponse {
242 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
243 status: 200,
244 headers: HashMap::new(),
245 rate_limits: None,
246 };
247
248 Ok(dummy.into())
249 }
250 }
251
252 #[test]
253 fn get_futures_lead_trader_status_required_params_success() {
254 TOKIO_SHARED_RT.block_on(async {
255 let client = MockFutureCopyTradingApiClient { force_error: false };
256
257 let params = GetFuturesLeadTraderStatusParams::builder().build().unwrap();
258
259 let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"success","data":{"isLeadTrader":true,"time":1717382310843},"success":true}"#).unwrap();
260 let expected_response : models::GetFuturesLeadTraderStatusResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::GetFuturesLeadTraderStatusResponse");
261
262 let resp = client.get_futures_lead_trader_status(params).await.expect("Expected a response");
263 let data_future = resp.data();
264 let actual_response = data_future.await.unwrap();
265 assert_eq!(actual_response, expected_response);
266 });
267 }
268
269 #[test]
270 fn get_futures_lead_trader_status_optional_params_success() {
271 TOKIO_SHARED_RT.block_on(async {
272 let client = MockFutureCopyTradingApiClient { force_error: false };
273
274 let params = GetFuturesLeadTraderStatusParams::builder().recv_window(5000).build().unwrap();
275
276 let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"success","data":{"isLeadTrader":true,"time":1717382310843},"success":true}"#).unwrap();
277 let expected_response : models::GetFuturesLeadTraderStatusResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::GetFuturesLeadTraderStatusResponse");
278
279 let resp = client.get_futures_lead_trader_status(params).await.expect("Expected a response");
280 let data_future = resp.data();
281 let actual_response = data_future.await.unwrap();
282 assert_eq!(actual_response, expected_response);
283 });
284 }
285
286 #[test]
287 fn get_futures_lead_trader_status_response_error() {
288 TOKIO_SHARED_RT.block_on(async {
289 let client = MockFutureCopyTradingApiClient { force_error: true };
290
291 let params = GetFuturesLeadTraderStatusParams::builder().build().unwrap();
292
293 match client.get_futures_lead_trader_status(params).await {
294 Ok(_) => panic!("Expected an error"),
295 Err(err) => {
296 assert_eq!(err.to_string(), "Connector client error: ResponseError");
297 }
298 }
299 });
300 }
301
302 #[test]
303 fn get_futures_lead_trading_symbol_whitelist_required_params_success() {
304 TOKIO_SHARED_RT.block_on(async {
305 let client = MockFutureCopyTradingApiClient { force_error: false };
306
307 let params = GetFuturesLeadTradingSymbolWhitelistParams::builder().build().unwrap();
308
309 let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"success","data":[{"symbol":"BTCUSDT","baseAsset":"BTC","quoteAsset":"USDT"},{"symbol":"ETHUSDT","baseAsset":"ETH","quoteAsset":"USDT"}]}"#).unwrap();
310 let expected_response : models::GetFuturesLeadTradingSymbolWhitelistResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::GetFuturesLeadTradingSymbolWhitelistResponse");
311
312 let resp = client.get_futures_lead_trading_symbol_whitelist(params).await.expect("Expected a response");
313 let data_future = resp.data();
314 let actual_response = data_future.await.unwrap();
315 assert_eq!(actual_response, expected_response);
316 });
317 }
318
319 #[test]
320 fn get_futures_lead_trading_symbol_whitelist_optional_params_success() {
321 TOKIO_SHARED_RT.block_on(async {
322 let client = MockFutureCopyTradingApiClient { force_error: false };
323
324 let params = GetFuturesLeadTradingSymbolWhitelistParams::builder().recv_window(5000).build().unwrap();
325
326 let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"success","data":[{"symbol":"BTCUSDT","baseAsset":"BTC","quoteAsset":"USDT"},{"symbol":"ETHUSDT","baseAsset":"ETH","quoteAsset":"USDT"}]}"#).unwrap();
327 let expected_response : models::GetFuturesLeadTradingSymbolWhitelistResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::GetFuturesLeadTradingSymbolWhitelistResponse");
328
329 let resp = client.get_futures_lead_trading_symbol_whitelist(params).await.expect("Expected a response");
330 let data_future = resp.data();
331 let actual_response = data_future.await.unwrap();
332 assert_eq!(actual_response, expected_response);
333 });
334 }
335
336 #[test]
337 fn get_futures_lead_trading_symbol_whitelist_response_error() {
338 TOKIO_SHARED_RT.block_on(async {
339 let client = MockFutureCopyTradingApiClient { force_error: true };
340
341 let params = GetFuturesLeadTradingSymbolWhitelistParams::builder()
342 .build()
343 .unwrap();
344
345 match client
346 .get_futures_lead_trading_symbol_whitelist(params)
347 .await
348 {
349 Ok(_) => panic!("Expected an error"),
350 Err(err) => {
351 assert_eq!(err.to_string(), "Connector client error: ResponseError");
352 }
353 }
354 });
355 }
356}