barter_data/exchange/kraken/message.rs
1use crate::Identifier;
2use barter_integration::subscription::SubscriptionId;
3use serde::{Deserialize, Serialize};
4
5/// [`Kraken`](super::Kraken) message variants that can be received over
6/// [`WebSocket`](barter_integration::protocol::websocket::WebSocket).
7///
8/// ### Raw Payload Examples
9/// See docs: <https://docs.kraken.com/websockets/#overview>
10///
11/// #### OrderBookL1
12/// See docs: <https://docs.kraken.com/websockets/#message-spread>
13/// ```json
14/// [
15/// 0,
16/// [
17/// "5698.40000",
18/// "5700.00000",
19/// "1542057299.545897",
20/// "1.01234567",
21/// "0.98765432"
22/// ],
23/// "spread",
24/// "XBT/USD"
25/// ]
26/// ```
27///
28/// #### Trades
29/// See docs: <https://docs.kraken.com/websockets/#message-trade>
30/// ```json
31/// [
32/// 0,
33/// [
34/// [
35/// "5541.20000",
36/// "0.15850568",
37/// "1534614057.321597",
38/// "s",
39/// "l",
40/// ""
41/// ],
42/// [
43/// "6060.00000",
44/// "0.02455000",
45/// "1534614057.324998",
46/// "b",
47/// "l",
48/// ""
49/// ]
50/// ],
51/// "trade",
52/// "XBT/USD"
53/// ]
54/// ```
55///
56/// #### Heartbeat
57/// See docs: <https://docs.kraken.com/websockets/#message-heartbeat>
58/// ```json
59/// {
60/// "event": "heartbeat"
61/// }
62/// ```
63///
64/// #### KrakenError Generic
65/// See docs: <https://docs.kraken.com/websockets/#errortypes>
66/// ```json
67/// {
68/// "errorMessage": "Malformed request",
69/// "event": "error"
70/// }
71/// ```
72#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
73#[serde(untagged, rename_all = "snake_case")]
74pub enum KrakenMessage<T> {
75 Data(T),
76 Event(KrakenEvent),
77}
78
79impl<T> Identifier<Option<SubscriptionId>> for KrakenMessage<T>
80where
81 T: Identifier<Option<SubscriptionId>>,
82{
83 fn id(&self) -> Option<SubscriptionId> {
84 match self {
85 Self::Data(data) => data.id(),
86 Self::Event(_) => None,
87 }
88 }
89}
90
91/// [`Kraken`](super::Kraken) messages received over the WebSocket which are not subscription data.
92///
93/// eg/ [`Kraken`](super::Kraken) sends a [`KrakenEvent::Heartbeat`] if no subscription traffic
94/// has been sent within the last second.
95///
96/// See [`KrakenMessage`] for full raw payload examples.
97///
98/// See docs: <https://docs.kraken.com/websockets/#message-heartbeat>
99#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)]
100#[serde(tag = "event", rename_all = "camelCase")]
101pub enum KrakenEvent {
102 Heartbeat,
103 Error(KrakenError),
104}
105
106/// [`Kraken`](super::Kraken) generic error message String received over the WebSocket.
107///
108/// Note that since the [`KrakenError`] is only made up of a renamed message String field, it can
109/// be used flexible as a [`KrakenSubResponse::Error`](super::subscription::KrakenSubResponse)
110/// or as a generic error received over the WebSocket while subscriptions are active.
111///
112/// See [`KrakenMessage`] for full raw payload examples.
113///
114/// See docs: <https://docs.kraken.com/websockets/#errortypes> <br>
115/// See docs: <https://docs.kraken.com/websockets/#message-subscriptionStatus>
116#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)]
117pub struct KrakenError {
118 #[serde(alias = "errorMessage")]
119 pub message: String,
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 mod de {
127 use super::*;
128 use barter_integration::error::SocketError;
129
130 #[test]
131 fn test_kraken_message_event() {
132 struct TestCase {
133 input: &'static str,
134 expected: Result<KrakenMessage<()>, SocketError>,
135 }
136
137 let tests = vec![
138 TestCase {
139 // TC0: valid KrakenTrades::Event(KrakenEvent::Heartbeat)
140 input: r#"{"event": "heartbeat"}"#,
141 expected: Ok(KrakenMessage::Event(KrakenEvent::Heartbeat)),
142 },
143 TestCase {
144 // TC1: valid KrakenTrades::Event(KrakenEvent::Error(KrakenError))
145 input: r#"{"errorMessage": "Malformed request", "event": "error"}"#,
146 expected: Ok(KrakenMessage::Event(KrakenEvent::Error(KrakenError {
147 message: "Malformed request".to_string(),
148 }))),
149 },
150 ];
151
152 for (index, test) in tests.into_iter().enumerate() {
153 let actual = serde_json::from_str::<KrakenMessage<()>>(test.input);
154 match (actual, test.expected) {
155 (Ok(actual), Ok(expected)) => {
156 assert_eq!(actual, expected, "TC{} failed", index)
157 }
158 (Err(_), Err(_)) => {
159 // Test passed
160 }
161 (actual, expected) => {
162 // Test failed
163 panic!(
164 "TC{index} failed because actual != expected. \nActual: {actual:?}\nExpected: {expected:?}\n"
165 );
166 }
167 }
168 }
169 }
170 }
171}