1use crate::{Category, Path};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29#[non_exhaustive]
30pub enum Environment {
31 Mainnet,
33 MainnetBytick,
36 Testnet,
38 Demo,
41 Turkey,
43 Kazakhstan,
45 Georgia,
47 Indonesia,
49 Custom {
52 api: &'static str,
54 stream: &'static str,
56 },
57}
58
59impl Environment {
60 pub fn api_url(&self) -> &'static str {
62 match self {
63 Self::Mainnet => "https://api.bybit.com",
64 Self::MainnetBytick => "https://api.bytick.com",
65 Self::Testnet => "https://api-testnet.bybit.com",
66 Self::Demo => "https://api-demo.bybit.com",
67 Self::Turkey => "https://api.bybit.tr",
68 Self::Kazakhstan => "https://api.bybit.kz",
69 Self::Georgia => "https://api.bybitgeorgia.ge",
70 Self::Indonesia => "https://api.bybit.id",
71 Self::Custom { api, .. } => api,
72 }
73 }
74
75 pub fn public_stream_url(&self, category: Category) -> String {
78 let path = match category {
79 Category::Spot => Path::PublicSpot,
80 Category::Linear => Path::PublicLinear,
81 Category::Inverse => Path::PublicInverse,
82 Category::Option => Path::PublicOption,
83 };
84 format!("{}{path}", self.public_stream_base())
85 }
86
87 pub fn private_stream_url(&self) -> String {
90 format!("{}{}", self.private_stream_base(), Path::Private)
91 }
92
93 pub fn trade_stream_url(&self) -> Option<String> {
97 match self {
98 Self::Demo | Self::Georgia | Self::Indonesia => None,
99 _ => Some(format!("{}{}", self.private_stream_base(), Path::Trade)),
100 }
101 }
102
103 fn public_stream_base(&self) -> &'static str {
104 match self {
105 Self::Demo => Self::Mainnet.private_stream_base(),
107 _ => self.private_stream_base(),
108 }
109 }
110
111 fn private_stream_base(&self) -> &'static str {
112 match self {
113 Self::Mainnet | Self::MainnetBytick => "wss://stream.bybit.com",
114 Self::Testnet => "wss://stream-testnet.bybit.com",
115 Self::Demo => "wss://stream-demo.bybit.com",
116 Self::Turkey => "wss://stream.bybit.tr",
117 Self::Kazakhstan => "wss://stream.bybit.kz",
118 Self::Georgia => "wss://stream.bybitgeorgia.ge",
119 Self::Indonesia => "wss://stream.bybit.id",
120 Self::Custom { stream, .. } => stream,
121 }
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn urls_of_every_environment() {
131 let cases = [
132 (
133 Environment::Mainnet,
134 "https://api.bybit.com",
135 "wss://stream.bybit.com/v5/public/spot",
136 "wss://stream.bybit.com/v5/private",
137 ),
138 (
139 Environment::MainnetBytick,
140 "https://api.bytick.com",
141 "wss://stream.bybit.com/v5/public/spot",
142 "wss://stream.bybit.com/v5/private",
143 ),
144 (
145 Environment::Testnet,
146 "https://api-testnet.bybit.com",
147 "wss://stream-testnet.bybit.com/v5/public/spot",
148 "wss://stream-testnet.bybit.com/v5/private",
149 ),
150 (
151 Environment::Demo,
152 "https://api-demo.bybit.com",
153 "wss://stream.bybit.com/v5/public/spot",
154 "wss://stream-demo.bybit.com/v5/private",
155 ),
156 (
157 Environment::Turkey,
158 "https://api.bybit.tr",
159 "wss://stream.bybit.tr/v5/public/spot",
160 "wss://stream.bybit.tr/v5/private",
161 ),
162 (
163 Environment::Kazakhstan,
164 "https://api.bybit.kz",
165 "wss://stream.bybit.kz/v5/public/spot",
166 "wss://stream.bybit.kz/v5/private",
167 ),
168 (
169 Environment::Georgia,
170 "https://api.bybitgeorgia.ge",
171 "wss://stream.bybitgeorgia.ge/v5/public/spot",
172 "wss://stream.bybitgeorgia.ge/v5/private",
173 ),
174 (
175 Environment::Indonesia,
176 "https://api.bybit.id",
177 "wss://stream.bybit.id/v5/public/spot",
178 "wss://stream.bybit.id/v5/private",
179 ),
180 (
181 Environment::Custom {
182 api: "https://api.example.com",
183 stream: "wss://stream.example.com",
184 },
185 "https://api.example.com",
186 "wss://stream.example.com/v5/public/spot",
187 "wss://stream.example.com/v5/private",
188 ),
189 ];
190
191 assert_eq!(
192 Environment::Mainnet.trade_stream_url().as_deref(),
193 Some("wss://stream.bybit.com/v5/trade")
194 );
195 assert_eq!(
196 Environment::Testnet.trade_stream_url().as_deref(),
197 Some("wss://stream-testnet.bybit.com/v5/trade")
198 );
199 assert_eq!(Environment::Demo.trade_stream_url(), None);
200
201 for (env, api, public, private) in cases {
202 assert_eq!(env.api_url(), api, "{env:?}");
203 assert_eq!(env.public_stream_url(Category::Spot), public, "{env:?}");
204 assert_eq!(env.private_stream_url(), private, "{env:?}");
205 }
206 }
207
208 #[test]
209 fn public_stream_path_follows_the_category() {
210 let env = Environment::Mainnet;
211
212 assert!(
213 env.public_stream_url(Category::Linear)
214 .ends_with("/v5/public/linear")
215 );
216 assert!(
217 env.public_stream_url(Category::Inverse)
218 .ends_with("/v5/public/inverse")
219 );
220 assert!(
221 env.public_stream_url(Category::Option)
222 .ends_with("/v5/public/option")
223 );
224 }
225}