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}