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, 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}