1use deribit_base::{impl_json_debug_pretty, impl_json_display};
8use serde::{Deserialize, Serialize};
9
10#[derive(Clone, Serialize, Deserialize, PartialEq)]
12pub enum WebSocketMessage {
13 Request(JsonRpcRequest),
15 Response(JsonRpcResponse),
17 Notification(JsonRpcNotification),
19}
20
21#[derive(Clone, Serialize, Deserialize, PartialEq)]
23pub struct JsonRpcRequest {
24 pub jsonrpc: String,
26 pub id: serde_json::Value,
28 pub method: String,
30 pub params: Option<serde_json::Value>,
32}
33
34#[derive(Clone, Serialize, Deserialize, PartialEq)]
36pub struct JsonRpcResponse {
37 pub jsonrpc: String,
39 pub id: serde_json::Value,
41 #[serde(flatten)]
43 pub result: JsonRpcResult,
44}
45
46#[derive(Clone, Serialize, Deserialize, PartialEq)]
48#[serde(untagged)]
49pub enum JsonRpcResult {
50 Success {
52 result: serde_json::Value,
54 },
55 Error {
57 error: JsonRpcError,
59 },
60}
61
62#[derive(Clone, Serialize, Deserialize, PartialEq)]
64pub struct JsonRpcError {
65 pub code: i32,
67 pub message: String,
69 pub data: Option<serde_json::Value>,
71}
72
73#[derive(Clone, Serialize, Deserialize, PartialEq)]
75pub struct JsonRpcNotification {
76 pub jsonrpc: String,
78 pub method: String,
80 pub params: Option<serde_json::Value>,
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, Hash)]
86pub enum ConnectionState {
87 Disconnected,
89 Connecting,
91 Connected,
93 Authenticated,
95 Reconnecting,
97 Failed,
99}
100
101#[derive(Debug, Clone)]
103pub struct HeartbeatStatus {
104 pub last_ping: Option<std::time::Instant>,
106 pub last_pong: Option<std::time::Instant>,
108 pub missed_pongs: u32,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
114pub enum SubscriptionChannel {
115 Ticker(String),
117 OrderBook(String),
119 Trades(String),
121 ChartTrades {
123 instrument: String,
125 resolution: String,
127 },
128 UserOrders,
130 UserTrades,
132 UserPortfolio,
134 UserChanges {
136 instrument: String,
138 interval: String,
140 },
141 PriceIndex(String),
143 EstimatedExpirationPrice(String),
145 MarkPrice(String),
147 Funding(String),
149 Perpetual(String),
151 Quote(String),
153}
154
155#[derive(Clone, Serialize, Deserialize, PartialEq)]
157pub struct WsRequest {
158 pub jsonrpc: String,
160 pub id: serde_json::Value,
162 pub method: String,
164 pub params: Option<serde_json::Value>,
166}
167
168#[derive(Clone, Serialize, Deserialize, PartialEq)]
170pub struct WsResponse {
171 pub jsonrpc: String,
173 pub id: Option<serde_json::Value>,
175 pub result: Option<serde_json::Value>,
177 pub error: Option<JsonRpcError>,
179}
180
181impl JsonRpcRequest {
182 pub fn new<T: Serialize>(id: serde_json::Value, method: &str, params: Option<T>) -> Self {
184 Self {
185 jsonrpc: "2.0".to_string(),
186 id,
187 method: method.to_string(),
188 params: params.map(|p| serde_json::to_value(p).unwrap_or(serde_json::Value::Null)),
189 }
190 }
191}
192
193impl JsonRpcResponse {
194 pub fn success(id: serde_json::Value, result: serde_json::Value) -> Self {
196 Self {
197 jsonrpc: "2.0".to_string(),
198 id,
199 result: JsonRpcResult::Success { result },
200 }
201 }
202
203 pub fn error(id: serde_json::Value, error: JsonRpcError) -> Self {
205 Self {
206 jsonrpc: "2.0".to_string(),
207 id,
208 result: JsonRpcResult::Error { error },
209 }
210 }
211}
212
213impl JsonRpcNotification {
214 pub fn new<T: Serialize>(method: &str, params: Option<T>) -> Self {
216 Self {
217 jsonrpc: "2.0".to_string(),
218 method: method.to_string(),
219 params: params.map(|p| serde_json::to_value(p).unwrap_or(serde_json::Value::Null)),
220 }
221 }
222}
223
224impl SubscriptionChannel {
225 pub fn channel_name(&self) -> String {
227 match self {
228 SubscriptionChannel::Ticker(instrument) => format!("ticker.{}", instrument),
229 SubscriptionChannel::OrderBook(instrument) => format!("book.{}.raw", instrument),
230 SubscriptionChannel::Trades(instrument) => format!("trades.{}.raw", instrument),
231 SubscriptionChannel::ChartTrades {
232 instrument,
233 resolution,
234 } => {
235 format!("chart.trades.{}.{}", instrument, resolution)
236 }
237 SubscriptionChannel::UserOrders => "user.orders.any.any.raw".to_string(),
238 SubscriptionChannel::UserTrades => "user.trades.any.any.raw".to_string(),
239 SubscriptionChannel::UserPortfolio => "user.portfolio.any".to_string(),
240 SubscriptionChannel::UserChanges {
241 instrument,
242 interval,
243 } => {
244 format!("user.changes.{}.{}", instrument, interval)
245 }
246 SubscriptionChannel::PriceIndex(currency) => {
247 format!("deribit_price_index.{}_usd", currency.to_lowercase())
248 }
249 SubscriptionChannel::EstimatedExpirationPrice(instrument) => {
250 format!("estimated_expiration_price.{}", instrument)
251 }
252 SubscriptionChannel::MarkPrice(instrument) => {
253 format!("markprice.options.{}", instrument)
254 }
255 SubscriptionChannel::Funding(instrument) => format!("perpetual.{}.raw", instrument),
256 SubscriptionChannel::Perpetual(instrument) => format!("perpetual.{}.raw", instrument),
257 SubscriptionChannel::Quote(instrument) => format!("quote.{}", instrument),
258 }
259 }
260
261 pub fn from_string(s: &str) -> Option<Self> {
263 let parts: Vec<&str> = s.split('.').collect();
264 match parts.as_slice() {
265 ["ticker", instrument] => Some(SubscriptionChannel::Ticker(instrument.to_string())),
266 ["book", instrument, "raw"] => {
267 Some(SubscriptionChannel::OrderBook(instrument.to_string()))
268 }
269 ["trades", instrument, "raw"] => {
270 Some(SubscriptionChannel::Trades(instrument.to_string()))
271 }
272 ["chart", "trades", instrument, resolution] => Some(SubscriptionChannel::ChartTrades {
273 instrument: instrument.to_string(),
274 resolution: resolution.to_string(),
275 }),
276 ["user", "orders", "any", "any", "raw"] => Some(SubscriptionChannel::UserOrders),
277 ["user", "trades", "any", "any", "raw"] => Some(SubscriptionChannel::UserTrades),
278 ["user", "portfolio", "any"] => Some(SubscriptionChannel::UserPortfolio),
279 ["user", "changes", instrument, interval] => Some(SubscriptionChannel::UserChanges {
280 instrument: instrument.to_string(),
281 interval: interval.to_string(),
282 }),
283 ["deribit_price_index", currency_pair] => currency_pair
284 .strip_suffix("_usd")
285 .map(|currency| SubscriptionChannel::PriceIndex(currency.to_uppercase())),
286 ["estimated_expiration_price", instrument] => Some(
287 SubscriptionChannel::EstimatedExpirationPrice(instrument.to_string()),
288 ),
289 ["markprice", "options", instrument] => {
290 Some(SubscriptionChannel::MarkPrice(instrument.to_string()))
291 }
292 ["perpetual", instrument, "raw"] => {
293 Some(SubscriptionChannel::Perpetual(instrument.to_string()))
294 }
295 ["quote", instrument] => Some(SubscriptionChannel::Quote(instrument.to_string())),
296 _ => None,
297 }
298 }
299}
300
301impl std::fmt::Display for SubscriptionChannel {
302 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
303 write!(f, "{}", self.channel_name())
304 }
305}
306
307impl ConnectionState {
308 pub fn is_connected(&self) -> bool {
310 matches!(
311 self,
312 ConnectionState::Connected | ConnectionState::Authenticated
313 )
314 }
315
316 pub fn is_authenticated(&self) -> bool {
318 matches!(self, ConnectionState::Authenticated)
319 }
320
321 pub fn is_transitional(&self) -> bool {
323 matches!(
324 self,
325 ConnectionState::Connecting | ConnectionState::Reconnecting
326 )
327 }
328}
329
330impl HeartbeatStatus {
331 pub fn new() -> Self {
333 Self {
334 last_ping: None,
335 last_pong: None,
336 missed_pongs: 0,
337 }
338 }
339
340 pub fn ping_sent(&mut self) {
342 self.last_ping = Some(std::time::Instant::now());
343 }
344
345 pub fn pong_received(&mut self) {
347 self.last_pong = Some(std::time::Instant::now());
348 self.missed_pongs = 0;
349 }
350
351 pub fn missed_pong(&mut self) {
353 self.missed_pongs += 1;
354 }
355
356 pub fn is_stale(&self, max_missed_pongs: u32) -> bool {
358 self.missed_pongs >= max_missed_pongs
359 }
360}
361
362impl Default for HeartbeatStatus {
363 fn default() -> Self {
364 Self::new()
365 }
366}
367
368impl_json_display!(WebSocketMessage);
370impl_json_debug_pretty!(WebSocketMessage);
371
372impl_json_display!(JsonRpcRequest);
373impl_json_debug_pretty!(JsonRpcRequest);
374
375impl_json_display!(JsonRpcResponse);
376impl_json_debug_pretty!(JsonRpcResponse);
377
378impl_json_display!(JsonRpcResult);
379impl_json_debug_pretty!(JsonRpcResult);
380
381impl_json_display!(JsonRpcError);
382impl_json_debug_pretty!(JsonRpcError);
383
384impl_json_display!(JsonRpcNotification);
385impl_json_debug_pretty!(JsonRpcNotification);
386
387impl_json_display!(WsRequest);
388impl_json_debug_pretty!(WsRequest);
389
390impl_json_display!(WsResponse);
391impl_json_debug_pretty!(WsResponse);