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;
19
20const OKX_HTTP_URL: &str = "https://www.okx.com";
21const OKX_WS_PUBLIC_URL: &str = "wss://ws.okx.com:8443/ws/v5/public";
22const OKX_WS_PRIVATE_URL: &str = "wss://ws.okx.com:8443/ws/v5/private";
23const OKX_WS_BUSINESS_URL: &str = "wss://ws.okx.com:8443/ws/v5/business";
24const OKX_DEMO_WS_PUBLIC_URL: &str = "wss://wspap.okx.com:8443/ws/v5/public";
25const OKX_DEMO_WS_PRIVATE_URL: &str = "wss://wspap.okx.com:8443/ws/v5/private";
26const OKX_DEMO_WS_BUSINESS_URL: &str = "wss://wspap.okx.com:8443/ws/v5/business";
27
28/// OKX endpoint types for determining URL and authentication requirements.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30#[cfg_attr(feature = "python", pyo3::pyclass(from_py_object))]
31#[cfg_attr(
32    feature = "python",
33    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.okx")
34)]
35pub enum OKXEndpointType {
36    Public,
37    Private,
38    Business,
39}
40
41/// Checks if endpoint requires authentication.
42pub fn requires_authentication(endpoint_type: OKXEndpointType) -> bool {
43    matches!(
44        endpoint_type,
45        OKXEndpointType::Private | OKXEndpointType::Business
46    )
47}
48
49/// Returns the HTTP base URL.
50#[must_use]
51pub const fn get_http_base_url() -> &'static str {
52    OKX_HTTP_URL
53}
54
55/// Returns the WebSocket base URL for public data (market data).
56#[must_use]
57pub fn get_ws_base_url_public(environment: OKXEnvironment) -> &'static str {
58    match environment {
59        OKXEnvironment::Demo => OKX_DEMO_WS_PUBLIC_URL,
60        OKXEnvironment::Live => OKX_WS_PUBLIC_URL,
61    }
62}
63
64/// Returns the WebSocket base URL for private data (account/order management).
65#[must_use]
66pub fn get_ws_base_url_private(environment: OKXEnvironment) -> &'static str {
67    match environment {
68        OKXEnvironment::Demo => OKX_DEMO_WS_PRIVATE_URL,
69        OKXEnvironment::Live => OKX_WS_PRIVATE_URL,
70    }
71}
72
73/// Returns the WebSocket base URL for business data (bars/candlesticks).
74#[must_use]
75pub fn get_ws_base_url_business(environment: OKXEnvironment) -> &'static str {
76    match environment {
77        OKXEnvironment::Demo => OKX_DEMO_WS_BUSINESS_URL,
78        OKXEnvironment::Live => OKX_WS_BUSINESS_URL,
79    }
80}
81
82/// Derives a WebSocket URL for a given channel from a base URL.
83///
84/// Replaces the last path segment (`/public`, `/private`, or `/business`)
85/// with the target channel. If no recognized segment is found, appends
86/// `/{channel}` to the path.
87#[must_use]
88pub fn derive_ws_url(base_url: &str, channel: &str) -> String {
89    let url = base_url.trim_end_matches('/');
90    for suffix in ["/public", "/private", "/business"] {
91        if let Some(base) = url.strip_suffix(suffix) {
92            return format!("{base}/{channel}");
93        }
94    }
95    format!("{url}/{channel}")
96}
97
98/// Returns WebSocket URL by endpoint type.
99#[must_use]
100pub fn get_ws_url(endpoint_type: OKXEndpointType, environment: OKXEnvironment) -> &'static str {
101    match endpoint_type {
102        OKXEndpointType::Public => get_ws_base_url_public(environment),
103        OKXEndpointType::Private => get_ws_base_url_private(environment),
104        OKXEndpointType::Business => get_ws_base_url_business(environment),
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use rstest::rstest;
111
112    use super::*;
113
114    #[rstest]
115    fn test_endpoint_authentication() {
116        assert!(!requires_authentication(OKXEndpointType::Public));
117        assert!(requires_authentication(OKXEndpointType::Private));
118        assert!(requires_authentication(OKXEndpointType::Business));
119    }
120
121    #[rstest]
122    fn test_http_base_url() {
123        assert_eq!(get_http_base_url(), OKX_HTTP_URL);
124    }
125
126    #[rstest]
127    fn test_ws_urls_live() {
128        assert_eq!(
129            get_ws_base_url_public(OKXEnvironment::Live),
130            OKX_WS_PUBLIC_URL
131        );
132        assert_eq!(
133            get_ws_base_url_private(OKXEnvironment::Live),
134            OKX_WS_PRIVATE_URL
135        );
136        assert_eq!(
137            get_ws_base_url_business(OKXEnvironment::Live),
138            OKX_WS_BUSINESS_URL
139        );
140    }
141
142    #[rstest]
143    fn test_ws_urls_demo() {
144        assert_eq!(
145            get_ws_base_url_public(OKXEnvironment::Demo),
146            OKX_DEMO_WS_PUBLIC_URL
147        );
148        assert_eq!(
149            get_ws_base_url_private(OKXEnvironment::Demo),
150            OKX_DEMO_WS_PRIVATE_URL
151        );
152        assert_eq!(
153            get_ws_base_url_business(OKXEnvironment::Demo),
154            OKX_DEMO_WS_BUSINESS_URL
155        );
156    }
157
158    #[rstest]
159    #[case(
160        "wss://ws.okx.com:8443/ws/v5/public",
161        "business",
162        "wss://ws.okx.com:8443/ws/v5/business"
163    )]
164    #[case(
165        "wss://wseea.okx.com:8443/ws/v5/public",
166        "private",
167        "wss://wseea.okx.com:8443/ws/v5/private"
168    )]
169    #[case(
170        "wss://wseea.okx.com:8443/ws/v5/private",
171        "business",
172        "wss://wseea.okx.com:8443/ws/v5/business"
173    )]
174    #[case(
175        "wss://wseea.okx.com:8443/ws/v5/private/",
176        "business",
177        "wss://wseea.okx.com:8443/ws/v5/business"
178    )]
179    #[case(
180        "wss://custom.proxy:8443/ws/v5",
181        "business",
182        "wss://custom.proxy:8443/ws/v5/business"
183    )]
184    fn test_derive_ws_url(#[case] base_url: &str, #[case] channel: &str, #[case] expected: &str) {
185        assert_eq!(derive_ws_url(base_url, channel), expected);
186    }
187
188    #[rstest]
189    fn test_get_ws_url_by_type() {
190        assert_eq!(
191            get_ws_url(OKXEndpointType::Public, OKXEnvironment::Live),
192            get_ws_base_url_public(OKXEnvironment::Live)
193        );
194        assert_eq!(
195            get_ws_url(OKXEndpointType::Private, OKXEnvironment::Live),
196            get_ws_base_url_private(OKXEnvironment::Live)
197        );
198        assert_eq!(
199            get_ws_url(OKXEndpointType::Business, OKXEnvironment::Live),
200            get_ws_base_url_business(OKXEnvironment::Live)
201        );
202    }
203}