nautilus_hyperliquid/websocket/
enums.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use serde::{Deserialize, Serialize};
17use strum::{AsRefStr, Display, EnumIter, EnumString};
18
19/// WebSocket channel names for Hyperliquid.
20#[derive(
21    Clone,
22    Debug,
23    Display,
24    PartialEq,
25    Eq,
26    Hash,
27    AsRefStr,
28    EnumIter,
29    EnumString,
30    Serialize,
31    Deserialize,
32)]
33pub enum HyperliquidWsChannel {
34    #[serde(rename = "subscriptionResponse")]
35    SubscriptionResponse,
36    #[serde(rename = "trades")]
37    Trades,
38    #[serde(rename = "l2Book")]
39    L2Book,
40    #[serde(rename = "bbo")]
41    Bbo,
42    #[serde(rename = "orderUpdates")]
43    OrderUpdates,
44    #[serde(rename = "userEvents")]
45    UserEvents,
46    #[serde(rename = "userFills")]
47    UserFills,
48    #[serde(rename = "userFundings")]
49    UserFundings,
50    #[serde(rename = "userNonFundingLedgerUpdates")]
51    UserNonFundingLedgerUpdates,
52    #[serde(rename = "post")]
53    Post,
54    #[serde(rename = "pong")]
55    Pong,
56}
57
58impl HyperliquidWsChannel {
59    /// Returns the string representation of the channel.
60    pub fn as_str(&self) -> &'static str {
61        match self {
62            Self::SubscriptionResponse => "subscriptionResponse",
63            Self::Trades => "trades",
64            Self::L2Book => "l2Book",
65            Self::Bbo => "bbo",
66            Self::OrderUpdates => "orderUpdates",
67            Self::UserEvents => "userEvents",
68            Self::UserFills => "userFills",
69            Self::UserFundings => "userFundings",
70            Self::UserNonFundingLedgerUpdates => "userNonFundingLedgerUpdates",
71            Self::Post => "post",
72            Self::Pong => "pong",
73        }
74    }
75
76    /// Returns true if this is a public channel (does not require authentication).
77    pub fn is_public(&self) -> bool {
78        matches!(
79            self,
80            Self::SubscriptionResponse | Self::Trades | Self::L2Book | Self::Bbo | Self::Pong
81        )
82    }
83
84    /// Returns true if this is a private channel (requires authentication).
85    pub fn is_private(&self) -> bool {
86        !self.is_public()
87    }
88}
89
90////////////////////////////////////////////////////////////////////////////////
91// Tests
92////////////////////////////////////////////////////////////////////////////////
93
94#[cfg(test)]
95mod tests {
96    use rstest::rstest;
97    use serde_json;
98
99    use super::*;
100
101    #[rstest]
102    #[case(HyperliquidWsChannel::Trades, r#""trades""#)]
103    #[case(HyperliquidWsChannel::L2Book, r#""l2Book""#)]
104    #[case(HyperliquidWsChannel::UserFills, r#""userFills""#)]
105    #[case(HyperliquidWsChannel::Bbo, r#""bbo""#)]
106    #[case(
107        HyperliquidWsChannel::SubscriptionResponse,
108        r#""subscriptionResponse""#
109    )]
110    fn test_channel_serialization(#[case] channel: HyperliquidWsChannel, #[case] expected: &str) {
111        assert_eq!(serde_json::to_string(&channel).unwrap(), expected);
112    }
113
114    #[rstest]
115    #[case(r#""trades""#, HyperliquidWsChannel::Trades)]
116    #[case(r#""l2Book""#, HyperliquidWsChannel::L2Book)]
117    #[case(r#""userEvents""#, HyperliquidWsChannel::UserEvents)]
118    #[case(r#""bbo""#, HyperliquidWsChannel::Bbo)]
119    #[case(r#""pong""#, HyperliquidWsChannel::Pong)]
120    fn test_channel_deserialization(#[case] json: &str, #[case] expected: HyperliquidWsChannel) {
121        assert_eq!(
122            serde_json::from_str::<HyperliquidWsChannel>(json).unwrap(),
123            expected
124        );
125    }
126
127    #[rstest]
128    #[case(HyperliquidWsChannel::Trades, "trades")]
129    #[case(HyperliquidWsChannel::L2Book, "l2Book")]
130    #[case(HyperliquidWsChannel::UserFills, "userFills")]
131    #[case(
132        HyperliquidWsChannel::UserNonFundingLedgerUpdates,
133        "userNonFundingLedgerUpdates"
134    )]
135    #[case(HyperliquidWsChannel::Bbo, "bbo")]
136    fn test_as_str_method(#[case] channel: HyperliquidWsChannel, #[case] expected: &str) {
137        assert_eq!(channel.as_str(), expected);
138    }
139
140    #[test]
141    fn test_display_trait() {
142        assert_eq!(format!("{}", HyperliquidWsChannel::Trades), "Trades");
143        assert_eq!(format!("{}", HyperliquidWsChannel::L2Book), "L2Book");
144        assert_eq!(format!("{}", HyperliquidWsChannel::UserFills), "UserFills");
145    }
146
147    #[test]
148    fn test_is_public_channel() {
149        assert!(HyperliquidWsChannel::Trades.is_public());
150        assert!(HyperliquidWsChannel::L2Book.is_public());
151        assert!(HyperliquidWsChannel::Bbo.is_public());
152        assert!(HyperliquidWsChannel::SubscriptionResponse.is_public());
153        assert!(HyperliquidWsChannel::Pong.is_public());
154
155        assert!(!HyperliquidWsChannel::OrderUpdates.is_public());
156        assert!(!HyperliquidWsChannel::UserEvents.is_public());
157        assert!(!HyperliquidWsChannel::UserFills.is_public());
158        assert!(!HyperliquidWsChannel::UserFundings.is_public());
159        assert!(!HyperliquidWsChannel::UserNonFundingLedgerUpdates.is_public());
160        assert!(!HyperliquidWsChannel::Post.is_public());
161    }
162
163    #[test]
164    fn test_is_private_channel() {
165        assert!(!HyperliquidWsChannel::Trades.is_private());
166        assert!(!HyperliquidWsChannel::L2Book.is_private());
167        assert!(!HyperliquidWsChannel::Bbo.is_private());
168
169        assert!(HyperliquidWsChannel::OrderUpdates.is_private());
170        assert!(HyperliquidWsChannel::UserEvents.is_private());
171        assert!(HyperliquidWsChannel::UserFills.is_private());
172        assert!(HyperliquidWsChannel::UserFundings.is_private());
173        assert!(HyperliquidWsChannel::UserNonFundingLedgerUpdates.is_private());
174        assert!(HyperliquidWsChannel::Post.is_private());
175    }
176
177    #[test]
178    fn test_enum_iter() {
179        use strum::IntoEnumIterator;
180
181        let channels: Vec<HyperliquidWsChannel> = HyperliquidWsChannel::iter().collect();
182        assert_eq!(channels.len(), 11);
183        assert!(channels.contains(&HyperliquidWsChannel::Trades));
184        assert!(channels.contains(&HyperliquidWsChannel::L2Book));
185        assert!(channels.contains(&HyperliquidWsChannel::UserFills));
186    }
187
188    #[test]
189    fn test_from_str() {
190        use std::str::FromStr;
191
192        assert_eq!(
193            HyperliquidWsChannel::from_str("Trades").unwrap(),
194            HyperliquidWsChannel::Trades
195        );
196        assert_eq!(
197            HyperliquidWsChannel::from_str("L2Book").unwrap(),
198            HyperliquidWsChannel::L2Book
199        );
200        assert_eq!(
201            HyperliquidWsChannel::from_str("UserFills").unwrap(),
202            HyperliquidWsChannel::UserFills
203        );
204
205        assert!(HyperliquidWsChannel::from_str("InvalidChannel").is_err());
206    }
207}