use serde::{Deserialize, Serialize};
pub const KRAKEN_WS_URL: &str = "wss://ws.kraken.com/v2";
#[derive(Debug, Clone, Serialize)]
pub struct SubscribeRequest {
pub method: String,
pub params: SubscribeParams,
#[serde(skip_serializing_if = "Option::is_none")]
pub req_id: Option<u64>,
}
#[derive(Debug, Clone, Serialize)]
pub struct SubscribeParams {
pub channel: String,
pub symbol: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub depth: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub snapshot: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub interval: Option<u32>,
}
impl SubscribeRequest {
pub fn orderbook(symbols: Vec<String>, depth: u32) -> Self {
Self {
method: "subscribe".to_string(),
params: SubscribeParams {
channel: "book".to_string(),
symbol: symbols,
depth: Some(depth),
snapshot: Some(true),
interval: None,
},
req_id: None,
}
}
pub fn trades(symbols: Vec<String>) -> Self {
Self {
method: "subscribe".to_string(),
params: SubscribeParams {
channel: "trade".to_string(),
symbol: symbols,
depth: None,
snapshot: Some(true),
interval: None,
},
req_id: None,
}
}
pub fn ticker(symbols: Vec<String>) -> Self {
Self {
method: "subscribe".to_string(),
params: SubscribeParams {
channel: "ticker".to_string(),
symbol: symbols,
depth: None,
snapshot: Some(true),
interval: None,
},
req_id: None,
}
}
pub fn ohlc(symbols: Vec<String>, interval: u32) -> Self {
Self {
method: "subscribe".to_string(),
params: SubscribeParams {
channel: "ohlc".to_string(),
symbol: symbols,
depth: None,
snapshot: Some(true),
interval: Some(interval),
},
req_id: None,
}
}
pub fn with_req_id(mut self, id: u64) -> Self {
self.req_id = Some(id);
self
}
}
#[derive(Debug, Clone, Serialize)]
pub struct UnsubscribeRequest {
pub method: String,
pub params: SubscribeParams,
#[serde(skip_serializing_if = "Option::is_none")]
pub req_id: Option<u64>,
}
impl UnsubscribeRequest {
pub fn new(channel: String, symbols: Vec<String>) -> Self {
Self {
method: "unsubscribe".to_string(),
params: SubscribeParams {
channel,
symbol: symbols,
depth: None,
snapshot: None,
interval: None,
},
req_id: None,
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct PingRequest {
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub req_id: Option<u64>,
}
impl Default for PingRequest {
fn default() -> Self {
Self {
method: "ping".to_string(),
req_id: None,
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct KrakenResponse {
#[serde(default)]
pub channel: String,
#[serde(rename = "type", default)]
pub msg_type: String,
#[serde(default)]
pub method: String,
#[serde(default)]
pub success: Option<bool>,
#[serde(default)]
pub error: Option<String>,
#[serde(default)]
pub req_id: Option<u64>,
#[serde(default)]
pub result: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SystemStatus {
pub channel: String,
#[serde(rename = "type")]
pub msg_type: String,
pub data: Vec<SystemStatusData>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SystemStatusData {
pub api_version: String,
pub connection_id: u64,
pub system: String,
pub version: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Heartbeat {
pub channel: String,
#[serde(rename = "type")]
pub msg_type: String,
}
#[derive(Debug, Clone)]
pub enum KrakyMessage {
SystemStatus(SystemStatus),
Heartbeat,
Pong { req_id: Option<u64> },
SubscriptionStatus {
success: bool,
channel: String,
symbol: Option<String>,
error: Option<String>,
},
#[cfg(feature = "orderbook")]
Orderbook(crate::models::OrderbookUpdate),
#[cfg(feature = "trades")]
Trade(crate::models::TradeUpdate),
#[cfg(feature = "ticker")]
Ticker(crate::models::TickerUpdate),
#[cfg(feature = "ohlc")]
OHLC(crate::models::OHLCUpdate),
Unknown(serde_json::Value),
}
impl KrakyMessage {
pub fn parse(text: &str) -> Result<Self, serde_json::Error> {
#[cfg(feature = "simd")]
let value: serde_json::Value = {
let mut bytes = text.as_bytes().to_vec();
simd_json::from_slice(&mut bytes).map_err(|e| {
serde_json::Error::io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
e.to_string(),
))
})?
};
#[cfg(not(feature = "simd"))]
let value: serde_json::Value = serde_json::from_str(text)?;
if let Some(method) = value.get("method").and_then(|m| m.as_str()) {
match method {
"pong" => {
let req_id = value.get("req_id").and_then(|r| r.as_u64());
return Ok(KrakyMessage::Pong { req_id });
}
"subscribe" | "unsubscribe" => {
let success = value
.get("success")
.and_then(|s| s.as_bool())
.unwrap_or(false);
let error = value
.get("error")
.and_then(|e| e.as_str())
.map(String::from);
let result = value.get("result");
let channel = result
.and_then(|r| r.get("channel"))
.and_then(|c| c.as_str())
.unwrap_or("")
.to_string();
let symbol = result
.and_then(|r| r.get("symbol"))
.and_then(|s| s.as_str())
.map(String::from);
return Ok(KrakyMessage::SubscriptionStatus {
success,
channel,
symbol,
error,
});
}
_ => {}
}
}
if let Some(channel) = value.get("channel").and_then(|c| c.as_str()) {
match channel {
"status" => {
let status: SystemStatus = serde_json::from_value(value)?;
return Ok(KrakyMessage::SystemStatus(status));
}
"heartbeat" => {
return Ok(KrakyMessage::Heartbeat);
}
#[cfg(feature = "orderbook")]
"book" => {
let update: crate::models::OrderbookUpdate = serde_json::from_value(value)?;
return Ok(KrakyMessage::Orderbook(update));
}
#[cfg(feature = "trades")]
"trade" => {
let update: crate::models::TradeUpdate = serde_json::from_value(value)?;
return Ok(KrakyMessage::Trade(update));
}
#[cfg(feature = "ticker")]
"ticker" => {
let update: crate::models::TickerUpdate = serde_json::from_value(value)?;
return Ok(KrakyMessage::Ticker(update));
}
#[cfg(feature = "ohlc")]
"ohlc" => {
let update: crate::models::OHLCUpdate = serde_json::from_value(value)?;
return Ok(KrakyMessage::OHLC(update));
}
_ => {}
}
}
Ok(KrakyMessage::Unknown(value))
}
}