barter_data/exchange/okx/
subscription.rs

1use super::{channel::OkxChannel, market::OkxMarket};
2use crate::exchange::subscription::ExchangeSub;
3use barter_integration::{Validator, error::SocketError};
4use serde::{Deserialize, Serialize, Serializer, ser::SerializeStruct};
5
6// Implement custom Serialize to assist aesthetics of <Okx as Connector>::requests() function.
7impl Serialize for ExchangeSub<OkxChannel, OkxMarket> {
8    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
9    where
10        S: Serializer,
11    {
12        let mut state = serializer.serialize_struct("OkxSubArg", 2)?;
13        state.serialize_field("channel", self.channel.as_ref())?;
14        state.serialize_field("instId", self.market.as_ref())?;
15        state.end()
16    }
17}
18
19/// [`Okx`](super::Okx) WebSocket subscription response.
20///
21/// ### Raw Payload Examples
22/// #### Subscription Trades Ok Response
23/// ```json
24/// {
25///   "event": "subscribe",
26///   "args": {
27///     "channel": "trades",
28///     "instId": "BTC-USD-191227"
29///   }
30/// }
31/// ```
32///
33/// #### Subscription Trades Error Response
34/// ```json
35/// {
36///   "event": "error",
37///   "code": "60012",
38///   "msg": "Invalid request: {\"op\": \"subscribe\", \"args\":[{ \"channel\" : \"trades\", \"instId\" : \"BTC-USD-191227\"}]}"
39/// }
40/// ```
41///
42/// See docs: <https://www.okx.com/docs-v5/en/#websocket-api-subscribe>
43#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)]
44#[serde(tag = "event", rename_all = "lowercase")]
45pub enum OkxSubResponse {
46    #[serde(rename = "subscribe")]
47    Subscribed,
48    Error {
49        code: String,
50        #[serde(rename = "msg")]
51        message: String,
52    },
53}
54
55impl Validator for OkxSubResponse {
56    fn validate(self) -> Result<Self, SocketError>
57    where
58        Self: Sized,
59    {
60        match self {
61            Self::Subscribed => Ok(self),
62            Self::Error { code, message } => Err(SocketError::Subscribe(format!(
63                "received failure subscription response code: {code} with message: {message}",
64            ))),
65        }
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    mod de {
74        use super::*;
75
76        #[test]
77        fn test_okx_subscription_response() {
78            struct TestCase {
79                input: &'static str,
80                expected: Result<OkxSubResponse, SocketError>,
81            }
82
83            let cases = vec![
84                TestCase {
85                    // TC0: input response is subscription success
86                    input: r#"
87                {
88                    "event": "subscribe",
89                    "args": {"channel": "trades", "instId": "BTC-USD-191227"}
90                }
91                "#,
92                    expected: Ok(OkxSubResponse::Subscribed),
93                },
94                TestCase {
95                    // TC1: input response is failed subscription
96                    input: r#"
97                {
98                    "event": "error",
99                    "code": "60012",
100                    "msg": "Invalid request: {\"op\": \"subscribe\", \"args\":[{ \"channel\" : \"trades\", \"instId\" : \"BTC-USD-191227\"}]}"
101                }
102                "#,
103                    expected: Ok(OkxSubResponse::Error {
104                        code: "60012".to_string(),
105                        message: "Invalid request: {\"op\": \"subscribe\", \"args\":[{ \"channel\" : \"trades\", \"instId\" : \"BTC-USD-191227\"}]}".to_string()
106                    }),
107                },
108            ];
109
110            for (index, test) in cases.into_iter().enumerate() {
111                let actual = serde_json::from_str::<OkxSubResponse>(test.input);
112                match (actual, test.expected) {
113                    (Ok(actual), Ok(expected)) => {
114                        assert_eq!(actual, expected, "TC{} failed", index)
115                    }
116                    (Err(_), Err(_)) => {
117                        // Test passed
118                    }
119                    (actual, expected) => {
120                        // Test failed
121                        panic!(
122                            "TC{index} failed because actual != expected. \nActual: {actual:?}\nExpected: {expected:?}\n"
123                        );
124                    }
125                }
126            }
127        }
128    }
129
130    #[test]
131    fn test_validate_okx_sub_response() {
132        struct TestCase {
133            input_response: OkxSubResponse,
134            is_valid: bool,
135        }
136
137        let cases = vec![
138            TestCase {
139                // TC0: input response is subscription success
140                input_response: OkxSubResponse::Subscribed,
141                is_valid: true,
142            },
143            TestCase {
144                // TC1: input response is failed subscription
145                input_response: OkxSubResponse::Error {
146                    code: "60012".to_string(),
147                    message: "Invalid request: {\"op\": \"subscribe\", \"args\":[{ \"channel\" : \"trades\", \"instId\" : \"BTC-USD-191227\"}]}".to_string()
148                },
149                is_valid: false,
150            },
151        ];
152
153        for (index, test) in cases.into_iter().enumerate() {
154            let actual = test.input_response.validate().is_ok();
155            assert_eq!(actual, test.is_valid, "TestCase {} failed", index);
156        }
157    }
158}