binance_sdk/wallet/rest_api/apis/
others_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::wallet::rest_api::models;
29
30const HAS_TIME_UNIT: bool = false;
31
32#[async_trait]
33pub trait OthersApi: Send + Sync {
34 async fn get_symbols_delist_schedule_for_spot(
35 &self,
36 params: GetSymbolsDelistScheduleForSpotParams,
37 ) -> anyhow::Result<RestApiResponse<Vec<models::GetSymbolsDelistScheduleForSpotResponseInner>>>;
38 async fn system_status(&self) -> anyhow::Result<RestApiResponse<models::SystemStatusResponse>>;
39}
40
41#[derive(Debug, Clone)]
42pub struct OthersApiClient {
43 configuration: ConfigurationRestApi,
44}
45
46impl OthersApiClient {
47 pub fn new(configuration: ConfigurationRestApi) -> Self {
48 Self { configuration }
49 }
50}
51
52#[derive(Clone, Debug, Builder, Default)]
57#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
58pub struct GetSymbolsDelistScheduleForSpotParams {
59 #[builder(setter(into), default)]
64 pub recv_window: Option<i64>,
65}
66
67impl GetSymbolsDelistScheduleForSpotParams {
68 #[must_use]
71 pub fn builder() -> GetSymbolsDelistScheduleForSpotParamsBuilder {
72 GetSymbolsDelistScheduleForSpotParamsBuilder::default()
73 }
74}
75
76#[async_trait]
77impl OthersApi for OthersApiClient {
78 async fn get_symbols_delist_schedule_for_spot(
79 &self,
80 params: GetSymbolsDelistScheduleForSpotParams,
81 ) -> anyhow::Result<RestApiResponse<Vec<models::GetSymbolsDelistScheduleForSpotResponseInner>>>
82 {
83 let GetSymbolsDelistScheduleForSpotParams { recv_window } = params;
84
85 let mut query_params = BTreeMap::new();
86 let body_params = BTreeMap::new();
87
88 if let Some(rw) = recv_window {
89 query_params.insert("recvWindow".to_string(), json!(rw));
90 }
91
92 send_request::<Vec<models::GetSymbolsDelistScheduleForSpotResponseInner>>(
93 &self.configuration,
94 "/sapi/v1/spot/delist-schedule",
95 reqwest::Method::GET,
96 query_params,
97 body_params,
98 if HAS_TIME_UNIT {
99 self.configuration.time_unit
100 } else {
101 None
102 },
103 false,
104 )
105 .await
106 }
107
108 async fn system_status(&self) -> anyhow::Result<RestApiResponse<models::SystemStatusResponse>> {
109 let query_params = BTreeMap::new();
110 let body_params = BTreeMap::new();
111
112 send_request::<models::SystemStatusResponse>(
113 &self.configuration,
114 "/sapi/v1/system/status",
115 reqwest::Method::GET,
116 query_params,
117 body_params,
118 if HAS_TIME_UNIT {
119 self.configuration.time_unit
120 } else {
121 None
122 },
123 false,
124 )
125 .await
126 }
127}
128
129#[cfg(all(test, feature = "wallet"))]
130mod tests {
131 use super::*;
132 use crate::TOKIO_SHARED_RT;
133 use crate::{errors::ConnectorError, models::DataFuture, models::RestApiRateLimit};
134 use async_trait::async_trait;
135 use std::collections::HashMap;
136
137 struct DummyRestApiResponse<T> {
138 inner: Box<dyn FnOnce() -> DataFuture<Result<T, ConnectorError>> + Send + Sync>,
139 status: u16,
140 headers: HashMap<String, String>,
141 rate_limits: Option<Vec<RestApiRateLimit>>,
142 }
143
144 impl<T> From<DummyRestApiResponse<T>> for RestApiResponse<T> {
145 fn from(dummy: DummyRestApiResponse<T>) -> Self {
146 Self {
147 data_fn: dummy.inner,
148 status: dummy.status,
149 headers: dummy.headers,
150 rate_limits: dummy.rate_limits,
151 }
152 }
153 }
154
155 struct MockOthersApiClient {
156 force_error: bool,
157 }
158
159 #[async_trait]
160 impl OthersApi for MockOthersApiClient {
161 async fn get_symbols_delist_schedule_for_spot(
162 &self,
163 _params: GetSymbolsDelistScheduleForSpotParams,
164 ) -> anyhow::Result<
165 RestApiResponse<Vec<models::GetSymbolsDelistScheduleForSpotResponseInner>>,
166 > {
167 if self.force_error {
168 return Err(ConnectorError::ConnectorClientError {
169 msg: "ResponseError".to_string(),
170 code: None,
171 }
172 .into());
173 }
174
175 let resp_json: Value = serde_json::from_str(r#"[{"delistTime":1686161202000,"symbols":["ADAUSDT","BNBUSDT"]},{"delistTime":1686222232000,"symbols":["ETHUSDT"]}]"#).unwrap();
176 let dummy_response: Vec<models::GetSymbolsDelistScheduleForSpotResponseInner> =
177 serde_json::from_value(resp_json.clone()).expect(
178 "should parse into Vec<models::GetSymbolsDelistScheduleForSpotResponseInner>",
179 );
180
181 let dummy = DummyRestApiResponse {
182 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
183 status: 200,
184 headers: HashMap::new(),
185 rate_limits: None,
186 };
187
188 Ok(dummy.into())
189 }
190
191 async fn system_status(
192 &self,
193 ) -> anyhow::Result<RestApiResponse<models::SystemStatusResponse>> {
194 if self.force_error {
195 return Err(ConnectorError::ConnectorClientError {
196 msg: "ResponseError".to_string(),
197 code: None,
198 }
199 .into());
200 }
201
202 let resp_json: Value = serde_json::from_str(r#"{"status":0,"msg":"normal"}"#).unwrap();
203 let dummy_response: models::SystemStatusResponse =
204 serde_json::from_value(resp_json.clone())
205 .expect("should parse into models::SystemStatusResponse");
206
207 let dummy = DummyRestApiResponse {
208 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
209 status: 200,
210 headers: HashMap::new(),
211 rate_limits: None,
212 };
213
214 Ok(dummy.into())
215 }
216 }
217
218 #[test]
219 fn get_symbols_delist_schedule_for_spot_required_params_success() {
220 TOKIO_SHARED_RT.block_on(async {
221 let client = MockOthersApiClient { force_error: false };
222
223 let params = GetSymbolsDelistScheduleForSpotParams::builder().build().unwrap();
224
225 let resp_json: Value = serde_json::from_str(r#"[{"delistTime":1686161202000,"symbols":["ADAUSDT","BNBUSDT"]},{"delistTime":1686222232000,"symbols":["ETHUSDT"]}]"#).unwrap();
226 let expected_response : Vec<models::GetSymbolsDelistScheduleForSpotResponseInner> = serde_json::from_value(resp_json.clone()).expect("should parse into Vec<models::GetSymbolsDelistScheduleForSpotResponseInner>");
227
228 let resp = client.get_symbols_delist_schedule_for_spot(params).await.expect("Expected a response");
229 let data_future = resp.data();
230 let actual_response = data_future.await.unwrap();
231 assert_eq!(actual_response, expected_response);
232 });
233 }
234
235 #[test]
236 fn get_symbols_delist_schedule_for_spot_optional_params_success() {
237 TOKIO_SHARED_RT.block_on(async {
238 let client = MockOthersApiClient { force_error: false };
239
240 let params = GetSymbolsDelistScheduleForSpotParams::builder().recv_window(5000).build().unwrap();
241
242 let resp_json: Value = serde_json::from_str(r#"[{"delistTime":1686161202000,"symbols":["ADAUSDT","BNBUSDT"]},{"delistTime":1686222232000,"symbols":["ETHUSDT"]}]"#).unwrap();
243 let expected_response : Vec<models::GetSymbolsDelistScheduleForSpotResponseInner> = serde_json::from_value(resp_json.clone()).expect("should parse into Vec<models::GetSymbolsDelistScheduleForSpotResponseInner>");
244
245 let resp = client.get_symbols_delist_schedule_for_spot(params).await.expect("Expected a response");
246 let data_future = resp.data();
247 let actual_response = data_future.await.unwrap();
248 assert_eq!(actual_response, expected_response);
249 });
250 }
251
252 #[test]
253 fn get_symbols_delist_schedule_for_spot_response_error() {
254 TOKIO_SHARED_RT.block_on(async {
255 let client = MockOthersApiClient { force_error: true };
256
257 let params = GetSymbolsDelistScheduleForSpotParams::builder()
258 .build()
259 .unwrap();
260
261 match client.get_symbols_delist_schedule_for_spot(params).await {
262 Ok(_) => panic!("Expected an error"),
263 Err(err) => {
264 assert_eq!(err.to_string(), "Connector client error: ResponseError");
265 }
266 }
267 });
268 }
269
270 #[test]
271 fn system_status_required_params_success() {
272 TOKIO_SHARED_RT.block_on(async {
273 let client = MockOthersApiClient { force_error: false };
274
275 let resp_json: Value = serde_json::from_str(r#"{"status":0,"msg":"normal"}"#).unwrap();
276 let expected_response: models::SystemStatusResponse =
277 serde_json::from_value(resp_json.clone())
278 .expect("should parse into models::SystemStatusResponse");
279
280 let resp = client.system_status().await.expect("Expected a response");
281 let data_future = resp.data();
282 let actual_response = data_future.await.unwrap();
283 assert_eq!(actual_response, expected_response);
284 });
285 }
286
287 #[test]
288 fn system_status_optional_params_success() {
289 TOKIO_SHARED_RT.block_on(async {
290 let client = MockOthersApiClient { force_error: false };
291
292 let resp_json: Value = serde_json::from_str(r#"{"status":0,"msg":"normal"}"#).unwrap();
293 let expected_response: models::SystemStatusResponse =
294 serde_json::from_value(resp_json.clone())
295 .expect("should parse into models::SystemStatusResponse");
296
297 let resp = client.system_status().await.expect("Expected a response");
298 let data_future = resp.data();
299 let actual_response = data_future.await.unwrap();
300 assert_eq!(actual_response, expected_response);
301 });
302 }
303
304 #[test]
305 fn system_status_response_error() {
306 TOKIO_SHARED_RT.block_on(async {
307 let client = MockOthersApiClient { force_error: true };
308
309 match client.system_status().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}