1use futures_util::Future;
2use serde::Deserialize;
3
4use super::super::{Client, HttpError, HttpVerb};
5
6static FEE_RESOURCE: &str = "account/fee-rates";
7static WITHDRAW_FEE_RESOURCE: &str = "withdraw-fee";
8
9#[derive(Clone, Debug, Deserialize)]
10pub struct Fees {
11 pub volume: f64,
12 #[serde(rename = "makerRate")]
13 pub maker_rate: f64,
14 #[serde(rename = "nprRate")]
15 pub npr_rate: f64,
16 #[serde(rename = "nprOffRate")]
17 pub npr_off_rate: f64,
18}
19
20#[derive(Clone, Debug, Deserialize)]
21pub struct WithdrawFee {
22 pub fee: f64,
23}
24
25impl Client {
26 pub fn fees(self) -> impl Future<Output = Result<Fees, HttpError>> {
27 let url = self.url_for_v1_resource(FEE_RESOURCE);
28 self.request(HttpVerb::Get, &url, None)
29 }
30
31 pub fn withdraw_fee(
32 self,
33 currency: &str,
34 ) -> impl Future<Output = Result<WithdrawFee, HttpError>> {
35 let url = self.url_for_v1_resource(&format!("{}/{}", WITHDRAW_FEE_RESOURCE, currency));
36 self.request(HttpVerb::Get, &url, None)
37 }
38}
39
40#[cfg(test)]
41mod tests {
42 use super::*;
43
44 use crate::util::server::{new_test_server_and_client, ApiMock};
45
46 const FEES_RESPONSE_BODY: &str = r#"
47 {
48 "volume": 2302868.809741,
49 "makerRate": 0.00021,
50 "nprRate": 0.00035,
51 "nprOffRate": 0.00105
52 }
53 "#;
54
55 const FEES_INVALID_TOKEN_RESPONSE_BODY: &str = r#"
56 {
57 "error": "invalid token. check authorization header."
58 }
59 "#;
60
61 const WITHDRAW_FEE_RESPONSE_BODY: &str = r#"
62 {
63 "fee": 0.001
64 }
65 "#;
66
67 #[tokio::test]
68 async fn test_fees_ok() {
69 let mock = ApiMock {
70 action: HttpVerb::Get,
71 body: FEES_RESPONSE_BODY.into(),
72 path: format!("/v1/{}", FEE_RESOURCE),
73 response_code: 200,
74 };
75
76 let (client, _server, mock_results) = new_test_server_and_client(vec![mock]).await;
77
78 let result = client.fees().await;
79
80 assert!(result.is_ok());
81
82 for mock in mock_results {
83 mock.assert_async().await;
84 }
85 }
86
87 #[tokio::test]
88 async fn test_fees_invalid_token() {
89 let mock = ApiMock {
90 action: HttpVerb::Get,
91 body: FEES_INVALID_TOKEN_RESPONSE_BODY.into(),
92 path: format!("/v1/{}", FEE_RESOURCE),
93 response_code: 401,
94 };
95
96 let (client, _server, mock_results) = new_test_server_and_client(vec![mock]).await;
97
98 let result = client.fees().await;
99
100 assert!(result.is_err());
101 assert!(
102 result.unwrap_err().to_string()
103 == "Error while making request: `\"invalid token. check authorization header.\"`"
104 );
105
106 for mock in mock_results {
107 mock.assert_async().await;
108 }
109 }
110
111 #[tokio::test]
112 async fn test_withdraw_fee() {
113 let mock = ApiMock {
114 action: HttpVerb::Get,
115 body: WITHDRAW_FEE_RESPONSE_BODY.into(),
116 path: format!("/v1/{}/{}", WITHDRAW_FEE_RESOURCE, "eth"),
117 response_code: 200,
118 };
119
120 let (client, _server, mock_results) = new_test_server_and_client(vec![mock]).await;
121
122 let result = client.withdraw_fee("eth").await;
123
124 assert!(result.is_ok());
125
126 for mock in mock_results {
127 mock.assert_async().await;
128 }
129 }
130}