crypto_botters_coincheck/
lib.rs1use std::{
5 marker::PhantomData,
6 time::SystemTime,
7};
8use hmac::{Hmac, Mac};
9use sha2::Sha256;
10use serde::{de::DeserializeOwned, Serialize};
11use serde_json::json;
12use crypto_botters_api::{HandlerOption, HandlerOptions, HttpOption, WebSocketOption};
13use generic_api_client::{http::{*, header::HeaderValue}, websocket::*};
14
15pub type CoincheckRequestResult<T> = Result<T, CoincheckRequestError>;
17pub type CoincheckRequestError = RequestError<&'static str, CoincheckHandlerError>;
18
19pub enum CoincheckOption {
21 Default,
23 Key(String),
25 Secret(String),
27 HttpUrl(CoincheckHttpUrl),
29 HttpAuth(bool),
31 RequestConfig(RequestConfig),
34 WebSocketUrl(CoincheckWebSocketUrl),
36 WebSocketChannels(Vec<String>),
38 WebSocketConfig(WebSocketConfig),
42}
43
44#[derive(Clone, Debug)]
46pub struct CoincheckOptions {
47 pub key: Option<String>,
49 pub secret: Option<String>,
51 pub http_url: CoincheckHttpUrl,
53 pub http_auth: bool,
55 pub request_config: RequestConfig,
57 pub websocket_url: CoincheckWebSocketUrl,
59 pub websocket_channels: Vec<String>,
61 pub websocket_config: WebSocketConfig,
63}
64
65#[derive(Debug, Eq, PartialEq, Copy, Clone)]
67pub enum CoincheckHttpUrl {
68 Default,
70 None,
72}
73
74#[derive(Debug, Eq, PartialEq, Copy, Clone)]
76#[non_exhaustive]
77pub enum CoincheckWebSocketUrl {
78 Default,
80 None,
82}
83
84#[derive(Debug)]
85pub enum CoincheckHandlerError {
86 ApiError(serde_json::Value),
87 RequestLimitExceeded(serde_json::Value),
88 ParseError,
89}
90
91pub struct CoincheckRequestHandler<'a, R: DeserializeOwned> {
93 options: CoincheckOptions,
94 _phantom: PhantomData<&'a R>,
95}
96
97pub struct CoincheckWebSocketHandler<H: FnMut(serde_json::Value) + Send + 'static> {
99 message_handler: H,
100 options: CoincheckOptions,
101}
102
103impl<'a, B, R> RequestHandler<B> for CoincheckRequestHandler<'a, R>
104where
105 B: Serialize,
106 R: DeserializeOwned,
107{
108 type Successful = R;
109 type Unsuccessful = CoincheckHandlerError;
110 type BuildError = &'static str;
111
112 fn request_config(&self) -> RequestConfig {
113 let mut config = self.options.request_config.clone();
114 if self.options.http_url != CoincheckHttpUrl::None {
115 config.url_prefix = self.options.http_url.as_str().to_owned();
116 }
117 config
118 }
119
120 fn build_request(&self, mut builder: RequestBuilder, request_body: &Option<B>, _: u8) -> Result<Request, Self::BuildError> {
121 if let Some(body) = request_body {
122 let encoded = serde_urlencoded::to_string(body).or(Err("could not serialize body as application/x-www-form-urlencoded"))?;
123 builder = builder
124 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
125 .body(encoded);
126 }
127
128 let mut request = builder.build().or(Err("failed to build request"))?;
129
130 if self.options.http_auth {
131 let time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let nonce = time.as_millis() as u64;
134
135 let body = request.body()
136 .and_then(|body| body.as_bytes())
137 .map(String::from_utf8_lossy)
138 .unwrap_or_default();
139
140 let sign_contents = format!("{}{}{}", nonce, request.url(), body);
141
142 let secret = self.options.secret.as_deref().ok_or("API secret not set")?;
143 let mut hmac = Hmac::<Sha256>::new_from_slice(secret.as_bytes()).unwrap(); hmac.update(sign_contents.as_bytes());
146 let signature = hex::encode(hmac.finalize().into_bytes());
147
148 let key = HeaderValue::from_str(self.options.key.as_deref().ok_or("API key not set")?).or(
149 Err("invalid character in API key")
150 )?;
151 let headers = request.headers_mut();
152 headers.insert("ACCESS-KEY", key);
153 headers.insert("ACCESS-NONCE", HeaderValue::from(nonce));
154 headers.insert("ACCESS-SIGNATURE", HeaderValue::from_str(&signature).unwrap()); }
156
157 Ok(request)
158 }
159
160 fn handle_response(&self, status: StatusCode, _: HeaderMap, response_body: Bytes) -> Result<Self::Successful, Self::Unsuccessful> {
161 if status.is_success() {
162 serde_json::from_slice(&response_body).map_err(|error| {
163 log::error!("Failed to parse response due to an error: {}", error);
164 CoincheckHandlerError::ParseError
165 })
166 } else {
167 let error = match serde_json::from_slice(&response_body) {
168 Ok(parsed_error) => {
169 if status == 429 {
170 CoincheckHandlerError::RequestLimitExceeded(parsed_error)
171 } else {
172 CoincheckHandlerError::ApiError(parsed_error)
173 }
174 },
175 Err(error) => {
176 log::error!("Failed to parse error response due to an error: {}", error);
177 CoincheckHandlerError::ParseError
178 }
179 };
180 Err(error)
181 }
182 }
183}
184
185impl<H> WebSocketHandler for CoincheckWebSocketHandler<H> where H: FnMut(serde_json::Value) + Send + 'static, {
186 fn websocket_config(&self) -> WebSocketConfig {
187 let mut config = self.options.websocket_config.clone();
188 if self.options.websocket_url != CoincheckWebSocketUrl::None {
189 config.url_prefix = self.options.websocket_url.as_str().to_owned();
190 }
191 config
192 }
193
194 fn handle_start(&mut self) -> Vec<WebSocketMessage> {
195 self.options.websocket_channels.clone().into_iter().map(|channel| {
196 WebSocketMessage::Text(json!({ "type": "subscribe", "channel": channel }).to_string())
197 }).collect()
198 }
199
200 fn handle_message(&mut self, message: WebSocketMessage) -> Vec<WebSocketMessage> {
201 match message {
202 WebSocketMessage::Text(message) => {
203 match serde_json::from_str(&message) {
204 Ok(message) => (self.message_handler)(message),
205 Err(_) => log::warn!("Invalid JSON message received"),
206 };
207 },
208 WebSocketMessage::Binary(_) => log::warn!("Unexpected binary message received"),
209 WebSocketMessage::Ping(_) | WebSocketMessage::Pong(_) => (),
210 }
211 vec![]
212 }
213}
214
215impl CoincheckHttpUrl {
216 #[inline(always)]
218 fn as_str(&self) -> &'static str {
219 match self {
220 Self::Default => "https://coincheck.com",
221 Self::None => "",
222 }
223 }
224}
225
226impl CoincheckWebSocketUrl {
227 #[inline(always)]
229 fn as_str(&self) -> &'static str {
230 match self {
231 Self::Default => "wss://ws-api.coincheck.com/",
232 Self::None => "",
233 }
234 }
235}
236
237impl HandlerOptions for CoincheckOptions {
238 type OptionItem = CoincheckOption;
239
240 fn update(&mut self, option: Self::OptionItem) {
241 match option {
242 CoincheckOption::Default => (),
243 CoincheckOption::Key(v) => self.key = Some(v),
244 CoincheckOption::Secret(v) => self.secret = Some(v),
245 CoincheckOption::HttpUrl(v) => self.http_url = v,
246 CoincheckOption::HttpAuth(v) => self.http_auth = v,
247 CoincheckOption::RequestConfig(v) => self.request_config = v,
248 CoincheckOption::WebSocketUrl(v) => self.websocket_url = v,
249 CoincheckOption::WebSocketChannels(v) => self.websocket_channels = v,
250 CoincheckOption::WebSocketConfig(v) => self.websocket_config = v,
251 }
252 }
253}
254
255impl Default for CoincheckOptions {
256 fn default() -> Self {
257 let mut websocket_config = WebSocketConfig::new();
258 websocket_config.ignore_duplicate_during_reconnection = true;
259 Self {
260 key: None,
261 secret: None,
262 http_url: CoincheckHttpUrl::Default,
263 http_auth: false,
264 request_config: RequestConfig::default(),
265 websocket_url: CoincheckWebSocketUrl::Default,
266 websocket_channels: vec![],
267 websocket_config,
268 }
269 }
270}
271
272impl<'a, R: DeserializeOwned + 'a> HttpOption<'a, R> for CoincheckOption {
273 type RequestHandler = CoincheckRequestHandler<'a, R>;
274
275 #[inline(always)]
276 fn request_handler(options: Self::Options) -> Self::RequestHandler {
277 CoincheckRequestHandler::<'a, R> {
278 options,
279 _phantom: PhantomData,
280 }
281 }
282}
283
284impl<H: FnMut(serde_json::Value) + Send + 'static> WebSocketOption<H> for CoincheckOption {
285 type WebSocketHandler = CoincheckWebSocketHandler<H>;
286
287 #[inline(always)]
288 fn websocket_handler(handler: H, options: Self::Options) -> Self::WebSocketHandler {
289 CoincheckWebSocketHandler {
290 message_handler: handler,
291 options,
292 }
293 }
294}
295
296impl HandlerOption for CoincheckOption {
297 type Options = CoincheckOptions;
298}
299
300impl Default for CoincheckOption {
301 fn default() -> Self {
302 Self::Default
303 }
304}