Skip to main content

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    type Error = SocketError;
57
58    fn validate(self) -> Result<Self, SocketError>
59    where
60        Self: Sized,
61    {
62        match self {
63            Self::Subscribed => Ok(self),
64            Self::Error { code, message } => Err(SocketError::Subscribe(format!(
65                "received failure subscription response code: {code} with message: {message}",
66            ))),
67        }
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    mod de {
76        use super::*;
77
78        #[test]
79        fn test_okx_subscription_response() {
80            struct TestCase {
81                input: &'static str,
82                expected: Result<OkxSubResponse, SocketError>,
83            }
84
85            let cases = vec![
86                TestCase {
87                    // TC0: input response is subscription success
88                    input: r#"
89                {
90                    "event": "subscribe",
91                    "args": {"channel": "trades", "instId": "BTC-USD-191227"}
92                }
93                "#,
94                    expected: Ok(OkxSubResponse::Subscribed),
95                },
96                TestCase {
97                    // TC1: input response is failed subscription
98                    input: r#"
99                {
100                    "event": "error",
101                    "code": "60012",
102                    "msg": "Invalid request: {\"op\": \"subscribe\", \"args\":[{ \"channel\" : \"trades\", \"instId\" : \"BTC-USD-191227\"}]}"
103                }
104                "#,
105                    expected: Ok(OkxSubResponse::Error {
106                        code: "60012".to_string(),
107                        message: "Invalid request: {\"op\": \"subscribe\", \"args\":[{ \"channel\" : \"trades\", \"instId\" : \"BTC-USD-191227\"}]}".to_string()
108                    }),
109                },
110            ];
111
112            for (index, test) in cases.into_iter().enumerate() {
113                let actual = serde_json::from_str::<OkxSubResponse>(test.input);
114                match (actual, test.expected) {
115                    (Ok(actual), Ok(expected)) => {
116                        assert_eq!(actual, expected, "TC{} failed", index)
117                    }
118                    (Err(_), Err(_)) => {
119                        // Test passed
120                    }
121                    (actual, expected) => {
122                        // Test failed
123                        panic!(
124                            "TC{index} failed because actual != expected. \nActual: {actual:?}\nExpected: {expected:?}\n"
125                        );
126                    }
127                }
128            }
129        }
130    }
131
132    #[test]
133    fn test_validate_okx_sub_response() {
134        struct TestCase {
135            input_response: OkxSubResponse,
136            is_valid: bool,
137        }
138
139        let cases = vec![
140            TestCase {
141                // TC0: input response is subscription success
142                input_response: OkxSubResponse::Subscribed,
143                is_valid: true,
144            },
145            TestCase {
146                // TC1: input response is failed subscription
147                input_response: OkxSubResponse::Error {
148                    code: "60012".to_string(),
149                    message: "Invalid request: {\"op\": \"subscribe\", \"args\":[{ \"channel\" : \"trades\", \"instId\" : \"BTC-USD-191227\"}]}".to_string()
150                },
151                is_valid: false,
152            },
153        ];
154
155        for (index, test) in cases.into_iter().enumerate() {
156            let actual = test.input_response.validate().is_ok();
157            assert_eq!(actual, test.is_valid, "TestCase {} failed", index);
158        }
159    }
160}