kraken_client/
client.rs

1use crate::error::Error;
2use crate::sign;
3use reqwest::header;
4use serde::{de::DeserializeOwned, Deserialize};
5
6#[derive(Debug, Deserialize)]
7struct ResponseWrapper<T> {
8    pub error: Vec<String>,
9    pub result: Option<T>,
10}
11
12pub type Result<T> = std::result::Result<T, Error>;
13
14const DEFAULT_BASE_URL: &str = "https://api.kraken.com";
15
16const DEFAULT_USER_AGENT: &str = "rust-kraken-client/0.7.1";
17
18#[derive(Default)]
19pub struct ClientBuilder {
20    base_url: Option<String>,
21    user_agent: Option<String>,
22    api_key: Option<String>,
23    api_secret: Option<String>,
24    http_client: Option<reqwest::Client>,
25}
26
27impl ClientBuilder {
28    pub fn base_url(mut self, base_url: &str) -> Self {
29        self.base_url = Some(base_url.to_string());
30        self
31    }
32
33    pub fn user_agent(mut self, user_agent: &str) -> Self {
34        self.user_agent = Some(user_agent.to_string());
35        self
36    }
37
38    pub fn api_key(mut self, api_key: &str) -> Self {
39        self.api_key = Some(api_key.to_string());
40        self
41    }
42
43    pub fn api_secret(mut self, api_secret: &str) -> Self {
44        self.api_secret = Some(api_secret.to_string());
45        self
46    }
47
48    pub fn auth(mut self, api_key: &str, api_secret: &str) -> Self {
49        self.api_key = Some(api_key.to_string());
50        self.api_secret = Some(api_secret.to_string());
51        self
52    }
53
54    pub fn http_client(mut self, http_client: reqwest::Client) -> Self {
55        self.http_client = Some(http_client);
56        self
57    }
58
59    pub fn build(self) -> Client {
60        Client {
61            base_url: self
62                .base_url
63                .unwrap_or_else(|| DEFAULT_BASE_URL.to_string()),
64            user_agent: self
65                .user_agent
66                .unwrap_or_else(|| DEFAULT_USER_AGENT.to_string()),
67            api_key: self.api_key,
68            api_secret: self.api_secret,
69            http_client: self.http_client.unwrap_or_else(reqwest::Client::new),
70        }
71    }
72}
73
74#[derive(Clone)]
75pub struct Client {
76    base_url: String,
77    /// You must supply a user agent string while creating a request header else you
78    /// will not be able to connect to the API.
79    user_agent: String,
80    api_key: Option<String>,
81    api_secret: Option<String>,
82    http_client: reqwest::Client,
83}
84
85impl Default for Client {
86    fn default() -> Self {
87        Self::builder().build()
88    }
89}
90
91impl Client {
92    pub fn new(api_key: &str, api_secret: &str) -> Self {
93        Self::builder()
94            .api_key(api_key)
95            .api_secret(api_secret)
96            .build()
97    }
98
99    pub fn builder() -> ClientBuilder {
100        ClientBuilder::default()
101    }
102
103    async fn unwrap_response<Resp>(&self, resp: reqwest::Response) -> Result<Resp>
104    where
105        Resp: DeserializeOwned,
106    {
107        let resp: ResponseWrapper<Resp> = resp.json().await?;
108
109        if !resp.error.is_empty() {
110            return Err(Error::Api(resp.error.join(",")));
111        }
112
113        if let Some(result) = resp.result {
114            Ok(result)
115        } else {
116            Err(Error::internal("no result field in response"))
117        }
118    }
119
120    /// Sends a public request to the API.
121    pub async fn send_public<Resp>(&self, url: &str) -> Result<Resp>
122    where
123        Resp: DeserializeOwned,
124    {
125        let url = format!("{}{}", self.base_url, url);
126
127        let resp = self
128            .http_client
129            .get(&url)
130            .header(header::USER_AGENT, &self.user_agent)
131            .send()
132            .await?;
133
134        self.unwrap_response(resp).await
135    }
136
137    /// Sends a private request to the API.
138    pub async fn send_private<Resp>(&self, url: &str, query: Option<String>) -> Result<Resp>
139    where
140        Resp: DeserializeOwned,
141    {
142        let resp = if let Some(api_key) = &self.api_key {
143            if let Some(api_secret) = &self.api_secret {
144                let pathname = url;
145                let url = format!("{}{}", self.base_url, url);
146
147                let nonce = sign::compute_nonce().to_string();
148
149                let formdata = if let Some(query) = query {
150                    format!("{}&nonce={}", query, nonce)
151                } else {
152                    format!("nonce={}", nonce)
153                };
154
155                self.http_client
156                    .post(&url)
157                    .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
158                    .header(header::USER_AGENT, &self.user_agent)
159                    .header("API-Key", api_key)
160                    .header(
161                        "API-Sign",
162                        sign::compute_signature(api_secret, &pathname, &nonce, &formdata)?,
163                    )
164                    .body(formdata.into_bytes())
165                    .send()
166                    .await?
167            } else {
168                return Err(Error::Unauthorized);
169            }
170        } else {
171            return Err(Error::Unauthorized);
172        };
173
174        self.unwrap_response(resp).await
175    }
176}