chainstream-sdk 2.0.11

SDK for interacting with the ChainStream API
Documentation
//! ChainStream Client - Main entry point for the SDK
//!
//! This module provides the main `ChainStreamClient` that integrates both
//! REST API (OpenAPI) and WebSocket (Stream) functionality.

use crate::stream::StreamApi;
use crate::{CHAINSTREAM_BASE_URL, CHAINSTREAM_STREAM_URL};

/// Options for configuring the ChainStream client
#[derive(Debug, Clone)]
pub struct ChainStreamClientOptions {
    /// Base URL for the REST API
    pub server_url: Option<String>,
    /// WebSocket URL for the Stream API
    pub stream_url: Option<String>,
    /// Whether to automatically connect to WebSocket on first subscription
    pub auto_connect_websocket: bool,
    /// Enable debug logging
    pub debug: bool,
}

impl Default for ChainStreamClientOptions {
    fn default() -> Self {
        Self {
            server_url: None,
            stream_url: None,
            auto_connect_websocket: true,
            debug: false,
        }
    }
}

/// Token provider trait for dynamic token refresh
pub trait TokenProvider: Send + Sync {
    /// Get the current access token
    fn get_token(&self) -> String;
}

/// Simple token provider that holds a static token
pub struct StaticTokenProvider {
    token: String,
}

impl StaticTokenProvider {
    /// Create a new static token provider
    pub fn new(token: &str) -> Self {
        Self {
            token: token.to_string(),
        }
    }
}

impl TokenProvider for StaticTokenProvider {
    fn get_token(&self) -> String {
        self.token.clone()
    }
}

/// Main ChainStream client that provides access to all SDK functionality
///
/// # Example
///
/// ```rust,no_run
/// use chainstream_sdk::ChainStreamClient;
///
/// #[tokio::main]
/// async fn main() {
///     let client = ChainStreamClient::new("your-access-token", None);
///
///     // Use the stream API for real-time data
///     client.stream.connect().await.unwrap();
///
///     // Subscribe to token candles
///     let unsub = client.stream.subscribe_token_candles(
///         "sol",
///         "So11111111111111111111111111111111111111112",
///         "1s",
///         |candle| {
///             println!("Candle: {:?}", candle);
///         },
///         None,
///     ).await.unwrap();
/// }
/// ```
pub struct ChainStreamClient {
    /// Access token for authentication
    access_token: String,
    /// Base URL for REST API
    pub server_url: String,
    /// WebSocket URL for Stream API
    pub stream_url: String,
    /// Stream API for real-time data subscriptions
    pub stream: StreamApi,
    /// Debug mode flag
    pub debug: bool,
}

impl ChainStreamClient {
    /// Create a new ChainStreamClient with an access token
    ///
    /// # Arguments
    ///
    /// * `access_token` - The access token for authentication
    /// * `options` - Optional configuration options
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use chainstream_sdk::{ChainStreamClient, ChainStreamClientOptions};
    ///
    /// let client = ChainStreamClient::new("your-access-token", None);
    ///
    /// // Or with custom options
    /// let client = ChainStreamClient::new("your-access-token", Some(ChainStreamClientOptions {
    ///     stream_url: Some("wss://custom-url.com/ws".to_string()),
    ///     ..Default::default()
    /// }));
    /// ```
    pub fn new(access_token: &str, options: Option<ChainStreamClientOptions>) -> Self {
        let opts = options.unwrap_or_default();

        let server_url = opts
            .server_url
            .unwrap_or_else(|| CHAINSTREAM_BASE_URL.to_string());
        let stream_url = opts
            .stream_url
            .unwrap_or_else(|| CHAINSTREAM_STREAM_URL.to_string());

        let stream = StreamApi::new(&stream_url, access_token);

        Self {
            access_token: access_token.to_string(),
            server_url,
            stream_url,
            stream,
            debug: opts.debug,
        }
    }

    /// Create a new ChainStreamClient with a token provider for dynamic token refresh
    ///
    /// # Arguments
    ///
    /// * `token_provider` - A token provider that can refresh tokens
    /// * `options` - Optional configuration options
    pub fn new_with_token_provider<T: TokenProvider>(
        token_provider: &T,
        options: Option<ChainStreamClientOptions>,
    ) -> Self {
        Self::new(&token_provider.get_token(), options)
    }

    /// Get the current access token
    pub fn get_access_token(&self) -> &str {
        &self.access_token
    }

    /// Close all connections
    pub async fn close(&self) {
        self.stream.disconnect().await;
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_client_creation() {
        let client = ChainStreamClient::new("test-token", None);
        assert_eq!(client.get_access_token(), "test-token");
        assert_eq!(client.server_url, CHAINSTREAM_BASE_URL);
        assert_eq!(client.stream_url, CHAINSTREAM_STREAM_URL);
    }

    #[test]
    fn test_client_with_options() {
        let options = ChainStreamClientOptions {
            server_url: Some("https://custom-api.com".to_string()),
            stream_url: Some("wss://custom-ws.com".to_string()),
            auto_connect_websocket: false,
            debug: true,
        };

        let client = ChainStreamClient::new("test-token", Some(options));
        assert_eq!(client.server_url, "https://custom-api.com");
        assert_eq!(client.stream_url, "wss://custom-ws.com");
        assert!(client.debug);
    }

    #[test]
    fn test_static_token_provider() {
        let provider = StaticTokenProvider::new("my-token");
        assert_eq!(provider.get_token(), "my-token");

        let client = ChainStreamClient::new_with_token_provider(&provider, None);
        assert_eq!(client.get_access_token(), "my-token");
    }
}