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;
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/// "1s",
78/// |candle| {
79/// println!("Candle: {:?}", candle);
80/// },
81/// None,
82/// ).await.unwrap();
83/// }
84/// ```
85pub struct ChainStreamClient {
86 /// Access token for authentication
87 access_token: String,
88 /// Base URL for REST API
89 pub server_url: String,
90 /// WebSocket URL for Stream API
91 pub stream_url: String,
92 /// Stream API for real-time data subscriptions
93 pub stream: StreamApi,
94 /// Debug mode flag
95 pub debug: bool,
96}
97
98impl ChainStreamClient {
99 /// Create a new ChainStreamClient with an access token
100 ///
101 /// # Arguments
102 ///
103 /// * `access_token` - The access token for authentication
104 /// * `options` - Optional configuration options
105 ///
106 /// # Example
107 ///
108 /// ```rust,no_run
109 /// use chainstream_sdk::{ChainStreamClient, ChainStreamClientOptions};
110 ///
111 /// let client = ChainStreamClient::new("your-access-token", None);
112 ///
113 /// // Or with custom options
114 /// let client = ChainStreamClient::new("your-access-token", Some(ChainStreamClientOptions {
115 /// stream_url: Some("wss://custom-url.com/ws".to_string()),
116 /// ..Default::default()
117 /// }));
118 /// ```
119 pub fn new(access_token: &str, options: Option<ChainStreamClientOptions>) -> Self {
120 let opts = options.unwrap_or_default();
121
122 let server_url = opts
123 .server_url
124 .unwrap_or_else(|| CHAINSTREAM_BASE_URL.to_string());
125 let stream_url = opts
126 .stream_url
127 .unwrap_or_else(|| CHAINSTREAM_STREAM_URL.to_string());
128
129 let stream = StreamApi::new(&stream_url, access_token);
130
131 Self {
132 access_token: access_token.to_string(),
133 server_url,
134 stream_url,
135 stream,
136 debug: opts.debug,
137 }
138 }
139
140 /// Create a new ChainStreamClient with a token provider for dynamic token refresh
141 ///
142 /// # Arguments
143 ///
144 /// * `token_provider` - A token provider that can refresh tokens
145 /// * `options` - Optional configuration options
146 pub fn new_with_token_provider<T: TokenProvider>(
147 token_provider: &T,
148 options: Option<ChainStreamClientOptions>,
149 ) -> Self {
150 Self::new(&token_provider.get_token(), options)
151 }
152
153 /// Get the current access token
154 pub fn get_access_token(&self) -> &str {
155 &self.access_token
156 }
157
158 /// Close all connections
159 pub async fn close(&self) {
160 self.stream.disconnect().await;
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn test_client_creation() {
170 let client = ChainStreamClient::new("test-token", None);
171 assert_eq!(client.get_access_token(), "test-token");
172 assert_eq!(client.server_url, CHAINSTREAM_BASE_URL);
173 assert_eq!(client.stream_url, CHAINSTREAM_STREAM_URL);
174 }
175
176 #[test]
177 fn test_client_with_options() {
178 let options = ChainStreamClientOptions {
179 server_url: Some("https://custom-api.com".to_string()),
180 stream_url: Some("wss://custom-ws.com".to_string()),
181 auto_connect_websocket: false,
182 debug: true,
183 };
184
185 let client = ChainStreamClient::new("test-token", Some(options));
186 assert_eq!(client.server_url, "https://custom-api.com");
187 assert_eq!(client.stream_url, "wss://custom-ws.com");
188 assert!(client.debug);
189 }
190
191 #[test]
192 fn test_static_token_provider() {
193 let provider = StaticTokenProvider::new("my-token");
194 assert_eq!(provider.get_token(), "my-token");
195
196 let client = ChainStreamClient::new_with_token_provider(&provider, None);
197 assert_eq!(client.get_access_token(), "my-token");
198 }
199}