chainstream_sdk/chainstream.rs
1//! ChainStream Client - Main entry point for the SDK
2//!
3//! This module provides the main `ChainStreamClient` that integrates both
4//! REST API (OpenAPI) and WebSocket (Stream) functionality.
5
6use crate::stream::StreamApi;
7use crate::{CHAINSTREAM_BASE_URL, CHAINSTREAM_STREAM_URL};
8
9/// Options for configuring the ChainStream client
10#[derive(Debug, Clone)]
11pub struct ChainStreamClientOptions {
12 /// Base URL for the REST API
13 pub server_url: Option<String>,
14 /// WebSocket URL for the Stream API
15 pub stream_url: Option<String>,
16 /// Whether to automatically connect to WebSocket on first subscription
17 pub auto_connect_websocket: bool,
18 /// Enable debug logging
19 pub debug: bool,
20}
21
22impl Default for ChainStreamClientOptions {
23 fn default() -> Self {
24 Self {
25 server_url: None,
26 stream_url: None,
27 auto_connect_websocket: true,
28 debug: false,
29 }
30 }
31}
32
33/// Token provider trait for dynamic token refresh
34pub trait TokenProvider: Send + Sync {
35 /// Get the current access token
36 fn get_token(&self) -> String;
37}
38
39/// Simple token provider that holds a static token
40pub struct StaticTokenProvider {
41 token: String,
42}
43
44impl StaticTokenProvider {
45 /// Create a new static token provider
46 pub fn new(token: &str) -> Self {
47 Self {
48 token: token.to_string(),
49 }
50 }
51}
52
53impl TokenProvider for StaticTokenProvider {
54 fn get_token(&self) -> String {
55 self.token.clone()
56 }
57}
58
59/// Main ChainStream client that provides access to all SDK functionality
60///
61/// # Example
62///
63/// ```rust,no_run
64/// use chainstream_sdk::{ChainStreamClient, openapi::types::Resolution};
65///
66/// #[tokio::main]
67/// async fn main() {
68/// let client = ChainStreamClient::new("your-access-token", None);
69///
70/// // Use the stream API for real-time data
71/// client.stream.connect().await.unwrap();
72///
73/// // Subscribe to token candles
74/// let unsub = client.stream.subscribe_token_candles(
75/// "sol",
76/// "So11111111111111111111111111111111111111112",
77/// Resolution::X1s,
78/// |candle| {
79/// println!("Candle: {:?}", candle);
80/// },
81/// None,
82/// None,
83/// ).await.unwrap();
84/// }
85/// ```
86pub struct ChainStreamClient {
87 /// Access token for authentication
88 access_token: String,
89 /// Base URL for REST API
90 pub server_url: String,
91 /// WebSocket URL for Stream API
92 pub stream_url: String,
93 /// Stream API for real-time data subscriptions
94 pub stream: StreamApi,
95 /// Debug mode flag
96 pub debug: bool,
97}
98
99impl ChainStreamClient {
100 /// Create a new ChainStreamClient with an access token
101 ///
102 /// # Arguments
103 ///
104 /// * `access_token` - The access token for authentication
105 /// * `options` - Optional configuration options
106 ///
107 /// # Example
108 ///
109 /// ```rust,no_run
110 /// use chainstream_sdk::{ChainStreamClient, ChainStreamClientOptions};
111 ///
112 /// let client = ChainStreamClient::new("your-access-token", None);
113 ///
114 /// // Or with custom options
115 /// let client = ChainStreamClient::new("your-access-token", Some(ChainStreamClientOptions {
116 /// stream_url: Some("wss://custom-url.com/ws".to_string()),
117 /// ..Default::default()
118 /// }));
119 /// ```
120 pub fn new(access_token: &str, options: Option<ChainStreamClientOptions>) -> Self {
121 let opts = options.unwrap_or_default();
122
123 let server_url = opts
124 .server_url
125 .unwrap_or_else(|| CHAINSTREAM_BASE_URL.to_string());
126 let stream_url = opts
127 .stream_url
128 .unwrap_or_else(|| CHAINSTREAM_STREAM_URL.to_string());
129
130 let stream = StreamApi::new(&stream_url, access_token);
131
132 Self {
133 access_token: access_token.to_string(),
134 server_url,
135 stream_url,
136 stream,
137 debug: opts.debug,
138 }
139 }
140
141 /// Create a new ChainStreamClient with a token provider for dynamic token refresh
142 ///
143 /// # Arguments
144 ///
145 /// * `token_provider` - A token provider that can refresh tokens
146 /// * `options` - Optional configuration options
147 pub fn new_with_token_provider<T: TokenProvider>(
148 token_provider: &T,
149 options: Option<ChainStreamClientOptions>,
150 ) -> Self {
151 Self::new(&token_provider.get_token(), options)
152 }
153
154 /// Get the current access token
155 pub fn get_access_token(&self) -> &str {
156 &self.access_token
157 }
158
159 /// Close all connections
160 pub async fn close(&self) {
161 self.stream.disconnect().await;
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn test_client_creation() {
171 let client = ChainStreamClient::new("test-token", None);
172 assert_eq!(client.get_access_token(), "test-token");
173 assert_eq!(client.server_url, CHAINSTREAM_BASE_URL);
174 assert_eq!(client.stream_url, CHAINSTREAM_STREAM_URL);
175 }
176
177 #[test]
178 fn test_client_with_options() {
179 let options = ChainStreamClientOptions {
180 server_url: Some("https://custom-api.com".to_string()),
181 stream_url: Some("wss://custom-ws.com".to_string()),
182 auto_connect_websocket: false,
183 debug: true,
184 };
185
186 let client = ChainStreamClient::new("test-token", Some(options));
187 assert_eq!(client.server_url, "https://custom-api.com");
188 assert_eq!(client.stream_url, "wss://custom-ws.com");
189 assert!(client.debug);
190 }
191
192 #[test]
193 fn test_static_token_provider() {
194 let provider = StaticTokenProvider::new("my-token");
195 assert_eq!(provider.get_token(), "my-token");
196
197 let client = ChainStreamClient::new_with_token_provider(&provider, None);
198 assert_eq!(client.get_access_token(), "my-token");
199 }
200}