1use std::str::FromStr;
19
20use nautilus_core::serialization::{deserialize_decimal, deserialize_optional_decimal};
21use nautilus_model::{
22 data::{
23 Data, FundingRateUpdate, InstrumentStatus, OrderBookDeltas, greeks::OptionGreekValues,
24 option_chain::OptionGreeks,
25 },
26 events::{
27 AccountState, OrderAccepted, OrderCancelRejected, OrderCanceled, OrderExpired,
28 OrderModifyRejected, OrderRejected, OrderUpdated,
29 },
30 instruments::InstrumentAny,
31 reports::{FillReport, OrderStatusReport},
32};
33use rust_decimal::{Decimal, prelude::ToPrimitive};
34use serde::{Deserialize, Deserializer, Serialize, de};
35use ustr::Ustr;
36
37use super::enums::{DeribitBookAction, DeribitBookMsgType, DeribitHeartbeatType};
38pub use crate::common::{
39 enums::DeribitInstrumentState,
40 rpc::{DeribitJsonRpcError, DeribitJsonRpcRequest, DeribitJsonRpcResponse},
41};
42use crate::websocket::error::DeribitWsError;
43
44#[derive(Debug, Clone, Deserialize)]
46pub struct DeribitSubscriptionNotification<T> {
47 pub jsonrpc: String,
49 pub method: String,
51 pub params: DeribitSubscriptionParams<T>,
53}
54
55#[derive(Debug, Clone, Deserialize)]
57pub struct DeribitSubscriptionParams<T> {
58 pub channel: String,
60 pub data: T,
62}
63
64#[derive(Debug, Clone, Serialize)]
66pub struct DeribitAuthParams {
67 pub grant_type: String,
69 pub client_id: String,
71 pub timestamp: u64,
73 pub signature: String,
75 pub nonce: String,
77 pub data: String,
79 #[serde(skip_serializing_if = "Option::is_none")]
83 pub scope: Option<String>,
84}
85
86#[derive(Debug, Clone, Serialize)]
88pub struct DeribitRefreshTokenParams {
89 pub grant_type: String,
91 pub refresh_token: String,
93}
94
95#[derive(Debug, Clone, Deserialize)]
97pub struct DeribitAuthResult {
98 pub access_token: String,
100 pub expires_in: u64,
102 pub refresh_token: String,
104 pub scope: String,
106 pub token_type: String,
108 #[serde(default)]
110 pub enabled_features: Vec<String>,
111}
112
113#[derive(Debug, Clone, Serialize)]
115pub struct DeribitSubscribeParams {
116 pub channels: Vec<String>,
118}
119
120#[derive(Debug, Clone, Deserialize)]
122pub struct DeribitSubscribeResult(pub Vec<String>);
123
124#[derive(Debug, Clone, Serialize)]
126pub struct DeribitHeartbeatParams {
127 pub interval: u64,
129}
130
131#[derive(Debug, Clone, Deserialize)]
133pub struct DeribitHeartbeatData {
134 #[serde(rename = "type")]
136 pub heartbeat_type: DeribitHeartbeatType,
137}
138
139#[derive(Debug, Clone, Deserialize)]
141pub struct DeribitTradeMsg {
142 pub trade_id: String,
144 pub instrument_name: Ustr,
146 #[serde(deserialize_with = "deserialize_decimal")]
148 pub price: Decimal,
149 #[serde(deserialize_with = "deserialize_decimal")]
151 pub amount: Decimal,
152 pub direction: String,
154 pub timestamp: u64,
156 pub trade_seq: u64,
158 pub tick_direction: i8,
160 #[serde(deserialize_with = "deserialize_decimal")]
162 pub index_price: Decimal,
163 #[serde(deserialize_with = "deserialize_decimal")]
165 pub mark_price: Decimal,
166 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
168 pub iv: Option<Decimal>,
169 pub liquidation: Option<String>,
171 pub combo_trade_id: Option<String>,
173 pub block_trade_id: Option<String>,
175 pub combo_id: Option<String>,
177}
178
179#[derive(Debug, Clone, Deserialize)]
184pub struct DeribitBookMsg {
185 #[serde(rename = "type", default = "default_book_msg_type")]
187 pub msg_type: DeribitBookMsgType,
188 pub instrument_name: Ustr,
190 pub timestamp: u64,
192 pub change_id: u64,
194 pub prev_change_id: Option<u64>,
196 pub bids: Vec<Vec<serde_json::Value>>,
198 pub asks: Vec<Vec<serde_json::Value>>,
200}
201
202fn default_book_msg_type() -> DeribitBookMsgType {
204 DeribitBookMsgType::Snapshot
205}
206
207#[derive(Debug, Clone)]
209pub struct DeribitBookLevel {
210 pub price: Decimal,
212 pub amount: Decimal,
214 pub action: Option<DeribitBookAction>,
216}
217
218#[derive(Debug, Clone, Deserialize)]
220pub struct DeribitTickerMsg {
221 pub instrument_name: Ustr,
223 pub timestamp: u64,
225 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
227 pub best_bid_price: Option<Decimal>,
228 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
230 pub best_bid_amount: Option<Decimal>,
231 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
233 pub best_ask_price: Option<Decimal>,
234 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
236 pub best_ask_amount: Option<Decimal>,
237 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
239 pub last_price: Option<Decimal>,
240 #[serde(deserialize_with = "deserialize_decimal")]
242 pub mark_price: Decimal,
243 #[serde(deserialize_with = "deserialize_decimal")]
245 pub index_price: Decimal,
246 #[serde(deserialize_with = "deserialize_decimal")]
248 pub open_interest: Decimal,
249 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
251 pub current_funding: Option<Decimal>,
252 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
254 pub funding_8h: Option<Decimal>,
255 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
257 pub settlement_price: Option<Decimal>,
258 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
260 pub volume: Option<Decimal>,
261 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
263 pub volume_usd: Option<Decimal>,
264 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
266 pub high: Option<Decimal>,
267 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
269 pub low: Option<Decimal>,
270 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
272 pub price_change: Option<Decimal>,
273 pub state: String,
275 pub greeks: Option<DeribitGreeks>,
278 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
280 pub mark_iv: Option<Decimal>,
281 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
283 pub bid_iv: Option<Decimal>,
284 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
286 pub ask_iv: Option<Decimal>,
287 #[serde(default, deserialize_with = "deserialize_optional_decimal")]
289 pub underlying_price: Option<Decimal>,
290 pub underlying_index: Option<String>,
292}
293
294#[derive(Debug, Clone, Deserialize)]
296pub struct DeribitGreeks {
297 #[serde(deserialize_with = "deserialize_decimal")]
298 pub delta: Decimal,
299 #[serde(deserialize_with = "deserialize_decimal")]
300 pub gamma: Decimal,
301 #[serde(deserialize_with = "deserialize_decimal")]
302 pub vega: Decimal,
303 #[serde(deserialize_with = "deserialize_decimal")]
304 pub theta: Decimal,
305 #[serde(deserialize_with = "deserialize_decimal")]
306 pub rho: Decimal,
307}
308
309impl DeribitGreeks {
310 pub fn to_greek_values(&self) -> OptionGreekValues {
312 OptionGreekValues {
313 delta: self.delta.to_f64().unwrap_or(0.0),
314 gamma: self.gamma.to_f64().unwrap_or(0.0),
315 vega: self.vega.to_f64().unwrap_or(0.0),
316 theta: self.theta.to_f64().unwrap_or(0.0),
317 rho: self.rho.to_f64().unwrap_or(0.0),
318 }
319 }
320}
321
322#[derive(Debug, Clone, Deserialize)]
324pub struct DeribitQuoteMsg {
325 pub instrument_name: Ustr,
327 pub timestamp: u64,
329 #[serde(deserialize_with = "deserialize_decimal")]
331 pub best_bid_price: Decimal,
332 #[serde(deserialize_with = "deserialize_decimal")]
334 pub best_bid_amount: Decimal,
335 #[serde(deserialize_with = "deserialize_decimal")]
337 pub best_ask_price: Decimal,
338 #[serde(deserialize_with = "deserialize_decimal")]
340 pub best_ask_amount: Decimal,
341}
342
343#[derive(Debug, Clone, Deserialize)]
348pub struct DeribitInstrumentStateMsg {
349 pub instrument_name: Ustr,
351 pub state: DeribitInstrumentState,
353 pub timestamp: u64,
355}
356
357#[derive(Debug, Clone, Deserialize)]
363pub struct DeribitPerpetualMsg {
364 #[serde(deserialize_with = "deserialize_decimal")]
366 pub index_price: Decimal,
367 #[serde(deserialize_with = "deserialize_decimal")]
369 pub interest: Decimal,
370 pub timestamp: u64,
372}
373
374#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)]
379#[serde(rename_all = "lowercase")]
380pub enum DeribitChartStatus {
381 #[default]
383 Ok,
384 Imputed,
386}
387
388#[derive(Debug, Clone, Deserialize)]
390pub struct DeribitChartMsg {
391 pub tick: u64,
393 pub open: f64,
395 pub high: f64,
397 pub low: f64,
399 pub close: f64,
401 pub volume: f64,
403 pub cost: f64,
405 #[serde(default)]
407 pub status: DeribitChartStatus,
408}
409
410#[derive(Debug, Clone, Serialize)]
415pub struct DeribitOrderParams {
416 pub instrument_name: String,
418 #[serde(with = "rust_decimal::serde::float")]
420 pub amount: Decimal,
421 #[serde(rename = "type")]
423 pub order_type: String,
424 #[serde(skip_serializing_if = "Option::is_none")]
426 pub label: Option<String>,
427 #[serde(
429 skip_serializing_if = "Option::is_none",
430 with = "rust_decimal::serde::float_option"
431 )]
432 pub price: Option<Decimal>,
433 #[serde(skip_serializing_if = "Option::is_none")]
435 pub time_in_force: Option<String>,
436 #[serde(skip_serializing_if = "Option::is_none")]
439 pub post_only: Option<bool>,
440 #[serde(skip_serializing_if = "Option::is_none")]
443 pub reject_post_only: Option<bool>,
444 #[serde(skip_serializing_if = "Option::is_none")]
446 pub reduce_only: Option<bool>,
447 #[serde(
449 skip_serializing_if = "Option::is_none",
450 with = "rust_decimal::serde::float_option"
451 )]
452 pub trigger_price: Option<Decimal>,
453 #[serde(skip_serializing_if = "Option::is_none")]
455 pub trigger: Option<String>,
456 #[serde(
458 skip_serializing_if = "Option::is_none",
459 with = "rust_decimal::serde::float_option"
460 )]
461 pub max_show: Option<Decimal>,
462 #[serde(skip_serializing_if = "Option::is_none")]
464 pub valid_until: Option<u64>,
465}
466
467#[derive(Debug, Clone, Serialize)]
469pub struct DeribitCancelParams {
470 pub order_id: String,
472}
473
474#[derive(Debug, Clone, Serialize)]
476pub struct DeribitCancelAllByInstrumentParams {
477 pub instrument_name: String,
479 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
481 pub order_type: Option<String>,
482}
483
484#[derive(Debug, Clone, Serialize)]
489pub struct DeribitEditParams {
490 pub order_id: String,
492 #[serde(with = "rust_decimal::serde::float")]
494 pub amount: Decimal,
495 #[serde(
497 skip_serializing_if = "Option::is_none",
498 with = "rust_decimal::serde::float_option"
499 )]
500 pub price: Option<Decimal>,
501 #[serde(
503 skip_serializing_if = "Option::is_none",
504 with = "rust_decimal::serde::float_option"
505 )]
506 pub trigger_price: Option<Decimal>,
507 #[serde(skip_serializing_if = "Option::is_none")]
510 pub post_only: Option<bool>,
511 #[serde(skip_serializing_if = "Option::is_none")]
514 pub reject_post_only: Option<bool>,
515 #[serde(skip_serializing_if = "Option::is_none")]
517 pub reduce_only: Option<bool>,
518}
519
520#[derive(Debug, Clone, Serialize)]
522pub struct DeribitGetOrderStateParams {
523 pub order_id: String,
525}
526
527fn deserialize_optional_decimal_or_market<'de, D>(
532 deserializer: D,
533) -> Result<Option<Decimal>, D::Error>
534where
535 D: Deserializer<'de>,
536{
537 struct Visitor;
538
539 impl<'de> de::Visitor<'de> for Visitor {
540 type Value = Option<Decimal>;
541
542 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
543 formatter.write_str(
544 "null, a decimal as string/integer/float, or the literal \"market_price\"",
545 )
546 }
547
548 fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
549 if v.is_empty() || v == "market_price" {
550 return Ok(None);
551 }
552
553 if v.contains('e') || v.contains('E') {
554 Decimal::from_scientific(v).map(Some).map_err(E::custom)
555 } else {
556 Decimal::from_str(v).map(Some).map_err(E::custom)
557 }
558 }
559
560 fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> {
561 self.visit_str(&v)
562 }
563
564 fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
565 Ok(Some(Decimal::from(v)))
566 }
567
568 fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
569 Ok(Some(Decimal::from(v)))
570 }
571
572 fn visit_i128<E: de::Error>(self, v: i128) -> Result<Self::Value, E> {
573 Ok(Some(Decimal::from(v)))
574 }
575
576 fn visit_u128<E: de::Error>(self, v: u128) -> Result<Self::Value, E> {
577 Ok(Some(Decimal::from(v)))
578 }
579
580 fn visit_f64<E: de::Error>(self, v: f64) -> Result<Self::Value, E> {
581 if v.is_nan() || v.is_infinite() {
582 return Err(E::invalid_value(de::Unexpected::Float(v), &self));
583 }
584 Decimal::try_from(v).map(Some).map_err(E::custom)
585 }
586
587 fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
588 Ok(None)
589 }
590
591 fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
592 Ok(None)
593 }
594 }
595
596 deserializer.deserialize_any(Visitor)
597}
598
599#[derive(Debug, Clone, Deserialize)]
603pub struct DeribitOrderResponse {
604 pub order: DeribitOrderMsg,
606 #[serde(default)]
608 pub trades: Vec<DeribitUserTradeMsg>,
609}
610
611#[derive(Debug, Clone, Deserialize)]
615pub struct DeribitOrderMsg {
616 pub order_id: String,
618 pub label: Option<String>,
620 pub instrument_name: Ustr,
622 pub direction: String,
624 pub order_type: String,
626 pub order_state: String,
628 #[serde(default, deserialize_with = "deserialize_optional_decimal_or_market")]
631 pub price: Option<Decimal>,
632 #[serde(deserialize_with = "nautilus_core::serialization::deserialize_decimal")]
634 pub amount: Decimal,
635 #[serde(deserialize_with = "nautilus_core::serialization::deserialize_decimal")]
637 pub filled_amount: Decimal,
638 #[serde(
640 default,
641 deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
642 )]
643 pub average_price: Option<Decimal>,
644 pub creation_timestamp: u64,
646 pub last_update_timestamp: u64,
648 pub time_in_force: String,
650 #[serde(
652 default,
653 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
654 )]
655 pub commission: Decimal,
656 #[serde(default)]
658 pub post_only: bool,
659 #[serde(default)]
661 pub reduce_only: bool,
662 #[serde(
664 default,
665 deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
666 )]
667 pub trigger_price: Option<Decimal>,
668 pub trigger: Option<String>,
670 #[serde(
672 default,
673 deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
674 )]
675 pub max_show: Option<Decimal>,
676 #[serde(default)]
678 pub api: bool,
679 pub reject_reason: Option<String>,
681 pub cancel_reason: Option<String>,
683}
684
685#[derive(Debug, Clone, Serialize, Deserialize)]
689pub struct DeribitUserTradeMsg {
690 pub trade_id: String,
692 pub order_id: String,
694 pub instrument_name: Ustr,
696 pub direction: String,
698 #[serde(
700 serialize_with = "nautilus_core::serialization::serialize_decimal",
701 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
702 )]
703 pub price: Decimal,
704 #[serde(
706 serialize_with = "nautilus_core::serialization::serialize_decimal",
707 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
708 )]
709 pub amount: Decimal,
710 #[serde(
712 serialize_with = "nautilus_core::serialization::serialize_decimal",
713 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
714 )]
715 pub fee: Decimal,
716 pub fee_currency: String,
718 pub timestamp: u64,
720 pub trade_seq: u64,
722 pub liquidity: String,
724 pub order_type: String,
726 #[serde(
728 serialize_with = "nautilus_core::serialization::serialize_decimal",
729 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
730 )]
731 pub index_price: Decimal,
732 #[serde(
734 serialize_with = "nautilus_core::serialization::serialize_decimal",
735 deserialize_with = "nautilus_core::serialization::deserialize_decimal"
736 )]
737 pub mark_price: Decimal,
738 pub tick_direction: i8,
740 pub state: String,
742 pub label: Option<String>,
744 #[serde(default)]
746 pub reduce_only: bool,
747 #[serde(default)]
749 pub post_only: bool,
750 #[serde(default)]
752 pub liquidation: Option<String>,
753 #[serde(
755 default,
756 serialize_with = "nautilus_core::serialization::serialize_optional_decimal",
757 deserialize_with = "nautilus_core::serialization::deserialize_optional_decimal"
758 )]
759 pub profit_loss: Option<Decimal>,
760}
761
762#[derive(Debug, Clone, Deserialize)]
764pub struct DeribitPortfolioMsg {
765 pub currency: String,
767 #[serde(with = "rust_decimal::serde::float")]
769 pub equity: Decimal,
770 #[serde(with = "rust_decimal::serde::float")]
772 pub balance: Decimal,
773 #[serde(with = "rust_decimal::serde::float")]
775 pub available_funds: Decimal,
776 #[serde(with = "rust_decimal::serde::float")]
778 pub margin_balance: Decimal,
779 #[serde(with = "rust_decimal::serde::float")]
781 pub initial_margin: Decimal,
782 #[serde(with = "rust_decimal::serde::float")]
784 pub maintenance_margin: Decimal,
785}
786
787#[derive(Debug, Clone)]
789pub enum DeribitWsMessage {
790 Response(DeribitJsonRpcResponse<serde_json::Value>),
792 Notification(DeribitSubscriptionNotification<serde_json::Value>),
794 Heartbeat(DeribitHeartbeatData),
796 Error(DeribitJsonRpcError),
798 Reconnected,
800}
801
802#[derive(Debug, Clone, Serialize, Deserialize)]
804pub struct DeribitWebSocketError {
805 pub code: i64,
807 pub message: String,
809 pub timestamp: u64,
811}
812
813impl From<DeribitJsonRpcError> for DeribitWebSocketError {
814 fn from(err: DeribitJsonRpcError) -> Self {
815 Self {
816 code: err.code,
817 message: err.message,
818 timestamp: 0,
819 }
820 }
821}
822
823#[derive(Debug, Clone)]
825pub enum NautilusWsMessage {
826 Data(Vec<Data>),
828 Deltas(OrderBookDeltas),
830 Instrument(Box<InstrumentAny>),
832 FundingRates(Vec<FundingRateUpdate>),
834 OptionGreeks(OptionGreeks),
836 OrderStatusReports(Vec<OrderStatusReport>),
838 FillReports(Vec<FillReport>),
840 OrderAccepted(OrderAccepted),
842 OrderCanceled(OrderCanceled),
844 OrderExpired(OrderExpired),
846 OrderRejected(OrderRejected),
848 OrderCancelRejected(OrderCancelRejected),
850 OrderModifyRejected(OrderModifyRejected),
852 OrderUpdated(OrderUpdated),
854 AccountState(AccountState),
856 InstrumentStatus(InstrumentStatus),
858 Error(DeribitWsError),
860 Raw(serde_json::Value),
862 Reconnected,
864 Authenticated(Box<DeribitAuthResult>),
866 AuthenticationFailed(String),
868}
869
870pub fn parse_raw_message(text: &str) -> Result<DeribitWsMessage, DeribitWsError> {
876 let value: serde_json::Value =
877 serde_json::from_str(text).map_err(|e| DeribitWsError::Json(e.to_string()))?;
878
879 if let Some(method) = value.get("method").and_then(|m| m.as_str()) {
881 if method == "subscription" {
882 let notification: DeribitSubscriptionNotification<serde_json::Value> =
883 serde_json::from_value(value).map_err(|e| DeribitWsError::Json(e.to_string()))?;
884 return Ok(DeribitWsMessage::Notification(notification));
885 }
886 if method == "heartbeat"
888 && let Some(params) = value.get("params")
889 {
890 let heartbeat: DeribitHeartbeatData = serde_json::from_value(params.clone())
891 .map_err(|e| DeribitWsError::Json(e.to_string()))?;
892 return Ok(DeribitWsMessage::Heartbeat(heartbeat));
893 }
894 }
895
896 if value.get("id").is_some() {
901 let response: DeribitJsonRpcResponse<serde_json::Value> =
902 serde_json::from_value(value).map_err(|e| DeribitWsError::Json(e.to_string()))?;
903 return Ok(DeribitWsMessage::Response(response));
904 }
905
906 let response: DeribitJsonRpcResponse<serde_json::Value> =
908 serde_json::from_value(value).map_err(|e| DeribitWsError::Json(e.to_string()))?;
909 Ok(DeribitWsMessage::Response(response))
910}
911
912pub fn extract_instrument_from_channel(channel: &str) -> Option<&str> {
916 let parts: Vec<&str> = channel.split('.').collect();
917 if parts.len() >= 2 {
918 Some(parts[1])
919 } else {
920 None
921 }
922}
923
924#[cfg(test)]
925mod tests {
926 use rstest::rstest;
927
928 use super::*;
929
930 #[rstest]
931 fn test_parse_subscription_notification() {
932 let json = r#"{
933 "jsonrpc": "2.0",
934 "method": "subscription",
935 "params": {
936 "channel": "trades.BTC-PERPETUAL.raw",
937 "data": [{"trade_id": "123", "price": 50000.0}]
938 }
939 }"#;
940
941 let msg = parse_raw_message(json).unwrap();
942 assert!(matches!(msg, DeribitWsMessage::Notification(_)));
943 }
944
945 #[rstest]
946 fn test_parse_response() {
947 let json = r#"{
948 "jsonrpc": "2.0",
949 "id": 1,
950 "result": ["trades.BTC-PERPETUAL.raw"],
951 "testnet": true,
952 "usIn": 1234567890,
953 "usOut": 1234567891,
954 "usDiff": 1
955 }"#;
956
957 let msg = parse_raw_message(json).unwrap();
958 assert!(matches!(msg, DeribitWsMessage::Response(_)));
959 }
960
961 #[rstest]
962 fn test_parse_error_response() {
963 let json = r#"{
966 "jsonrpc": "2.0",
967 "id": 1,
968 "error": {
969 "code": 10028,
970 "message": "too_many_requests"
971 }
972 }"#;
973
974 let msg = parse_raw_message(json).unwrap();
975 match msg {
976 DeribitWsMessage::Response(resp) => {
977 assert!(resp.error.is_some());
978 let error = resp.error.unwrap();
979 assert_eq!(error.code, 10028);
980 assert_eq!(error.message, "too_many_requests");
981 }
982 _ => panic!("Expected Response with error, was {msg:?}"),
983 }
984 }
985
986 #[rstest]
987 fn test_extract_instrument_from_channel() {
988 assert_eq!(
989 extract_instrument_from_channel("trades.BTC-PERPETUAL.raw"),
990 Some("BTC-PERPETUAL")
991 );
992 assert_eq!(
993 extract_instrument_from_channel("book.ETH-25DEC25.raw"),
994 Some("ETH-25DEC25")
995 );
996 assert_eq!(extract_instrument_from_channel("platform_state"), None);
997 }
998}