sfox/http/v1/
fee.rs

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}