Skip to main content

nautilus_okx/common/
urls.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 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
16//! URL helpers and endpoint metadata for OKX services.
17
18use crate::common::enums::{OKXEnvironment, OKXRegion};
19
20// Global region (accounts registered on www.okx.com).
21const OKX_HTTP_URL: &str = "https://www.okx.com";
22const OKX_WS_PUBLIC_URL: &str = "wss://ws.okx.com:8443/ws/v5/public";
23const OKX_WS_PRIVATE_URL: &str = "wss://ws.okx.com:8443/ws/v5/private";
24const OKX_WS_BUSINESS_URL: &str = "wss://ws.okx.com:8443/ws/v5/business";
25const OKX_DEMO_WS_PUBLIC_URL: &str = "wss://wspap.okx.com:8443/ws/v5/public";
26const OKX_DEMO_WS_PRIVATE_URL: &str = "wss://wspap.okx.com:8443/ws/v5/private";
27const OKX_DEMO_WS_BUSINESS_URL: &str = "wss://wspap.okx.com:8443/ws/v5/business";
28
29// EEA region (European Economic Area, accounts registered on my.okx.com).
30const OKX_EEA_HTTP_URL: &str = "https://eea.okx.com";
31const OKX_EEA_WS_PUBLIC_URL: &str = "wss://wseea.okx.com:8443/ws/v5/public";
32const OKX_EEA_WS_PRIVATE_URL: &str = "wss://wseea.okx.com:8443/ws/v5/private";
33const OKX_EEA_WS_BUSINESS_URL: &str = "wss://wseea.okx.com:8443/ws/v5/business";
34const OKX_EEA_DEMO_WS_PUBLIC_URL: &str = "wss://wseeapap.okx.com:8443/ws/v5/public";
35const OKX_EEA_DEMO_WS_PRIVATE_URL: &str = "wss://wseeapap.okx.com:8443/ws/v5/private";
36const OKX_EEA_DEMO_WS_BUSINESS_URL: &str = "wss://wseeapap.okx.com:8443/ws/v5/business";
37
38// US region (United States and Australia, accounts registered on app.okx.com).
39const OKX_US_HTTP_URL: &str = "https://us.okx.com";
40const OKX_US_WS_PUBLIC_URL: &str = "wss://wsus.okx.com:8443/ws/v5/public";
41const OKX_US_WS_PRIVATE_URL: &str = "wss://wsus.okx.com:8443/ws/v5/private";
42const OKX_US_WS_BUSINESS_URL: &str = "wss://wsus.okx.com:8443/ws/v5/business";
43const OKX_US_DEMO_WS_PUBLIC_URL: &str = "wss://wsuspap.okx.com:8443/ws/v5/public";
44const OKX_US_DEMO_WS_PRIVATE_URL: &str = "wss://wsuspap.okx.com:8443/ws/v5/private";
45const OKX_US_DEMO_WS_BUSINESS_URL: &str = "wss://wsuspap.okx.com:8443/ws/v5/business";
46
47/// OKX endpoint types for determining URL and authentication requirements.
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum OKXEndpointType {
50    Public,
51    Private,
52    Business,
53}
54
55/// Checks if endpoint requires authentication.
56pub fn requires_authentication(endpoint_type: OKXEndpointType) -> bool {
57    matches!(
58        endpoint_type,
59        OKXEndpointType::Private | OKXEndpointType::Business
60    )
61}
62
63/// Returns the HTTP base URL for the given region.
64///
65/// The REST host is region-only; demo trading reuses the live host together
66/// with the `x-simulated-trading` header.
67#[must_use]
68pub const fn get_http_base_url(region: OKXRegion) -> &'static str {
69    match region {
70        OKXRegion::Global => OKX_HTTP_URL,
71        OKXRegion::Eea => OKX_EEA_HTTP_URL,
72        OKXRegion::Us => OKX_US_HTTP_URL,
73    }
74}
75
76/// Returns the WebSocket base URL for public data (market data).
77#[must_use]
78pub fn get_ws_base_url_public(region: OKXRegion, environment: OKXEnvironment) -> &'static str {
79    match (region, environment) {
80        (OKXRegion::Global, OKXEnvironment::Live) => OKX_WS_PUBLIC_URL,
81        (OKXRegion::Global, OKXEnvironment::Demo) => OKX_DEMO_WS_PUBLIC_URL,
82        (OKXRegion::Eea, OKXEnvironment::Live) => OKX_EEA_WS_PUBLIC_URL,
83        (OKXRegion::Eea, OKXEnvironment::Demo) => OKX_EEA_DEMO_WS_PUBLIC_URL,
84        (OKXRegion::Us, OKXEnvironment::Live) => OKX_US_WS_PUBLIC_URL,
85        (OKXRegion::Us, OKXEnvironment::Demo) => OKX_US_DEMO_WS_PUBLIC_URL,
86    }
87}
88
89/// Returns the WebSocket base URL for private data (account/order management).
90#[must_use]
91pub fn get_ws_base_url_private(region: OKXRegion, environment: OKXEnvironment) -> &'static str {
92    match (region, environment) {
93        (OKXRegion::Global, OKXEnvironment::Live) => OKX_WS_PRIVATE_URL,
94        (OKXRegion::Global, OKXEnvironment::Demo) => OKX_DEMO_WS_PRIVATE_URL,
95        (OKXRegion::Eea, OKXEnvironment::Live) => OKX_EEA_WS_PRIVATE_URL,
96        (OKXRegion::Eea, OKXEnvironment::Demo) => OKX_EEA_DEMO_WS_PRIVATE_URL,
97        (OKXRegion::Us, OKXEnvironment::Live) => OKX_US_WS_PRIVATE_URL,
98        (OKXRegion::Us, OKXEnvironment::Demo) => OKX_US_DEMO_WS_PRIVATE_URL,
99    }
100}
101
102/// Returns the WebSocket base URL for business data (bars/candlesticks).
103#[must_use]
104pub fn get_ws_base_url_business(region: OKXRegion, environment: OKXEnvironment) -> &'static str {
105    match (region, environment) {
106        (OKXRegion::Global, OKXEnvironment::Live) => OKX_WS_BUSINESS_URL,
107        (OKXRegion::Global, OKXEnvironment::Demo) => OKX_DEMO_WS_BUSINESS_URL,
108        (OKXRegion::Eea, OKXEnvironment::Live) => OKX_EEA_WS_BUSINESS_URL,
109        (OKXRegion::Eea, OKXEnvironment::Demo) => OKX_EEA_DEMO_WS_BUSINESS_URL,
110        (OKXRegion::Us, OKXEnvironment::Live) => OKX_US_WS_BUSINESS_URL,
111        (OKXRegion::Us, OKXEnvironment::Demo) => OKX_US_DEMO_WS_BUSINESS_URL,
112    }
113}
114
115/// Derives a WebSocket URL for a given channel from a base URL.
116///
117/// Replaces the last path segment (`/public`, `/private`, or `/business`)
118/// with the target channel. If no recognized segment is found, appends
119/// `/{channel}` to the path.
120#[must_use]
121pub fn derive_ws_url(base_url: &str, channel: &str) -> String {
122    let url = base_url.trim_end_matches('/');
123    for suffix in ["/public", "/private", "/business"] {
124        if let Some(base) = url.strip_suffix(suffix) {
125            return format!("{base}/{channel}");
126        }
127    }
128    format!("{url}/{channel}")
129}
130
131/// Returns WebSocket URL by endpoint type.
132#[must_use]
133pub fn get_ws_url(
134    endpoint_type: OKXEndpointType,
135    region: OKXRegion,
136    environment: OKXEnvironment,
137) -> &'static str {
138    match endpoint_type {
139        OKXEndpointType::Public => get_ws_base_url_public(region, environment),
140        OKXEndpointType::Private => get_ws_base_url_private(region, environment),
141        OKXEndpointType::Business => get_ws_base_url_business(region, environment),
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use rstest::rstest;
148
149    use super::*;
150
151    #[rstest]
152    fn test_endpoint_authentication() {
153        assert!(!requires_authentication(OKXEndpointType::Public));
154        assert!(requires_authentication(OKXEndpointType::Private));
155        assert!(requires_authentication(OKXEndpointType::Business));
156    }
157
158    #[rstest]
159    #[case(OKXRegion::Global, "https://www.okx.com")]
160    #[case(OKXRegion::Eea, "https://eea.okx.com")]
161    #[case(OKXRegion::Us, "https://us.okx.com")]
162    fn test_http_base_url(#[case] region: OKXRegion, #[case] expected: &str) {
163        assert_eq!(get_http_base_url(region), expected);
164    }
165
166    #[rstest]
167    #[case(
168        OKXRegion::Global,
169        "wss://ws.okx.com:8443/ws/v5/public",
170        "wss://ws.okx.com:8443/ws/v5/private",
171        "wss://ws.okx.com:8443/ws/v5/business"
172    )]
173    #[case(
174        OKXRegion::Eea,
175        "wss://wseea.okx.com:8443/ws/v5/public",
176        "wss://wseea.okx.com:8443/ws/v5/private",
177        "wss://wseea.okx.com:8443/ws/v5/business"
178    )]
179    #[case(
180        OKXRegion::Us,
181        "wss://wsus.okx.com:8443/ws/v5/public",
182        "wss://wsus.okx.com:8443/ws/v5/private",
183        "wss://wsus.okx.com:8443/ws/v5/business"
184    )]
185    fn test_ws_urls_live(
186        #[case] region: OKXRegion,
187        #[case] public: &str,
188        #[case] private: &str,
189        #[case] business: &str,
190    ) {
191        assert_eq!(get_ws_base_url_public(region, OKXEnvironment::Live), public);
192        assert_eq!(
193            get_ws_base_url_private(region, OKXEnvironment::Live),
194            private
195        );
196        assert_eq!(
197            get_ws_base_url_business(region, OKXEnvironment::Live),
198            business
199        );
200    }
201
202    #[rstest]
203    #[case(
204        OKXRegion::Global,
205        "wss://wspap.okx.com:8443/ws/v5/public",
206        "wss://wspap.okx.com:8443/ws/v5/private",
207        "wss://wspap.okx.com:8443/ws/v5/business"
208    )]
209    #[case(
210        OKXRegion::Eea,
211        "wss://wseeapap.okx.com:8443/ws/v5/public",
212        "wss://wseeapap.okx.com:8443/ws/v5/private",
213        "wss://wseeapap.okx.com:8443/ws/v5/business"
214    )]
215    #[case(
216        OKXRegion::Us,
217        "wss://wsuspap.okx.com:8443/ws/v5/public",
218        "wss://wsuspap.okx.com:8443/ws/v5/private",
219        "wss://wsuspap.okx.com:8443/ws/v5/business"
220    )]
221    fn test_ws_urls_demo(
222        #[case] region: OKXRegion,
223        #[case] public: &str,
224        #[case] private: &str,
225        #[case] business: &str,
226    ) {
227        assert_eq!(get_ws_base_url_public(region, OKXEnvironment::Demo), public);
228        assert_eq!(
229            get_ws_base_url_private(region, OKXEnvironment::Demo),
230            private
231        );
232        assert_eq!(
233            get_ws_base_url_business(region, OKXEnvironment::Demo),
234            business
235        );
236    }
237
238    #[rstest]
239    #[case(
240        "wss://ws.okx.com:8443/ws/v5/public",
241        "business",
242        "wss://ws.okx.com:8443/ws/v5/business"
243    )]
244    #[case(
245        "wss://wseea.okx.com:8443/ws/v5/public",
246        "private",
247        "wss://wseea.okx.com:8443/ws/v5/private"
248    )]
249    #[case(
250        "wss://wseea.okx.com:8443/ws/v5/private",
251        "business",
252        "wss://wseea.okx.com:8443/ws/v5/business"
253    )]
254    #[case(
255        "wss://wseea.okx.com:8443/ws/v5/private/",
256        "business",
257        "wss://wseea.okx.com:8443/ws/v5/business"
258    )]
259    #[case(
260        "wss://custom.proxy:8443/ws/v5",
261        "business",
262        "wss://custom.proxy:8443/ws/v5/business"
263    )]
264    fn test_derive_ws_url(#[case] base_url: &str, #[case] channel: &str, #[case] expected: &str) {
265        assert_eq!(derive_ws_url(base_url, channel), expected);
266    }
267
268    #[rstest]
269    #[case(OKXRegion::Global)]
270    #[case(OKXRegion::Eea)]
271    #[case(OKXRegion::Us)]
272    fn test_get_ws_url_by_type(#[case] region: OKXRegion) {
273        assert_eq!(
274            get_ws_url(OKXEndpointType::Public, region, OKXEnvironment::Live),
275            get_ws_base_url_public(region, OKXEnvironment::Live)
276        );
277        assert_eq!(
278            get_ws_url(OKXEndpointType::Private, region, OKXEnvironment::Live),
279            get_ws_base_url_private(region, OKXEnvironment::Live)
280        );
281        assert_eq!(
282            get_ws_url(OKXEndpointType::Business, region, OKXEnvironment::Live),
283            get_ws_base_url_business(region, OKXEnvironment::Live)
284        );
285    }
286}