binance_sdk/c2c/rest_api/apis/
c2_c_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::c2c::rest_api::models;
24use crate::common::{
25 config::ConfigurationRestApi,
26 models::{ParamBuildError, RestApiResponse},
27 utils::send_request,
28};
29
30const HAS_TIME_UNIT: bool = false;
31
32#[async_trait]
33pub trait C2CApi: Send + Sync {
34 async fn get_c2_c_trade_history(
35 &self,
36 params: GetC2CTradeHistoryParams,
37 ) -> anyhow::Result<RestApiResponse<models::GetC2CTradeHistoryResponse>>;
38}
39
40#[derive(Debug, Clone)]
41pub struct C2CApiClient {
42 configuration: ConfigurationRestApi,
43}
44
45impl C2CApiClient {
46 pub fn new(configuration: ConfigurationRestApi) -> Self {
47 Self { configuration }
48 }
49}
50
51#[derive(Clone, Debug, Builder, Default)]
56#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
57pub struct GetC2CTradeHistoryParams {
58 #[builder(setter(into), default)]
62 pub trade_type: Option<String>,
63 #[builder(setter(into), default)]
68 pub start_timestamp: Option<i64>,
69 #[builder(setter(into), default)]
74 pub end_timestamp: Option<i64>,
75 #[builder(setter(into), default)]
79 pub page: Option<i64>,
80 #[builder(setter(into), default)]
84 pub rows: Option<i64>,
85 #[builder(setter(into), default)]
90 pub recv_window: Option<i64>,
91}
92
93impl GetC2CTradeHistoryParams {
94 #[must_use]
97 pub fn builder() -> GetC2CTradeHistoryParamsBuilder {
98 GetC2CTradeHistoryParamsBuilder::default()
99 }
100}
101
102#[async_trait]
103impl C2CApi for C2CApiClient {
104 async fn get_c2_c_trade_history(
105 &self,
106 params: GetC2CTradeHistoryParams,
107 ) -> anyhow::Result<RestApiResponse<models::GetC2CTradeHistoryResponse>> {
108 let GetC2CTradeHistoryParams {
109 trade_type,
110 start_timestamp,
111 end_timestamp,
112 page,
113 rows,
114 recv_window,
115 } = params;
116
117 let mut query_params = BTreeMap::new();
118 let body_params = BTreeMap::new();
119
120 if let Some(rw) = trade_type {
121 query_params.insert("tradeType".to_string(), json!(rw));
122 }
123
124 if let Some(rw) = start_timestamp {
125 query_params.insert("startTimestamp".to_string(), json!(rw));
126 }
127
128 if let Some(rw) = end_timestamp {
129 query_params.insert("endTimestamp".to_string(), json!(rw));
130 }
131
132 if let Some(rw) = page {
133 query_params.insert("page".to_string(), json!(rw));
134 }
135
136 if let Some(rw) = rows {
137 query_params.insert("rows".to_string(), json!(rw));
138 }
139
140 if let Some(rw) = recv_window {
141 query_params.insert("recvWindow".to_string(), json!(rw));
142 }
143
144 send_request::<models::GetC2CTradeHistoryResponse>(
145 &self.configuration,
146 "/sapi/v1/c2c/orderMatch/listUserOrderHistory",
147 reqwest::Method::GET,
148 query_params,
149 body_params,
150 if HAS_TIME_UNIT {
151 self.configuration.time_unit
152 } else {
153 None
154 },
155 true,
156 )
157 .await
158 }
159}
160
161#[cfg(all(test, feature = "c2c"))]
162mod tests {
163 use super::*;
164 use crate::TOKIO_SHARED_RT;
165 use crate::{errors::ConnectorError, models::DataFuture, models::RestApiRateLimit};
166 use async_trait::async_trait;
167 use std::collections::HashMap;
168
169 struct DummyRestApiResponse<T> {
170 inner: Box<dyn FnOnce() -> DataFuture<Result<T, ConnectorError>> + Send + Sync>,
171 status: u16,
172 headers: HashMap<String, String>,
173 rate_limits: Option<Vec<RestApiRateLimit>>,
174 }
175
176 impl<T> From<DummyRestApiResponse<T>> for RestApiResponse<T> {
177 fn from(dummy: DummyRestApiResponse<T>) -> Self {
178 Self {
179 data_fn: dummy.inner,
180 status: dummy.status,
181 headers: dummy.headers,
182 rate_limits: dummy.rate_limits,
183 }
184 }
185 }
186
187 struct MockC2CApiClient {
188 force_error: bool,
189 }
190
191 #[async_trait]
192 impl C2CApi for MockC2CApiClient {
193 async fn get_c2_c_trade_history(
194 &self,
195 _params: GetC2CTradeHistoryParams,
196 ) -> anyhow::Result<RestApiResponse<models::GetC2CTradeHistoryResponse>> {
197 if self.force_error {
198 return Err(ConnectorError::ConnectorClientError {
199 msg: "ResponseError".to_string(),
200 code: None,
201 }
202 .into());
203 }
204
205 let resp_json: Value = serde_json::from_str(r#"{"orderNumber":"20219644646554779648","advNo":"11218246497340923904","tradeType":"SELL","asset":"USDT","fiat":"CNY","fiatSymbol":"¥","amount":"343.40000000","totalPrice":"2500.00000000","unitPrice":"7.28","orderStatus":"COMPLETED","createTime":1722997599534,"commission":"0","takerCommissionRate":"0","takerCommission":"0","takerAmount":"343.4","counterPartNickName":"aaa-***","payMethodName":"BANK","additionalKycVerify":0}"#).unwrap();
206 let dummy_response: models::GetC2CTradeHistoryResponse =
207 serde_json::from_value(resp_json.clone())
208 .expect("should parse into models::GetC2CTradeHistoryResponse");
209
210 let dummy = DummyRestApiResponse {
211 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
212 status: 200,
213 headers: HashMap::new(),
214 rate_limits: None,
215 };
216
217 Ok(dummy.into())
218 }
219 }
220
221 #[test]
222 fn get_c2_c_trade_history_required_params_success() {
223 TOKIO_SHARED_RT.block_on(async {
224 let client = MockC2CApiClient { force_error: false };
225
226 let params = GetC2CTradeHistoryParams::builder().build().unwrap();
227
228 let resp_json: Value = serde_json::from_str(r#"{"orderNumber":"20219644646554779648","advNo":"11218246497340923904","tradeType":"SELL","asset":"USDT","fiat":"CNY","fiatSymbol":"¥","amount":"343.40000000","totalPrice":"2500.00000000","unitPrice":"7.28","orderStatus":"COMPLETED","createTime":1722997599534,"commission":"0","takerCommissionRate":"0","takerCommission":"0","takerAmount":"343.4","counterPartNickName":"aaa-***","payMethodName":"BANK","additionalKycVerify":0}"#).unwrap();
229 let expected_response : models::GetC2CTradeHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::GetC2CTradeHistoryResponse");
230
231 let resp = client.get_c2_c_trade_history(params).await.expect("Expected a response");
232 let data_future = resp.data();
233 let actual_response = data_future.await.unwrap();
234 assert_eq!(actual_response, expected_response);
235 });
236 }
237
238 #[test]
239 fn get_c2_c_trade_history_optional_params_success() {
240 TOKIO_SHARED_RT.block_on(async {
241 let client = MockC2CApiClient { force_error: false };
242
243 let params = GetC2CTradeHistoryParams::builder().trade_type("trade_type_example".to_string()).start_timestamp(789).end_timestamp(789).page(1).rows(100).recv_window(5000).build().unwrap();
244
245 let resp_json: Value = serde_json::from_str(r#"{"orderNumber":"20219644646554779648","advNo":"11218246497340923904","tradeType":"SELL","asset":"USDT","fiat":"CNY","fiatSymbol":"¥","amount":"343.40000000","totalPrice":"2500.00000000","unitPrice":"7.28","orderStatus":"COMPLETED","createTime":1722997599534,"commission":"0","takerCommissionRate":"0","takerCommission":"0","takerAmount":"343.4","counterPartNickName":"aaa-***","payMethodName":"BANK","additionalKycVerify":0}"#).unwrap();
246 let expected_response : models::GetC2CTradeHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::GetC2CTradeHistoryResponse");
247
248 let resp = client.get_c2_c_trade_history(params).await.expect("Expected a response");
249 let data_future = resp.data();
250 let actual_response = data_future.await.unwrap();
251 assert_eq!(actual_response, expected_response);
252 });
253 }
254
255 #[test]
256 fn get_c2_c_trade_history_response_error() {
257 TOKIO_SHARED_RT.block_on(async {
258 let client = MockC2CApiClient { force_error: true };
259
260 let params = GetC2CTradeHistoryParams::builder().build().unwrap();
261
262 match client.get_c2_c_trade_history(params).await {
263 Ok(_) => panic!("Expected an error"),
264 Err(err) => {
265 assert_eq!(err.to_string(), "Connector client error: ResponseError");
266 }
267 }
268 });
269 }
270}