Skip to main content

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}