Skip to main content

rustrade_data/exchange/kraken/
subscription.rs

1use super::message::KrakenError;
2use rustrade_integration::{Validator, error::SocketError};
3use serde::{Deserialize, Serialize};
4
5/// [`Kraken`](super::Kraken) message received in response to WebSocket subscription requests.
6///
7/// ### Raw Payload Examples
8/// See docs: <https://docs.kraken.com/websockets/#message-subscriptionStatus>
9/// #### Subscription Trade Success
10/// ```json
11/// {
12///   "channelID": 10001,
13///   "channelName": "ticker",
14///   "event": "subscriptionStatus",
15///   "pair": "XBT/EUR",
16///   "status": "subscribed",
17///   "subscription": {
18///     "name": "ticker"
19///   }
20/// }
21/// ```
22///
23/// #### Subscription Trade Failure
24/// ```json
25/// {
26///   "errorMessage": "Subscription name invalid",
27///   "event": "subscriptionStatus",
28///   "pair": "XBT/USD",
29///   "status": "error",
30///   "subscription": {
31///     "name": "trades"
32///   }
33/// }
34/// ```
35#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)]
36#[serde(tag = "status", rename_all = "camelCase")]
37pub enum KrakenSubResponse {
38    Subscribed {
39        #[serde(alias = "channelID")]
40        channel_id: u64,
41        #[serde(alias = "channelName")]
42        channel_name: String,
43        pair: String,
44    },
45    Error(KrakenError),
46}
47
48impl Validator for KrakenSubResponse {
49    type Error = SocketError;
50
51    fn validate(self) -> Result<Self, SocketError>
52    where
53        Self: Sized,
54    {
55        match &self {
56            KrakenSubResponse::Subscribed { .. } => Ok(self),
57            KrakenSubResponse::Error(error) => Err(SocketError::Subscribe(format!(
58                "received failure subscription response: {}",
59                error.message
60            ))),
61        }
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    mod de {
70        use super::*;
71
72        #[test]
73        fn test_kraken_sub_response() {
74            struct TestCase {
75                input: &'static str,
76                expected: Result<KrakenSubResponse, SocketError>,
77            }
78
79            let cases = vec![
80                TestCase {
81                    // TC0: input response is Subscribed
82                    input: r#"
83                    {
84                        "channelID": 10001,
85                        "channelName": "ticker",
86                        "event": "subscriptionStatus",
87                        "pair": "XBT/EUR",
88                        "status": "subscribed",
89                        "subscription": {
90                            "name": "ticker"
91                        }
92                    }
93                    "#,
94                    expected: Ok(KrakenSubResponse::Subscribed {
95                        channel_id: 10001,
96                        channel_name: "ticker".to_string(),
97                        pair: "XBT/EUR".to_string(),
98                    }),
99                },
100                TestCase {
101                    // TC1: input response is failed subscription
102                    input: r#"
103                    {
104                        "errorMessage": "Subscription name invalid",
105                        "event": "subscriptionStatus",
106                        "pair": "XBT/USD",
107                        "status": "error",
108                        "subscription": {
109                            "name": "trades"
110                        }
111                    }
112                    "#,
113                    expected: Ok(KrakenSubResponse::Error(KrakenError {
114                        message: "Subscription name invalid".to_string(),
115                    })),
116                },
117            ];
118
119            for (index, test) in cases.into_iter().enumerate() {
120                let actual = serde_json::from_str::<KrakenSubResponse>(test.input);
121                match (actual, test.expected) {
122                    (Ok(actual), Ok(expected)) => {
123                        assert_eq!(actual, expected, "TC{} failed", index)
124                    }
125                    (Err(_), Err(_)) => {
126                        // Test passed
127                    }
128                    (actual, expected) => {
129                        // Test failed
130                        panic!(
131                            "TC{index} failed because actual != expected. \nActual: {actual:?}\nExpected: {expected:?}\n"
132                        );
133                    }
134                }
135            }
136        }
137    }
138
139    #[test]
140    fn test_kraken_sub_response_validate() {
141        struct TestCase {
142            input_response: KrakenSubResponse,
143            is_valid: bool,
144        }
145
146        let cases = vec![
147            TestCase {
148                // TC0: input response is successful subscription
149                input_response: KrakenSubResponse::Subscribed {
150                    channel_id: 10001,
151                    channel_name: "ticker".to_string(),
152                    pair: "XBT/EUR".to_string(),
153                },
154                is_valid: true,
155            },
156            TestCase {
157                // TC1: input response is failed subscription
158                input_response: KrakenSubResponse::Error(KrakenError {
159                    message: "Subscription name invalid".to_string(),
160                }),
161                is_valid: false,
162            },
163        ];
164
165        for (index, test) in cases.into_iter().enumerate() {
166            let actual = test.input_response.validate().is_ok();
167            assert_eq!(actual, test.is_valid, "TestCase {} failed", index);
168        }
169    }
170}